summaryrefslogtreecommitdiffstats
path: root/lib/util/stable_sort.c
blob: 47667c4284e96519a26a6702ec26bbcd36dac736 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
   Stable sort routines

   Copyright © Douglas Bagnall <douglas.bagnall@catalyst.net.nz>

     ** NOTE! The following LGPL license applies to this file which is used by
     ** the ldb library. This does NOT imply that all of Samba is released
     ** under the LGPL.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <talloc.h>
#include "replace.h"
#include "stable_sort.h"

static void sort_few(char *array, char *aux,
		     size_t n,
		     size_t s,
		     samba_compare_with_context_fn_t cmpfn,
		     void *opaque)
{
	/* a kind of insertion sort for small n. */
	int i, j, dist;
	int cmp;
	char *a, *b;

	for (i = 1; i < n; i++) {
		a = &array[i * s];
		/* leftwards is sorted. look until we find this one's place */
		for (j = i - 1; j >= 0; j--) {
			b = &array[j * s];
			cmp = cmpfn(a, b, opaque);
			if (cmp >= 0) {
				break;
			}
		}
		dist = i - 1 - j;
		if (dist == 0) {
			/* a is already in the right place */
			continue;
		}

		b = &array[(i - dist) * s];
		memcpy(aux, a, s);
		memmove(b + s, b, s * dist);
		memcpy(b, aux, s);
	}
}


static void merge(char *dest,
		  char *a, size_t alen,
		  char *b, size_t blen,
		  size_t s,
		  samba_compare_with_context_fn_t cmpfn,
		  void *opaque)
{
	size_t ai = 0;
	size_t bi = 0;
	size_t di = 0;
	while (ai < alen && bi < blen) {
		int cmp = cmpfn(&a[ai * s], &b[bi * s], opaque);
		if (cmp <= 0) {
			memcpy(&dest[di * s], &a[ai * s], s);
			ai++;
		} else {
			memcpy(&dest[di * s], &b[bi * s], s);
			bi++;
		}
		di++;
	}
	if (ai < alen) {
		memcpy(&dest[di * s], &a[ai * s], s * (alen - ai));
	} else if (bi < blen) {
		memcpy(&dest[di * s], &b[bi * s], s * (blen - bi));
	}
}


bool stable_sort_r(void *array, void *aux,
		   size_t n,
		   size_t s,
		   samba_compare_with_context_fn_t cmpfn,
		   void * opaque)
{
	char *src = array, *dest = aux, *tmp = NULL;
	size_t i, j, k;
	size_t runsize;
	if (array == NULL || aux == NULL) {
		return false;
	}

	if (n < 20) {
		sort_few(array, aux, n, s, cmpfn, opaque);
		return true;
	}

	if (n > SIZE_MAX / s) {
		/*
		 * We will have an integer overflow if we continue.
		 *
		 * This means that the *supposed* size of the allocated buffer
		 * is greater than SIZE_MAX, which is not possible in theory
		 * or practice, and is a sign the caller has got very
		 * confused.
		 */
		return false;
	}

	/*
	 * This is kind of a bottom-up merge sort.
	 *
	 * We start but sorting into a whole lot of little runs, using an
	 * insertion sort which is efficient for small numbers. Empirically,
	 * on 2 machines, a run size of around 8 seems optimal, but the peak
	 * is wide, and it seems worth adapting the size to avoid an
	 * unbalanced final merge at the top. That is, if we pick the right
	 * runsize now, we will finish with a merge of roughly n/2:n/2, and
	 * not have to follow that with an merge of roughly n:[a few], which
	 * we would sometimes do with a fixed size at the lowest level.
	 *
	 * The aim is a runsize of n / (a power of 2) rounded up, in the
	 * target range.
	 */

	runsize = n;
	while (runsize > 10) {
		runsize++;
		runsize >>= 1;
	}

	for (i = 0; i < n; i += runsize) {
		size_t nn = MIN(n - i, runsize);
		sort_few(&src[i * s], aux, nn, s, cmpfn, opaque);
	}

	while (runsize < n) {
		for (i = 0; i < n; i += runsize * 2) {
			j = i + runsize;
			if (j >= n) {
				/*
				 * first run doesn't fit, meaning this chunk
				 * is already sorted. We just need to copy
				 * it.
				 */
				size_t nn = n - i;
				memcpy(&dest[i * s], &src[i * s], nn * s);
				break;
			}
			k = j + runsize;
			if (k > n) {
				merge(&dest[i * s],
				      &src[i * s], runsize,
				      &src[j * s], n - j,
				      s,
				      cmpfn, opaque);
			} else {
				merge(&dest[i * s],
				      &src[i * s], runsize,
				      &src[j * s], runsize,
				      s,
				      cmpfn, opaque);
			}
		}

		tmp = src;
		src = dest;
		dest = tmp;
		runsize *= 2;
	}
	/*
	 * We have sorted the array into src, which is either array or aux.
	 */
	if (src != array) {
		memcpy(array, src, n * s);
	}
	return true;
}



/*
 * A wrapper that allocates (and frees) the temporary buffer if necessary.
 *
 * returns false on allocation error, true otherwise.
 */

bool stable_sort_talloc_r(TALLOC_CTX *mem_ctx,
			  void *array,
			  size_t n,
			  size_t s,
			  samba_compare_with_context_fn_t cmpfn,
			  void *opaque)
{
	bool ok;
	char *mem = talloc_array_size(mem_ctx, s, n);
	if (mem == NULL) {
		return false;
	}
	ok = stable_sort_r(array, mem, n, s, cmpfn, opaque);
	talloc_free(mem);
	return ok;
}


bool stable_sort(void *array, void *aux,
		 size_t n,
		 size_t s,
		 samba_compare_fn_t cmpfn)
{
	/*
	 * What is this magic, casting cmpfn into a different type that takes
	 * an extra parameter? Is that allowed?
	 *
	 * A: Yes. It's fine. The extra argument will be passed on the stack
	 * or (more likely) a register, and the cmpfn will remain blissfully
	 * unaware.
	 */
	return stable_sort_r(array, aux, n, s,
			     (samba_compare_with_context_fn_t)cmpfn,
			     NULL);
}


bool stable_sort_talloc(TALLOC_CTX *mem_ctx,
			void *array,
			size_t n,
			size_t s,
			samba_compare_fn_t cmpfn)
{
	return stable_sort_talloc_r(mem_ctx, array, n, s,
				    (samba_compare_with_context_fn_t)cmpfn,
				    NULL);
}