Skip to content

Commit e5530c9

Browse files
committed
core/clist_sort: rewrite
This adds a new merge sort implementation that strives to be better documented than the previous one and to compile with `-fanalyzer` without GCC barking.
1 parent dbbeae3 commit e5530c9

File tree

3 files changed

+226
-144
lines changed

3 files changed

+226
-144
lines changed

core/lib/clist.c

+224-121
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,256 @@
11
/*
2-
* Copyright (C) 2017 Inria
3-
* 2017 Freie Universität Berlin
4-
* 2017 Kaspar Schleiser <[email protected]>
2+
* Copyright (C) 2025 Marian Buschsieweke <[email protected]>
53
*
64
* This file is subject to the terms and conditions of the GNU Lesser
75
* General Public License v2.1. See the file LICENSE in the top level
86
* directory for more details.
9-
*
10-
* The code of _clist_sort() has been imported from
11-
* https://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html.
12-
* Original copyright notice:
13-
*
14-
* This file is copyright 2001 Simon Tatham.
15-
*
16-
* Permission is hereby granted, free of charge, to any person
17-
* obtaining a copy of this software and associated documentation
18-
* files (the "Software"), to deal in the Software without
19-
* restriction, including without limitation the rights to use,
20-
* copy, modify, merge, publish, distribute, sublicense, and/or
21-
* sell copies of the Software, and to permit persons to whom the
22-
* Software is furnished to do so, subject to the following
23-
* conditions:
24-
*
25-
* The above copyright notice and this permission notice shall be
26-
* included in all copies or substantial portions of the Software.
27-
*
28-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29-
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
30-
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31-
* NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
32-
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
33-
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34-
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35-
* SOFTWARE.
367
*/
378

389
/**
3910
* @ingroup core_util
40-
* @{
4111
*
4212
* @file
4313
* @brief clist helper implementations
4414
*
45-
* @author Kaspar Schleiser <[email protected]>
15+
* @author Marian Buschsieweke <[email protected]>
4616
*
47-
* @}
4817
*/
4918

5019
#include "clist.h"
20+
#include "container.h"
21+
22+
#define ENABLE_DEBUG 0
23+
#include "debug.h"
24+
25+
#ifndef CONFIG_CLIST_SORT_BUF_SIZE
26+
/**
27+
* @brief size of the two static buffer used to speed up the sort
28+
*
29+
* Bumping this will increase the sorting speed of larger list at the cost of
30+
* stack size. The stack size required by the two buffers amounts to:
31+
* `2 * CONFIG_CLIST_SORT_BUF_SIZE * sizeof(void *)`
32+
*/
33+
# define CONFIG_CLIST_SORT_BUF_SIZE 8
34+
#endif
5135

