xref: /illumos-gate/usr/src/tools/smatch/src/sort.c (revision c85f09cc)
1 /*
2  * sort_list: a stable sort for lists.
3  *
4  * Time complexity: O(n*log n)
5  *   [assuming limited zero-element fragments]
6  *
7  * Space complexity: O(1).
8  *
9  * Stable: yes.
10  */
11 
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include "lib.h"
17 #include "allocate.h"
18 
19 #undef PARANOIA
20 #undef COVERAGE
21 
22 #ifdef PARANOIA
23 #include <assert.h>
24 #else
25 #define assert(x)
26 #endif
27 
28 #ifdef COVERAGE
29 static unsigned char been_there[256];
30 #define BEEN_THERE(_c)					\
31   do {							\
32 	if (!been_there[_c]) {				\
33 		been_there[_c] = 1;			\
34 		printf ("Been there: %c\n", _c);	\
35 	}						\
36   } while (0)
37 #else
38 #define BEEN_THERE(_c) do { } while (0)
39 #endif
40 
41 // Sort one fragment.  LIST_NODE_NR (==29) is a bit too high for my
42 // taste for something this simple.  But, hey, it's O(1).
43 //
44 // I would use libc qsort for this, but its comparison function
45 // gets a pointer indirection extra.
array_sort(void ** ptr,int nr,int (* cmp)(const void *,const void *))46 static void array_sort(void **ptr, int nr, int (*cmp)(const void *, const void *))
47 {
48 	int i;
49 	for (i = 1; i < nr; i++) {
50 		void *p = ptr[i];
51 		if (cmp(ptr[i-1],p) > 0) {
52 			int j = i;
53 			do {
54 				ptr[j] = ptr[j-1];
55 				if (!--j)
56 					break;
57 			} while (cmp(ptr[j-1], p) > 0);
58 			ptr[j] = p;
59 		}
60 	}
61 }
62 
63 #ifdef PARANOIA
verify_seq_sorted(struct ptr_list * l,int n,int (* cmp)(const void *,const void *))64 static void verify_seq_sorted (struct ptr_list *l, int n,
65 			       int (*cmp)(const void *, const void *))
66 {
67 	int i = 0;
68 	const void *a;
69 	struct ptr_list *head = l;
70 
71 	while (l->nr == 0) {
72 		l = l->next;
73 		if (--n == 0)
74 			return;
75 		assert (l != head);
76 	}
77 
78 	a = l->list[0];
79 	while (n > 0) {
80 		const void *b;
81 		if (++i >= l->nr) {
82 			i = 0;
83 			l = l->next;
84 			n--;
85 			assert (l != head || n == 0);
86 			continue;
87 		}
88 		b = l->list[i];
89 		assert (cmp (a, b) <= 0);
90 		a = b;
91 	}
92 }
93 #endif
94 
95 
96 #define FLUSH_TO(b)						\
97   do {								\
98 	int nr = (b)->nr;					\
99 	assert (nbuf >= nr);					\
100 	memcpy ((b)->list, buffer, nr * sizeof (void *));	\
101 	nbuf -= nr;						\
102 	memmove (buffer, buffer + nr, nbuf * sizeof (void *));	\
103   } while (0)
104 
105 #define DUMP_TO(b)						\
106   do {								\
107         assert (nbuf <= (b)->nr);				\
108 	memcpy ((b)->list, buffer, nbuf * sizeof (void *));	\
109   } while (0)
110 
111 
112 // Merge two already-sorted sequences of blocks:
113 //   (b1_1, ..., b1_n)  and  (b2_1, ..., b2_m)
114 // Since we may be moving blocks around, we return the new head
115 // of the merged list.
116 static struct ptr_list *
merge_block_seqs(struct ptr_list * b1,int n,struct ptr_list * b2,int m,int (* cmp)(const void *,const void *))117 merge_block_seqs (struct ptr_list *b1, int n,
118 		  struct ptr_list *b2, int m,
119 		  int (*cmp)(const void *, const void *))
120 {
121 	int i1 = 0, i2 = 0;
122 	const void *buffer[2 * LIST_NODE_NR];
123 	int nbuf = 0;
124 	struct ptr_list *newhead = b1;
125 
126 	// printf ("Merging %d blocks at %p with %d blocks at %p\n", n, b1, m, b2);
127 
128 	// Skip empty blocks in b2.
129 	while (b2->nr == 0) {
130 		BEEN_THERE('F');
131 		b2 = b2->next;
132 		if (--m == 0) {
133 			BEEN_THERE('G');
134 			return newhead;
135 		}
136 	}
137 
138 	// Do a quick skip in case entire blocks from b1 are
139 	// already less than smallest element in b2.
140 	while (b1->nr == 0 ||
141 	       cmp (PTR_ENTRY_NOTAG(b1, b1->nr - 1), PTR_ENTRY_NOTAG(b2,0)) < 0) {
142 		// printf ("Skipping whole block.\n");
143 		BEEN_THERE('H');
144 		b1 = b1->next;
145 		if (--n == 0) {
146 			BEEN_THERE('I');
147 			return newhead;
148 		}
149 	}
150 
151 	while (1) {
152 		const void *d1 = PTR_ENTRY_NOTAG(b1,i1);
153 		const void *d2 = PTR_ENTRY_NOTAG(b2,i2);
154 
155 		assert (i1 >= 0 && i1 < b1->nr);
156 		assert (i2 >= 0 && i2 < b2->nr);
157 		assert (b1 != b2);
158 		assert (n > 0);
159 		assert (m > 0);
160 
161 		if (cmp (d1, d2) <= 0) {
162 			BEEN_THERE('J');
163 			buffer[nbuf++] = d1;
164 			// Element from b1 is smaller
165 			if (++i1 >= b1->nr) {
166 				BEEN_THERE('L');
167 				FLUSH_TO(b1);
168 				do {
169 					b1 = b1->next;
170 					if (--n == 0) {
171 						BEEN_THERE('O');
172 						while (b1 != b2) {
173 							BEEN_THERE('P');
174 							FLUSH_TO(b1);
175 							b1 = b1->next;
176 						}
177 						assert (nbuf == i2);
178 						DUMP_TO(b2);
179 						return newhead;
180 					}
181 				} while (b1->nr == 0);
182 				i1 = 0;
183 			}
184 		} else {
185 			BEEN_THERE('K');
186 			// Element from b2 is smaller
187 			buffer[nbuf++] = d2;
188 			if (++i2 >= b2->nr) {
189 				struct ptr_list *l = b2;
190 				BEEN_THERE('M');
191 				// OK, we finished with b2.  Pull it out
192 				// and plug it in before b1.
193 
194 				b2 = b2->next;
195 				b2->prev = l->prev;
196 				b2->prev->next = b2;
197 				l->next = b1;
198 				l->prev = b1->prev;
199 				l->next->prev = l;
200 				l->prev->next = l;
201 
202 				if (b1 == newhead) {
203 					BEEN_THERE('N');
204 					newhead = l;
205 				}
206 
207 				FLUSH_TO(l);
208 				b2 = b2->prev;
209 				do {
210 					b2 = b2->next;
211 					if (--m == 0) {
212 						BEEN_THERE('Q');
213 						assert (nbuf == i1);
214 						DUMP_TO(b1);
215 						return newhead;
216 					}
217 				} while (b2->nr == 0);
218 				i2 = 0;
219 			}
220 		}
221 	}
222 }
223 
224 
sort_list(struct ptr_list ** plist,int (* cmp)(const void *,const void *))225 void sort_list(struct ptr_list **plist, int (*cmp)(const void *, const void *))
226 {
227 	struct ptr_list *head = *plist, *list = head;
228 	int blocks = 1;
229 
230 	if (!head)
231 		return;
232 
233 	// Sort all the sub-lists
234 	do {
235 		array_sort(list->list, list->nr, cmp);
236 #ifdef PARANOIA
237 		verify_seq_sorted (list, 1, cmp);
238 #endif
239 		list = list->next;
240 	} while (list != head);
241 
242 	// Merge the damn things together
243 	while (1) {
244 		struct ptr_list *block1 = head;
245 
246 		do {
247 			struct ptr_list *block2 = block1;
248 			struct ptr_list *next, *newhead;
249 			int i;
250 
251 			for (i = 0; i < blocks; i++) {
252 				block2 = block2->next;
253 				if (block2 == head) {
254 					if (block1 == head) {
255 						BEEN_THERE('A');
256 						*plist = head;
257 						return;
258 					}
259 					BEEN_THERE('B');
260 					goto next_pass;
261 				}
262 			}
263 
264 			next = block2;
265 			for (i = 0; i < blocks; ) {
266 				next = next->next;
267 				i++;
268 				if (next == head) {
269 					BEEN_THERE('C');
270 					break;
271 				}
272 				BEEN_THERE('D');
273 			}
274 
275 			newhead = merge_block_seqs (block1, blocks,
276 						    block2, i,
277 						    cmp);
278 #ifdef PARANOIA
279 			verify_seq_sorted (newhead, blocks + i, cmp);
280 #endif
281 			if (block1 == head) {
282 				BEEN_THERE('E');
283 				head = newhead;
284 			}
285 			block1 = next;
286 		} while (block1 != head);
287 	next_pass:
288 		blocks <<= 1;
289 	}
290 }
291