#include "radix-sort.h"
#include <linux/limits.h>
#include <linux/types.h>
#include "memory-alloc.h"
#include "string-utils.h"
#define INSERTION_SORT_THRESHOLD 12
typedef const u8 *sort_key_t;
struct histogram {
u16 used;
u16 first;
u16 last;
u32 size[256];
};
struct task {
sort_key_t *first_key;
sort_key_t *last_key;
u16 offset;
u16 length;
};
struct radix_sorter {
unsigned int count;
struct histogram bins;
sort_key_t *pile[256];
struct task *end_of_stack;
struct task insertion_list[256];
struct task stack[];
};
static inline int compare(sort_key_t key1, sort_key_t key2, u16 offset, u16 length)
{
return memcmp(&key1[offset], &key2[offset], length);
}
static inline void insert_key(const struct task task, sort_key_t *next)
{
sort_key_t unsorted = *next;
while ((--next >= task.first_key) &&
(compare(unsorted, next[0], task.offset, task.length) < 0))
next[1] = next[0];
next[1] = unsorted;
}
static inline void insertion_sort(const struct task task)
{
sort_key_t *next;
for (next = task.first_key + 1; next <= task.last_key; next++)
insert_key(task, next);
}
static inline void push_task(struct task **stack_pointer, sort_key_t *first_key,
u32 count, u16 offset, u16 length)
{
struct task *task = (*stack_pointer)++;
task->first_key = first_key;
task->last_key = &first_key[count - 1];
task->offset = offset;
task->length = length;
}
static inline void swap_keys(sort_key_t *a, sort_key_t *b)
{
sort_key_t c = *a;
*a = *b;
*b = c;
}
static inline void measure_bins(const struct task task, struct histogram *bins)
{
sort_key_t *key_ptr;
bins->first = U8_MAX;
bins->last = 0;
for (key_ptr = task.first_key; key_ptr <= task.last_key; key_ptr++) {
u8 bin = (*key_ptr)[task.offset];
u32 size = ++bins->size[bin];
if (size == 1) {
bins->used += 1;
if (bin < bins->first)
bins->first = bin;
if (bin > bins->last)
bins->last = bin;
}
}
}
static inline int push_bins(struct task **stack, struct task *end_of_stack,
struct task **list, sort_key_t *pile[],
struct histogram *bins, sort_key_t *first_key,
u16 offset, u16 length)
{
sort_key_t *pile_start = first_key;
int bin;
for (bin = bins->first; ; bin++) {
u32 size = bins->size[bin];
if (size == 0)
continue;
if (length > 0) {
if (size > INSERTION_SORT_THRESHOLD) {
if (*stack >= end_of_stack)
return UDS_BAD_STATE;
push_task(stack, pile_start, size, offset, length);
} else if (size > 1) {
push_task(list, pile_start, size, offset, length);
}
}
pile_start += size;
pile[bin] = pile_start;
if (--bins->used == 0)
break;
}
return UDS_SUCCESS;
}
int uds_make_radix_sorter(unsigned int count, struct radix_sorter **sorter)
{
int result;
unsigned int stack_size = count / INSERTION_SORT_THRESHOLD;
struct radix_sorter *radix_sorter;
result = vdo_allocate_extended(struct radix_sorter, stack_size, struct task,
__func__, &radix_sorter);
if (result != VDO_SUCCESS)
return result;
radix_sorter->count = count;
radix_sorter->end_of_stack = radix_sorter->stack + stack_size;
*sorter = radix_sorter;
return UDS_SUCCESS;
}
void uds_free_radix_sorter(struct radix_sorter *sorter)
{
vdo_free(sorter);
}
int uds_radix_sort(struct radix_sorter *sorter, const unsigned char *keys[],
unsigned int count, unsigned short length)
{
struct task start;
struct histogram *bins = &sorter->bins;
sort_key_t **pile = sorter->pile;
struct task *task_stack = sorter->stack;
if ((count == 0) || (length == 0))
return UDS_SUCCESS;
start = (struct task) {
.first_key = keys,
.last_key = &keys[count - 1],
.offset = 0,
.length = length,
};
if (count <= INSERTION_SORT_THRESHOLD) {
insertion_sort(start);
return UDS_SUCCESS;
}
if (count > sorter->count)
return UDS_INVALID_ARGUMENT;
for (*task_stack = start; task_stack >= sorter->stack; task_stack--) {
const struct task task = *task_stack;
struct task *insertion_task_list;
int result;
sort_key_t *fence;
sort_key_t *end;
measure_bins(task, bins);
insertion_task_list = sorter->insertion_list;
result = push_bins(&task_stack, sorter->end_of_stack,
&insertion_task_list, pile, bins, task.first_key,
task.offset + 1, task.length - 1);
if (result != UDS_SUCCESS) {
memset(bins, 0, sizeof(*bins));
return result;
}
end = task.last_key - bins->size[bins->last];
bins->size[bins->last] = 0;
for (fence = task.first_key; fence <= end; ) {
u8 bin;
sort_key_t key = *fence;
while (--pile[bin = key[task.offset]] > fence)
swap_keys(pile[bin], &key);
*fence = key;
fence += bins->size[bin];
bins->size[bin] = 0;
}
while (--insertion_task_list >= sorter->insertion_list)
insertion_sort(*insertion_task_list);
}
return UDS_SUCCESS;
}