Skip to content

Commit 7e28138

Browse files
committed
Merge branch 'rs/qsort-s' into next
A few codepaths had to rely on a global variable when sorting elements of an array because sort(3) API does not allow extra data to be passed to the comparison function. Use qsort_s() when natively available, and a fallback implementation of it when not, to eliminate the need, which is a prerequisite for making the codepath reentrant. * rs/qsort-s: ref-filter: use QSORT_S in ref_array_sort() string-list: use QSORT_S in string_list_sort() perf: add basic sort performance test add QSORT_S compat: add qsort_s()
2 parents af6dd9d + 83fc4d6 commit 7e28138

File tree

7 files changed

+146
-12
lines changed

7 files changed

+146
-12
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ all::
279279
# is a simplified version of the merge sort used in glibc. This is
280280
# recommended if Git triggers O(n^2) behavior in your platform's qsort().
281281
#
282+
# Define HAVE_ISO_QSORT_S if your platform provides a qsort_s() that's
283+
# compatible with the one described in C11 Annex K.
284+
#
282285
# Define UNRELIABLE_FSTAT if your system's fstat does not return the same
283286
# information on a not yet closed file that lstat would return for the same
284287
# file after it was closed.
@@ -1418,6 +1421,11 @@ ifdef INTERNAL_QSORT
14181421
COMPAT_CFLAGS += -DINTERNAL_QSORT
14191422
COMPAT_OBJS += compat/qsort.o
14201423
endif
1424+
ifdef HAVE_ISO_QSORT_S
1425+
COMPAT_CFLAGS += -DHAVE_ISO_QSORT_S
1426+
else
1427+
COMPAT_OBJS += compat/qsort_s.o
1428+
endif
14211429
ifdef RUNTIME_PREFIX
14221430
COMPAT_CFLAGS += -DRUNTIME_PREFIX
14231431
endif

compat/qsort_s.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "../git-compat-util.h"
2+
3+
/*
4+
* A merge sort implementation, simplified from the qsort implementation
5+
* by Mike Haertel, which is a part of the GNU C Library.
6+
* Added context pointer, safety checks and return value.
7+
*/
8+
9+
static void msort_with_tmp(void *b, size_t n, size_t s,
10+
int (*cmp)(const void *, const void *, void *),
11+
char *t, void *ctx)
12+
{
13+
char *tmp;
14+
char *b1, *b2;
15+
size_t n1, n2;
16+
17+
if (n <= 1)
18+
return;
19+
20+
n1 = n / 2;
21+
n2 = n - n1;
22+
b1 = b;
23+
b2 = (char *)b + (n1 * s);
24+
25+
msort_with_tmp(b1, n1, s, cmp, t, ctx);
26+
msort_with_tmp(b2, n2, s, cmp, t, ctx);
27+
28+
tmp = t;
29+
30+
while (n1 > 0 && n2 > 0) {
31+
if (cmp(b1, b2, ctx) <= 0) {
32+
memcpy(tmp, b1, s);
33+
tmp += s;
34+
b1 += s;
35+
--n1;
36+
} else {
37+
memcpy(tmp, b2, s);
38+
tmp += s;
39+
b2 += s;
40+
--n2;
41+
}
42+
}
43+
if (n1 > 0)
44+
memcpy(tmp, b1, n1 * s);
45+
memcpy(b, t, (n - n2) * s);
46+
}
47+
48+
int git_qsort_s(void *b, size_t n, size_t s,
49+
int (*cmp)(const void *, const void *, void *), void *ctx)
50+
{
51+
const size_t size = st_mult(n, s);
52+
char buf[1024];
53+
54+
if (!n)
55+
return 0;
56+
if (!b || !cmp)
57+
return -1;
58+
59+
if (size < sizeof(buf)) {
60+
/* The temporary array fits on the small on-stack buffer. */
61+
msort_with_tmp(b, n, s, cmp, buf, ctx);
62+
} else {
63+
/* It's somewhat large, so malloc it. */
64+
char *tmp = xmalloc(size);
65+
msort_with_tmp(b, n, s, cmp, tmp, ctx);
66+
free(tmp);
67+
}
68+
return 0;
69+
}

git-compat-util.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,17 @@ static inline void sane_qsort(void *base, size_t nmemb, size_t size,
988988
qsort(base, nmemb, size, compar);
989989
}
990990

