File Coverage

deps/libgit2/src/tsort.c
Criterion Covered Total %
statement 43 180 23.8
branch 18 134 13.4
condition n/a
subroutine n/a
pod n/a
total 61 314 19.4


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
4             * This file is part of libgit2, distributed under the GNU GPL v2 with
5             * a Linking Exception. For full terms see the included COPYING file.
6             */
7              
8             #include "common.h"
9              
10             /**
11             * An array-of-pointers implementation of Python's Timsort
12             * Based on code by Christopher Swenson under the MIT license
13             *
14             * Copyright (c) 2010 Christopher Swenson
15             * Copyright (c) 2011 Vicent Marti
16             */
17              
18             #ifndef MAX
19             # define MAX(x,y) (((x) > (y) ? (x) : (y)))
20             #endif
21              
22             #ifndef MIN
23             # define MIN(x,y) (((x) < (y) ? (x) : (y)))
24             #endif
25              
26 631           static int binsearch(
27             void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload)
28             {
29             int l, c, r;
30             void *lx, *cx;
31              
32 631 50         assert(size > 0);
33              
34 631           l = 0;
35 631           r = (int)size - 1;
36 631           c = r >> 1;
37 631           lx = dst[l];
38              
39             /* check for beginning conditions */
40 631 100         if (cmp(x, lx, payload) < 0)
41 348           return 0;
42              
43 283 50         else if (cmp(x, lx, payload) == 0) {
44 0           int i = 1;
45 0 0         while (cmp(x, dst[i], payload) == 0)
46 0           i++;
47 0           return i;
48             }
49              
50             /* guaranteed not to be >= rx */
51 283           cx = dst[c];
52             while (1) {
53 470           const int val = cmp(x, cx, payload);
54 470 100         if (val < 0) {
55 177 100         if (c - l <= 1) return c;
56 98           r = c;
57 293 50         } else if (val > 0) {
58 293 100         if (r - c <= 1) return c + 1;
59 89           l = c;
60 89           lx = cx;
61             } else {
62             do {
63 0           cx = dst[++c];
64 0 0         } while (cmp(x, cx, payload) == 0);
65 0           return c;
66             }
67 187           c = l + ((r - l) >> 1);
68 187           cx = dst[c];
69 187           }
70             }
71              
72             /* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
73 422           static void bisort(
74             void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload)
75             {
76             size_t i;
77             void *x;
78             int location;
79              
80 1405 100         for (i = start; i < size; i++) {
81             int j;
82             /* If this entry is already correct, just move along */
83 983 100         if (cmp(dst[i - 1], dst[i], payload) <= 0)
84 352           continue;
85              
86             /* Else we need to find the right place, shift everything over, and squeeze in */
87 631           x = dst[i];
88 631           location = binsearch(dst, x, i, cmp, payload);
89 1961 100         for (j = (int)i - 1; j >= location; j--) {
90 1330           dst[j + 1] = dst[j];
91             }
92 631           dst[location] = x;
93             }
94 422           }
95              
96              
97             /* timsort implementation, based on timsort.txt */
98             struct tsort_run {
99             ssize_t start;
100             ssize_t length;
101             };
102              
103             struct tsort_store {
104             size_t alloc;
105             git__sort_r_cmp cmp;
106             void *payload;
107             void **storage;
108             };
109              
110 0           static void reverse_elements(void **dst, ssize_t start, ssize_t end)
111             {
112 0 0         while (start < end) {
113 0           void *tmp = dst[start];
114 0           dst[start] = dst[end];
115 0           dst[end] = tmp;
116              
117 0           start++;
118 0           end--;
119             }
120 0           }
121              
122 0           static ssize_t count_run(
123             void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
124             {
125 0           ssize_t curr = start + 2;
126              
127 0 0         if (size - start == 1)
128 0           return 1;
129              
130 0 0         if (start >= size - 2) {
131 0 0         if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) {
132 0           void *tmp = dst[size - 1];
133 0           dst[size - 1] = dst[size - 2];
134 0           dst[size - 2] = tmp;
135             }
136              
137 0           return 2;
138             }
139              
140 0 0         if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) {
141 0           while (curr < size - 1 &&
142 0           store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0)
143 0           curr++;
144              
145 0           return curr - start;
146             } else {
147 0           while (curr < size - 1 &&
148 0           store->cmp(dst[curr - 1], dst[curr], store->payload) > 0)
149 0           curr++;
150              
151             /* reverse in-place */
152 0           reverse_elements(dst, start, curr - 1);
153 0           return curr - start;
154             }
155             }
156              
157 0           static size_t compute_minrun(size_t n)
158             {
159 0           int r = 0;
160 0 0         while (n >= 64) {
161 0           r |= n & 1;
162 0           n >>= 1;
163             }
164 0           return n + r;
165             }
166              
167 0           static int check_invariant(struct tsort_run *stack, ssize_t stack_curr)
168             {
169 0 0         if (stack_curr < 2)
170 0           return 1;
171              
172 0 0         else if (stack_curr == 2) {
173 0           const ssize_t A = stack[stack_curr - 2].length;
174 0           const ssize_t B = stack[stack_curr - 1].length;
175 0           return (A > B);
176             } else {
177 0           const ssize_t A = stack[stack_curr - 3].length;
178 0           const ssize_t B = stack[stack_curr - 2].length;
179 0           const ssize_t C = stack[stack_curr - 1].length;
180 0 0         return !((A <= B + C) || (B <= C));
    0          
181             }
182             }
183              
184 0           static int resize(struct tsort_store *store, size_t new_size)
185             {
186 0 0         if (store->alloc < new_size) {
187             void **tempstore;
188              
189 0           tempstore = git__reallocarray(store->storage, new_size, sizeof(void *));
190              
191             /**
192             * Do not propagate on OOM; this will abort the sort and
193             * leave the array unsorted, but no error code will be
194             * raised
195             */
196 0 0         if (tempstore == NULL)
197 0           return -1;
198              
199 0           store->storage = tempstore;
200 0           store->alloc = new_size;
201             }
202              
203 0           return 0;
204             }
205              
206 0           static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store)
207             {
208 0           const ssize_t A = stack[stack_curr - 2].length;
209 0           const ssize_t B = stack[stack_curr - 1].length;
210 0           const ssize_t curr = stack[stack_curr - 2].start;
211              
212             void **storage;
213             ssize_t i, j, k;
214              
215 0 0         if (resize(store, MIN(A, B)) < 0)
216 0           return;
217              
218 0           storage = store->storage;
219              
220             /* left merge */
221 0 0         if (A < B) {
222 0           memcpy(storage, &dst[curr], A * sizeof(void *));
223 0           i = 0;
224 0           j = curr + A;
225              
226 0 0         for (k = curr; k < curr + A + B; k++) {
227 0 0         if ((i < A) && (j < curr + A + B)) {
    0          
228 0 0         if (store->cmp(storage[i], dst[j], store->payload) <= 0)
229 0           dst[k] = storage[i++];
230             else
231 0           dst[k] = dst[j++];
232 0 0         } else if (i < A) {
233 0           dst[k] = storage[i++];
234             } else
235 0           dst[k] = dst[j++];
236             }
237             } else {
238 0           memcpy(storage, &dst[curr + A], B * sizeof(void *));
239 0           i = B - 1;
240 0           j = curr + A - 1;
241              
242 0 0         for (k = curr + A + B - 1; k >= curr; k--) {
243 0 0         if ((i >= 0) && (j >= curr)) {
    0          
244 0 0         if (store->cmp(dst[j], storage[i], store->payload) > 0)
245 0           dst[k] = dst[j--];
246             else
247 0           dst[k] = storage[i--];
248 0 0         } else if (i >= 0)
249 0           dst[k] = storage[i--];
250             else
251 0           dst[k] = dst[j--];
252             }
253             }
254             }
255              
256 0           static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size)
257             {
258             ssize_t A, B, C;
259              
260             while (1) {
261             /* if the stack only has one thing on it, we are done with the collapse */
262 0 0         if (stack_curr <= 1)
263 0           break;
264              
265             /* if this is the last merge, just do it */
266 0 0         if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) {
    0          
267 0           merge(dst, stack, stack_curr, store);
268 0           stack[0].length += stack[1].length;
269 0           stack_curr--;
270 0           break;
271             }
272              
273             /* check if the invariant is off for a stack of 2 elements */
274 0 0         else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) {
    0          
275 0           merge(dst, stack, stack_curr, store);
276 0           stack[0].length += stack[1].length;
277 0           stack_curr--;
278 0           break;
279             }
280 0 0         else if (stack_curr == 2)
281 0           break;
282              
283 0           A = stack[stack_curr - 3].length;
284 0           B = stack[stack_curr - 2].length;
285 0           C = stack[stack_curr - 1].length;
286              
287             /* check first invariant */
288 0 0         if (A <= B + C) {
289 0 0         if (A < C) {
290 0           merge(dst, stack, stack_curr - 1, store);
291 0           stack[stack_curr - 3].length += stack[stack_curr - 2].length;
292 0           stack[stack_curr - 2] = stack[stack_curr - 1];
293 0           stack_curr--;
294             } else {
295 0           merge(dst, stack, stack_curr, store);
296 0           stack[stack_curr - 2].length += stack[stack_curr - 1].length;
297 0           stack_curr--;
298             }
299 0 0         } else if (B <= C) {
300 0           merge(dst, stack, stack_curr, store);
301 0           stack[stack_curr - 2].length += stack[stack_curr - 1].length;
302 0           stack_curr--;
303             } else
304 0           break;
305 0           }
306              
307 0           return stack_curr;
308             }
309              
310             #define PUSH_NEXT() do {\
311             len = count_run(dst, curr, size, store);\
312             run = minrun;\
313             if (run > (ssize_t)size - curr) run = size - curr;\
314             if (run > len) {\
315             bisort(&dst[curr], len, run, cmp, payload);\
316             len = run;\
317             }\
318             run_stack[stack_curr].start = curr;\
319             run_stack[stack_curr++].length = len;\
320             curr += len;\
321             if (curr == (ssize_t)size) {\
322             /* finish up */ \
323             while (stack_curr > 1) { \
324             merge(dst, run_stack, stack_curr, store); \
325             run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \
326             stack_curr--; \
327             } \
328             if (store->storage != NULL) {\
329             git__free(store->storage);\
330             store->storage = NULL;\
331             }\
332             return;\
333             }\
334             }\
335             while (0)
336              
337 422           void git__tsort_r(
338             void **dst, size_t size, git__sort_r_cmp cmp, void *payload)
339             {
340 422           struct tsort_store _store, *store = &_store;
341             struct tsort_run run_stack[128];
342              
343 422           ssize_t stack_curr = 0;
344             ssize_t len, run;
345 422           ssize_t curr = 0;
346             ssize_t minrun;
347              
348 422 50         if (size < 64) {
349 422           bisort(dst, 1, size, cmp, payload);
350 422           return;
351             }
352              
353             /* compute the minimum run length */
354 0           minrun = (ssize_t)compute_minrun(size);
355              
356             /* temporary storage for merges */
357 0           store->alloc = 0;
358 0           store->storage = NULL;
359 0           store->cmp = cmp;
360 0           store->payload = payload;
361              
362 0 0         PUSH_NEXT();
    0          
    0          
    0          
    0          
363 0 0         PUSH_NEXT();
    0          
    0          
    0          
    0          
364 0 0         PUSH_NEXT();
    0          
    0          
    0          
    0          
365              
366             while (1) {
367 0 0         if (!check_invariant(run_stack, stack_curr)) {
368 0           stack_curr = collapse(dst, run_stack, stack_curr, store, size);
369 0           continue;
370             }
371              
372 0 0         PUSH_NEXT();
    0          
    0          
    0          
    0          
373 0           }
374             }
375              
376 2367           static int tsort_r_cmp(const void *a, const void *b, void *payload)
377             {
378 2367           return ((git__tsort_cmp)payload)(a, b);
379             }
380              
381 422           void git__tsort(void **dst, size_t size, git__tsort_cmp cmp)
382             {
383 422           git__tsort_r(dst, size, tsort_r_cmp, cmp);
384 422           }