52-
clist_node_t *_clist_sort(clist_node_t *list, clist_cmp_func_t cmp)
36+
#if (CONFIG_CLIST_SORT_BUF_SIZE == 0) || (CONFIG_CLIST_SORT_BUF_SIZE & (CONFIG_CLIST_SORT_BUF_SIZE - 1) != 0)
37+
# error "CONFIG_CLIST_SORT_BUF_SIZE needs to be a power of 2"
38+
#endif
39+
40+
struct lists_buf {
41+
list_node_t *lists[CONFIG_CLIST_SORT_BUF_SIZE];
42+
};
43+
44+
/**
45+
* @brief print the given lists if `ENABLE_DEBUG` is set to `1`
46+
* @param head List to print
47+
* @note this function will be fully optimized out with `ENABLE_DEBUG == 0`
48+
*/
49+
static void _debug_print_list(list_node_t *head)
5350
{
54-
clist_node_t *p, *q, *e;
55-
int insize, psize, qsize, i;
56-
57-
/*
58-
* Silly special case: if `list' was passed in as NULL, return
59-
* NULL immediately.
60-
*/
61-
if (!list) {
51+
if (ENABLE_DEBUG) {
52+
while (head) {
53+
DEBUG("-->[%p]", (void *)head);
54+
head = head->next;
55+
}
56+
}
57+
}
58+
59+
/**
60+
* @brief Merge two sorted lists into one sorted lists
61+
* @param lhs left list to sort
62+
* @param rhs right list to sort
63+
* @param cmp compare function to use to determine the list order
64+
* @return head of the newly merged list
65+
*
66+
* @pre @p lhs is not `NULL`
67+
* @pre @p rhs is not `NULL`
68+
*/
69+
static list_node_t *_merge(list_node_t *lhs, list_node_t *rhs, clist_cmp_func_t cmp)
70+
{
71+
DEBUG("\nmerging:\nlhs: ");
72+
_debug_print_list(lhs);
73+
DEBUG("\nrhs: ");
74+
_debug_print_list(rhs);
75+
76+
/* handle first element special */
77+
list_node_t *result;
78+
if (cmp(lhs, rhs) > 0) {
79+
result = rhs;
80+
rhs = rhs->next;
81+
}
82+
else {
83+
result = lhs;
84+
lhs = lhs->next;
85+
}
86+
87+
/* while both lists still contain elements, merge them */
88+
list_node_t *iter = result;
89+
while (lhs && rhs) {
90+
if (cmp(lhs, rhs) > 0) {
91+
iter->next = rhs;
92+
rhs = rhs->next;
93+
}
94+
else {
95+
iter->next = lhs;
96+
lhs = lhs->next;
97+
}
98+
iter = iter->next;
99+
}
100+
101+
/* fill the rest with the list that is not fully drained */
102+
if (lhs) {
103+
iter->next = lhs;
104+
}
105+
else {
106+
iter->next = rhs;
107+
}
108+
109+
DEBUG("\nmerged: ");
110+
_debug_print_list(result);
111+
DEBUG_PUTS("");
112+
113+
return result;
114+
}
115+
116+
/**
117+
* @brief Break circular list into a linear list
118+
*
119+
* @return Head of the list
120+
* @retval NULL List was not broken and already is sorted
121+
*/
122+
static list_node_t *linearlize_list(clist_node_t *list)
123+
{
124+
list_node_t *last, *first;
125+
126+
last = list->next;
127+
128+
/* Special case: Empty list */
129+
if (!last) {
62130
return NULL;
63131
}
64132

65-
insize = 1;
66-
67-
while (1) {
68-
clist_node_t *tail = NULL;
69-
clist_node_t *oldhead = list;
70-
p = list;
71-
list = NULL;
72-
73-
int nmerges = 0; /* count number of merges we do in this pass */
74-
75-
while (p) {
76-
nmerges++; /* there exists a merge to be done */
77-
/* step `insize' places along from p */
78-
q = p;
79-
psize = 0;
80-
for (i = 0; i < insize; i++) {
81-
psize++;
82-
q = (q->next == oldhead) ? NULL : q->next;
83-
/* cppcheck-suppress nullPointer
84-
* (reason: possible bug in cppcheck 1.6x) */
85-
if (!q) {
86-
break;
87-
}
88-
}
133+
first = last->next;
134+
135+
/* Special case: One item list */
136+
if (first == last) {
137+
return NULL;
138+
}
89139

90-
/* if q hasn't fallen off end, we have two lists to merge */
91-
qsize = insize;
92-
93-
/* now we have two lists; merge them */
94-
while (psize > 0 || (qsize > 0 && q)) {
95-
96-
/* decide whether next element of merge comes from p or q */
97-
if (psize == 0) {
98-
/* p is empty; e must come from q. */
99-
e = q; q = q->next; qsize--;
100-
if (q == oldhead) {
101-
q = NULL;
102-
}
103-
}
104-
else if (qsize == 0 || !q) {
105-
/* q is empty; e must come from p. */
106-
e = p; p = p->next; psize--;
107-
if (p == oldhead) {
108-
p = NULL;
109-
}
110-
}
111-
else if (cmp(p, q) <= 0) {
112-
/* First element of p is lower (or same);
113-
* e must come from p. */
114-
e = p; p = p->next; psize--;
115-
if (p == oldhead) {
116-
p = NULL;
117-
}
118-
}
119-
else {
120-
/* First element of q is lower; e must come from q. */
121-
e = q; q = q->next; qsize--;
122-
if (q == oldhead) {
123-
q = NULL;
124-
}
125-
}
126-
127-
/* add the next element to the merged list */
128-
if (tail) {
129-
tail->next = e;
130-
}
131-
else {
132-
list = e;
133-
}
134-
tail = e;
140+
last->next = NULL;
141+
return first;
142+
}
143+
144+
/**
145+
* @brief take a buf of sorted list and merge them down into a single
146+
* sorted list
147+
* @param lists bunch of lists to merge
148+
* @param cmp compare function to use to determine the list order
149+
* @return head of the merged lists
150+
*
151+
* @note @p lists may have `NULL` elements
152+
*/
153+
static list_node_t *_binary_merge_bufs(struct lists_buf *lists,
154+
clist_cmp_func_t cmp)
155+
{
156+
while (lists->lists[1]) {
157+
unsigned dst_idx = 0;
158+
unsigned src_idx = 0;
159+
while (src_idx < ARRAY_SIZE(lists->lists)) {
160+
if (!lists->lists[src_idx + 1]) {
161+
list_node_t *tmp = lists->lists[src_idx];
162+
lists->lists[src_idx] = NULL;
163+
lists->lists[dst_idx] = tmp;
164+
break;
135165
}
166+
list_node_t *merged = _merge(lists->lists[src_idx],
167+
lists->lists[src_idx + 1], cmp);
168+
lists->lists[src_idx++] = NULL;
169+
lists->lists[src_idx++] = NULL;
170+
lists->lists[dst_idx++] = merged;
171+
}
172+
}
173+
174+
return lists->lists[0];
175+
}
176+
177+
/**
178+
* @brief Consume up to `2 * CONFIG_CLIST_SORT_BUF_SIZE` elements from the
179+
* given list and sort them
180+
* @param iter_in_out pointer to the iterator
181+
* @param cmp compare function to use to determine the list order
182+
* @return Head of the sorted sub-list bitten of from the input
183+
*
184+
* @p iter_in_out points to the iterator used to track which part of the input
185+
* of @ref clist_sort has not been sorted yet. At return, it will be updated
186+
* to the first item after the chunk that has been bitten off from the input
187+
* and sorted. If the end of the input was reached, it is upstead to point to
188+
* `NULL` instead.
189+
*/
190+
static list_node_t *_sort_chunk(list_node_t **iter_in_out, clist_cmp_func_t cmp)
191+
{
192+
struct lists_buf lists = { .lists = { NULL } };
193+
list_node_t *iter = *iter_in_out;
136194

137-
/* now p has stepped `insize' places along, and q has too */
138-
p = q;
195+
for (unsigned i = 0; i < ARRAY_SIZE(lists.lists); i++) {
196+
if (!iter) {
197+
break;
139198
}
199+
list_node_t *lhs = iter;
200+
iter = iter->next;
201+
lhs->next = NULL;
202+
list_node_t *rhs = iter;
203+
if (!rhs) {
204+
lists.lists[i] = lhs;
205+
break;
206+
}
207+
iter = iter->next;
208+
rhs->next = NULL;
209+
lists.lists[i] = _merge(lhs, rhs, cmp);
210+
}
140211

141-
/* cppcheck-suppress nullPointer
142-
* (reason: tail cannot be NULL at this point, because list != NULL) */
143-
tail->next = list;
212+
*iter_in_out = iter;
144213

145-
/* If we have done only one merge, we're finished. */
146-
if (nmerges <= 1) { /* allow for nmerges==0, the empty list case */
147-
return tail;
214+
return _binary_merge_bufs(&lists, cmp);
215+
}
216+
217+
/**
218+
* @brief Get the last element of the given linear list
219+
*/
220+
list_node_t *_get_last(list_node_t *head) {
221+
while (head->next) {
222+
head = head->next;
223+
}
224+
return head;
225+
}
226+
227+
void clist_sort(clist_node_t *list, clist_cmp_func_t cmp)
228+
{
229+
list_node_t *head = linearlize_list(list);
230+
if (!head) {
231+
return;
232+
}
233+
234+
list_node_t *iter = head;
235+
unsigned idx = 0;
236+
struct lists_buf lists = { .lists = {NULL } };
237+
238+
while (iter) {
239+
list_node_t *tmp = _sort_chunk(&iter, cmp);
240+
if (lists.lists[idx]) {
241+
lists.lists[idx] = _merge(lists.lists[idx], tmp, cmp);
242+
}
243+
else {
244+
lists.lists[idx] = tmp;
148245
}
149246

150-
/* Otherwise repeat, merging lists twice the size */
151-
insize *= 2;
247+
idx++;
248+
idx &= (ARRAY_SIZE(lists.lists) - 1);
152249
}
250+
251+
list_node_t *result = _binary_merge_bufs(&lists, cmp);
252+
253+
/* turn the linear list back into a circular one */
254+
list->next = _get_last(result);
255+
list->next->next = result;
153256
}

0 commit comments

Comments
 (0)