991+
#ifndef HAVE_ISO_QSORT_S
992+
int git_qsort_s(void *base, size_t nmemb, size_t size,
993+
int (*compar)(const void *, const void *, void *), void *ctx);
994+
#define qsort_s git_qsort_s
995+
#endif
996+
997+
#define QSORT_S(base, n, compar, ctx) do { \
998+
if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \
999+
die("BUG: qsort_s() failed"); \
1000+
} while (0)
1001+
9911002
#ifndef REG_STARTEND
9921003
#error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd"
9931004
#endif

ref-filter.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,8 +1594,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
15941594
return (s->reverse) ? -cmp : cmp;
15951595
}
15961596

1597-
static struct ref_sorting *ref_sorting;
1598-
static int compare_refs(const void *a_, const void *b_)
1597+
static int compare_refs(const void *a_, const void *b_, void *ref_sorting)
15991598
{
16001599
struct ref_array_item *a = *((struct ref_array_item **)a_);
16011600
struct ref_array_item *b = *((struct ref_array_item **)b_);
@@ -1611,8 +1610,7 @@ static int compare_refs(const void *a_, const void *b_)
16111610

16121611
void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array)
16131612
{
1614-
ref_sorting = sorting;
1615-
QSORT(array->items, array->nr, compare_refs);
1613+
QSORT_S(array->items, array->nr, compare_refs, sorting);
16161614
}
16171615

16181616
static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state)

string-list.c

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,21 +211,18 @@ struct string_list_item *string_list_append(struct string_list *list,
211211
list->strdup_strings ? xstrdup(string) : (char *)string);
212212
}
213213

214-
/* Yuck */
215-
static compare_strings_fn compare_for_qsort;
216-
217-
/* Only call this from inside string_list_sort! */
218-
static int cmp_items(const void *a, const void *b)
214+
static int cmp_items(const void *a, const void *b, void *ctx)
219215
{
216+
compare_strings_fn cmp = ctx;
220217
const struct string_list_item *one = a;
221218
const struct string_list_item *two = b;
222-
return compare_for_qsort(one->string, two->string);
219+
return cmp(one->string, two->string);
223220
}
224221

225222
void string_list_sort(struct string_list *list)
226223
{
227-
compare_for_qsort = list->cmp ? list->cmp : strcmp;
228-
QSORT(list->items, list->nr, cmp_items);
224+
QSORT_S(list->items, list->nr, cmp_items,
225+
list->cmp ? list->cmp : strcmp);
229226
}
230227

231228
struct string_list_item *unsorted_string_list_lookup(struct string_list *list,

t/helper/test-string-list.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,31 @@ int cmd_main(int argc, const char **argv)
9797
return 0;
9898
}
9999

100+
if (argc == 2 && !strcmp(argv[1], "sort")) {
101+
struct string_list list = STRING_LIST_INIT_NODUP;
102+
struct strbuf sb = STRBUF_INIT;
103+
struct string_list_item *item;
104+
105+
strbuf_read(&sb, 0, 0);
106+
107+
/*
108+
* Split by newline, but don't create a string_list item
109+
* for the empty string after the last separator.
110+
*/
111+
if (sb.buf[sb.len - 1] == '\n')
112+
strbuf_setlen(&sb, sb.len - 1);
113+
string_list_split_in_place(&list, sb.buf, '\n', -1);
114+
115+
string_list_sort(&list);
116+
117+
for_each_string_list_item(item, &list)
118+
puts(item->string);
119+
120+
string_list_clear(&list, 0);
121+
strbuf_release(&sb);
122+
return 0;
123+
}
124+
100125
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
101126
argv[1] ? argv[1] : "(there was none)");
102127
return 1;

t/perf/p0071-sort.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/sh
2+
3+
test_description='Basic sort performance tests'
4+
. ./perf-lib.sh
5+
6+
test_perf_default_repo
7+
8+
test_expect_success 'setup' '
9+
git ls-files --stage "*.[ch]" "*.sh" |
10+
cut -f2 -d" " |
11+
git cat-file --batch >unsorted
12+
'
13+
14+
test_perf 'sort(1)' '
15+
sort <unsorted >expect
16+
'
17+
18+
test_perf 'string_list_sort()' '
19+
test-string-list sort <unsorted >actual
20+
'
21+
22+
test_expect_success 'string_list_sort() sorts like sort(1)' '
23+
test_cmp_bin expect actual
24+
'
25+
26+
test_done

0 commit comments

Comments
 (0)