summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/list_sort.c252
1 files changed, 183 insertions, 69 deletions
diff --git a/lib/list_sort.c b/lib/list_sort.c
index 19d11e0bb958..362c10f1653f 100644
--- a/lib/list_sort.c
+++ b/lib/list_sort.c
@@ -4,99 +4,213 @@
#include <linux/slab.h>
#include <linux/list.h>
+#define MAX_LIST_LENGTH_BITS 20
+
+/*
+ * Returns a list organized in an intermediate format suited
+ * to chaining of merge() calls: null-terminated, no reserved or
+ * sentinel head node, "prev" links not maintained.
+ */
+static struct list_head *merge(void *priv,
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b),
+ struct list_head *a, struct list_head *b)
+{
+ struct list_head head, *tail = &head;
+
+ while (a && b) {
+ /* if equal, take 'a' -- important for sort stability */
+ if ((*cmp)(priv, a, b) <= 0) {
+ tail->next = a;
+ a = a->next;
+ } else {
+ tail->next = b;
+ b = b->next;
+ }
+ tail = tail->next;
+ }
+ tail->next = a?:b;
+ return head.next;
+}
+
+/*
+ * Combine final list merge with restoration of standard doubly-linked
+ * list structure. This approach duplicates code from merge(), but
+ * runs faster than the tidier alternatives of either a separate final
+ * prev-link restoration pass, or maintaining the prev links
+ * throughout.
+ */
+static void merge_and_restore_back_links(void *priv,
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b),
+ struct list_head *head,
+ struct list_head *a, struct list_head *b)
+{
+ struct list_head *tail = head;
+
+ while (a && b) {
+ /* if equal, take 'a' -- important for sort stability */
+ if ((*cmp)(priv, a, b) <= 0) {
+ tail->next = a;
+ a->prev = tail;
+ a = a->next;
+ } else {
+ tail->next = b;
+ b->prev = tail;
+ b = b->next;
+ }
+ tail = tail->next;
+ }
+ tail->next = a ? : b;
+
+ do {
+ /*
+ * In worst cases this loop may run many iterations.
+ * Continue callbacks to the client even though no
+ * element comparison is needed, so the client's cmp()
+ * routine can invoke cond_resched() periodically.
+ */
+ (*cmp)(priv, tail, tail);
+
+ tail->next->prev = tail;
+ tail = tail->next;
+ } while (tail->next);
+
+ tail->next = head;
+ head->prev = tail;
+}
+
/**
* list_sort - sort a list.
* @priv: private data, passed to @cmp
* @head: the list to sort
* @cmp: the elements comparison function
*
- * This function has been implemented by Mark J Roberts <mjr@znex.org>. It
- * implements "merge sort" which has O(nlog(n)) complexity. The list is sorted
- * in ascending order.
+ * This function implements "merge sort" which has O(nlog(n)) complexity.
+ * The list is sorted in ascending order.
*
* The comparison function @cmp is supposed to return a negative value if @a is
* less than @b, and a positive value if @a is greater than @b. If @a and @b
* are equivalent, then it does not matter what this function returns.
*/
void list_sort(void *priv, struct list_head *head,
- int (*cmp)(void *priv, struct list_head *a,
- struct list_head *b))
+ int (*cmp)(void *priv, struct list_head *a,
+ struct list_head *b))
{
- struct list_head *p, *q, *e, *list, *tail, *oldhead;
- int insize, nmerges, psize, qsize, i;
+ struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
+ -- last slot is a sentinel */
+ int lev; /* index into part[] */
+ int max_lev = 0;
+ struct list_head *list;
if (list_empty(head))
return;
+ memset(part, 0, sizeof(part));
+
+ head->prev->next = NULL;
list = head->next;
- list_del(head);
- insize = 1;
- for (;;) {
- p = oldhead = list;
- list = tail = NULL;
- nmerges = 0;
-
- while (p) {
- nmerges++;
- q = p;
- psize = 0;
- for (i = 0; i < insize; i++) {
- psize++;
- q = q->next == oldhead ? NULL : q->next;
- if (!q)
- break;
- }
- qsize = insize;
- while (psize > 0 || (qsize > 0 && q)) {
- if (!psize) {
- e = q;
- q = q->next;
- qsize--;
- if (q == oldhead)
- q = NULL;
- } else if (!qsize || !q) {
- e = p;
- p = p->next;
- psize--;
- if (p == oldhead)
- p = NULL;
- } else if (cmp(priv, p, q) <= 0) {
- e = p;
- p = p->next;
- psize--;
- if (p == oldhead)
- p = NULL;
- } else {
- e = q;
- q = q->next;
- qsize--;
- if (q == oldhead)
- q = NULL;
- }
- if (tail)
- tail->next = e;
- else
- list = e;
- e->prev = tail;
- tail = e;
+ while (list) {
+ struct list_head *cur = list;
+ list = list->next;
+ cur->next = NULL;
+
+ for (lev = 0; part[lev]; lev++) {
+ cur = merge(priv, cmp, part[lev], cur);
+ part[lev] = NULL;
+ }
+ if (lev > max_lev) {
+ if (unlikely(lev >= ARRAY_SIZE(part)-1)) {
+ printk_once(KERN_DEBUG "list passed to"
+ " list_sort() too long for"
+ " efficiency\n");
+ lev--;
}
- p = q;
+ max_lev = lev;
}
+ part[lev] = cur;
+ }
- tail->next = list;
- list->prev = tail;
+ for (lev = 0; lev < max_lev; lev++)
+ if (part[lev])
+ list = merge(priv, cmp, part[lev], list);
- if (nmerges <= 1)
- break;
+ merge_and_restore_back_links(priv, cmp, head, part[max_lev], list);
+}
+EXPORT_SYMBOL(list_sort);
- insize *= 2;
- }
+#ifdef DEBUG_LIST_SORT
+struct debug_el {
+ struct list_head l_h;
+ int value;
+ unsigned serial;
+};
- head->next = list;
- head->prev = list->prev;
- list->prev->next = head;
- list->prev = head;
+static int cmp(void *priv, struct list_head *a, struct list_head *b)
+{
+ return container_of(a, struct debug_el, l_h)->value
+ - container_of(b, struct debug_el, l_h)->value;
}
-EXPORT_SYMBOL(list_sort);
+/*
+ * The pattern of set bits in the list length determines which cases
+ * are hit in list_sort().
+ */
+#define LIST_SORT_TEST_LENGTH (512+128+2) /* not including head */
+
+static int __init list_sort_test(void)
+{
+ int i, r = 1, count;
+ struct list_head *head = kmalloc(sizeof(*head), GFP_KERNEL);
+ struct list_head *cur;
+
+ printk(KERN_WARNING "testing list_sort()\n");
+
+ cur = head;
+ for (i = 0; i < LIST_SORT_TEST_LENGTH; i++) {
+ struct debug_el *el = kmalloc(sizeof(*el), GFP_KERNEL);
+ BUG_ON(!el);
+ /* force some equivalencies */
+ el->value = (r = (r * 725861) % 6599) % (LIST_SORT_TEST_LENGTH/3);
+ el->serial = i;
+
+ el->l_h.prev = cur;
+ cur->next = &el->l_h;
+ cur = cur->next;
+ }
+ head->prev = cur;
+
+ list_sort(NULL, head, cmp);
+
+ count = 1;
+ for (cur = head->next; cur->next != head; cur = cur->next) {
+ struct debug_el *el = container_of(cur, struct debug_el, l_h);
+ int cmp_result = cmp(NULL, cur, cur->next);
+ if (cur->next->prev != cur) {
+ printk(KERN_EMERG "list_sort() returned "
+ "a corrupted list!\n");
+ return 1;
+ } else if (cmp_result > 0) {
+ printk(KERN_EMERG "list_sort() failed to sort!\n");
+ return 1;
+ } else if (cmp_result == 0 &&
+ el->serial >= container_of(cur->next,
+ struct debug_el, l_h)->serial) {
+ printk(KERN_EMERG "list_sort() failed to preserve order"
+ " of equivalent elements!\n");
+ return 1;
+ }
+ kfree(cur->prev);
+ count++;
+ }
+ kfree(cur);
+ if (count != LIST_SORT_TEST_LENGTH) {
+ printk(KERN_EMERG "list_sort() returned list of"
+ "different length!\n");
+ return 1;
+ }
+ return 0;
+}
+module_init(list_sort_test);
+#endif