zephyr/lib/os/rb.c

600 lines
15 KiB
C

/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/* These assertions are very useful when debugging the tree code
* itself, but produce significant performance degradation as they are
* checked many times per operation. Leave them off unless you're
* working on the rbtree code itself
*/
#define CHECK(n) /**/
/* #define CHECK(n) __ASSERT_NO_MSG(n) */
#include <zephyr/kernel.h>
#include <zephyr/sys/rb.h>
#include <stdbool.h>
enum rb_color { RED = 0U, BLACK = 1U };
static struct rbnode *get_child(struct rbnode *n, uint8_t side)
{
CHECK(n);
if (side != 0U) {
return n->children[1];
}
uintptr_t l = (uintptr_t) n->children[0];
l &= ~1UL;
return (struct rbnode *) l;
}
static void set_child(struct rbnode *n, uint8_t side, void *val)
{
CHECK(n);
if (side != 0U) {
n->children[1] = val;
} else {
uintptr_t old = (uintptr_t) n->children[0];
uintptr_t new = (uintptr_t) val;
n->children[0] = (void *) (new | (old & 1UL));
}
}
static enum rb_color get_color(struct rbnode *n)
{
CHECK(n);
return ((uintptr_t)n->children[0]) & 1UL;
}
static bool is_black(struct rbnode *n)
{
return get_color(n) == BLACK;
}
static bool is_red(struct rbnode *n)
{
return get_color(n) == RED;
}
static void set_color(struct rbnode *n, enum rb_color color)
{
CHECK(n);
uintptr_t *p = (void *) &n->children[0];
*p = (*p & ~1UL) | (uint8_t)color;
}
/* Searches the tree down to a node that is either identical with the
* "node" argument or has an empty/leaf child pointer where "node"
* should be, leaving all nodes found in the resulting stack. Note
* that tree must not be empty and that stack should be allocated to
* contain at least tree->max_depth entries! Returns the number of
* entries pushed onto the stack.
*/
static int find_and_stack(struct rbtree *tree, struct rbnode *node,
struct rbnode **stack)
{
int sz = 0;
stack[sz++] = tree->root;
while (stack[sz - 1] != node) {
uint8_t side = tree->lessthan_fn(node, stack[sz - 1]) ? 0U : 1U;
struct rbnode *ch = get_child(stack[sz - 1], side);
if (ch != NULL) {
stack[sz++] = ch;
} else {
break;
}
}
return sz;
}
struct rbnode *z_rb_get_minmax(struct rbtree *tree, uint8_t side)
{
struct rbnode *n;
for (n = tree->root; (n != NULL) && (get_child(n, side) != NULL);
n = get_child(n, side)) {
;
}
return n;
}
static uint8_t get_side(struct rbnode *parent, struct rbnode *child)
{
CHECK(get_child(parent, 0U) == child || get_child(parent, 1U) == child);
return (get_child(parent, 1U) == child) ? 1U : 0U;
}
/* Swaps the position of the two nodes at the top of the provided
* stack, modifying the stack accordingly. Does not change the color
* of either node. That is, it effects the following transition (or
* its mirror if N is on the other side of P, of course):
*
* P N
* N c --> a P
* a b b c
*
*/
static void rotate(struct rbnode **stack, int stacksz)
{
CHECK(stacksz >= 2);
struct rbnode *parent = stack[stacksz - 2];
struct rbnode *child = stack[stacksz - 1];
uint8_t side = get_side(parent, child);
struct rbnode *a = get_child(child, side);
struct rbnode *b = get_child(child, (side == 0U) ? 1U : 0U);
if (stacksz >= 3) {
struct rbnode *grandparent = stack[stacksz - 3];
set_child(grandparent, get_side(grandparent, parent), child);
}
set_child(child, side, a);
set_child(child, (side == 0U) ? 1U : 0U, parent);
set_child(parent, side, b);
stack[stacksz - 2] = child;
stack[stacksz - 1] = parent;
}
/* The node at the top of the provided stack is red, and its parent is
* too. Iteratively fix the tree so it becomes a valid red black tree
* again
*/
static void fix_extra_red(struct rbnode **stack, int stacksz)
{
while (stacksz > 1) {
struct rbnode *node = stack[stacksz - 1];
struct rbnode *parent = stack[stacksz - 2];
/* Correct child colors are a precondition of the loop */
CHECK((get_child(node, 0U) == NULL) ||
is_black(get_child(node, 0U)));
CHECK((get_child(node, 1U) == NULL) ||
is_black(get_child(node, 1U)));
if (is_black(parent)) {
return;
}
/* We are guaranteed to have a grandparent if our
* parent is red, as red nodes cannot be the root
*/
CHECK(stacksz >= 2);
struct rbnode *grandparent = stack[stacksz - 3];
uint8_t side = get_side(grandparent, parent);
struct rbnode *aunt = get_child(grandparent,
(side == 0U) ? 1U : 0U);
if ((aunt != NULL) && is_red(aunt)) {
set_color(grandparent, RED);
set_color(parent, BLACK);
set_color(aunt, BLACK);
/* We colored the grandparent red, which might
* have a red parent, so continue iterating
* from there.
*/
stacksz -= 2;
continue;
}
/* We can rotate locally to fix the whole tree. First
* make sure that node is on the same side of parent
* as parent is of grandparent.
*/
uint8_t parent_side = get_side(parent, node);
if (parent_side != side) {
rotate(stack, stacksz);
}
/* Rotate the grandparent with parent, swapping colors */
rotate(stack, stacksz - 1);
set_color(stack[stacksz - 3], BLACK);
set_color(stack[stacksz - 2], RED);
return;
}
/* If we exit the loop, it's because our node is now the root,
* which must be black.
*/
set_color(stack[0], BLACK);
}
void rb_insert(struct rbtree *tree, struct rbnode *node)
{
set_child(node, 0U, NULL);
set_child(node, 1U, NULL);
if (tree->root == NULL) {
tree->root = node;
tree->max_depth = 1;
set_color(node, BLACK);
return;
}
#ifdef CONFIG_MISRA_SANE
struct rbnode **stack = &tree->iter_stack[0];
#else
struct rbnode *stack[tree->max_depth + 1];
#endif
int stacksz = find_and_stack(tree, node, stack);
struct rbnode *parent = stack[stacksz - 1];
uint8_t side = tree->lessthan_fn(node, parent) ? 0U : 1U;
set_child(parent, side, node);
set_color(node, RED);
stack[stacksz++] = node;
fix_extra_red(stack, stacksz);
if (stacksz > tree->max_depth) {
tree->max_depth = stacksz;
}
/* We may have rotated up into the root! */
tree->root = stack[0];
CHECK(is_black(tree->root));
}
/* Called for a node N (at the top of the stack) which after a
* deletion operation is "missing a black" in its subtree. By
* construction N must be black (because if it was red it would be
* trivially fixed by recoloring and we wouldn't be here). Fixes up
* the tree to preserve red/black rules. The "null_node" pointer is
* for situations where we are removing a childless black node. The
* tree munging needs a real node for simplicity, so we use it and
* then clean it up (replace it with a simple NULL child in the
* parent) when finished.
*/
static void fix_missing_black(struct rbnode **stack, int stacksz,
struct rbnode *null_node)
{
/* Loop upward until we reach the root */
while (stacksz > 1) {
struct rbnode *c0, *c1, *inner, *outer;
struct rbnode *n = stack[stacksz - 1];
struct rbnode *parent = stack[stacksz - 2];
uint8_t n_side = get_side(parent, n);
struct rbnode *sib = get_child(parent,
(n_side == 0U) ? 1U : 0U);
CHECK(is_black(n));
/* Guarantee the sibling is black, rotating N down a
* level if needed (after rotate() our parent is the
* child of our previous-sibling, so N is lower in the
* tree)
*/
if (!is_black(sib)) {
stack[stacksz - 1] = sib;
rotate(stack, stacksz);
set_color(parent, RED);
set_color(sib, BLACK);
stack[stacksz++] = n;
parent = stack[stacksz - 2];
sib = get_child(parent, (n_side == 0U) ? 1U : 0U);
}
CHECK(sib);
/* Cases where the sibling has only black children
* have simple resolutions
*/
c0 = get_child(sib, 0U);
c1 = get_child(sib, 1U);
if (((c0 == NULL) || is_black(c0)) && ((c1 == NULL) ||
is_black(c1))) {
if (n == null_node) {
set_child(parent, n_side, NULL);
}
set_color(sib, RED);
if (is_black(parent)) {
/* Balance the sibling's subtree by
* coloring it red, then our parent
* has a missing black so iterate
* upward
*/
stacksz--;
continue;
} else {
/* Recoloring makes the whole tree OK */
set_color(parent, BLACK);
return;
}
}
CHECK((c0 && is_red(c0)) || (c1 && is_red(c1)));
/* We know sibling has at least one red child. Fix it
* so that the far/outer position (i.e. on the
* opposite side from N) is definitely red.
*/
outer = get_child(sib, (n_side == 0U) ? 1U : 0U);
if (!((outer != NULL) && is_red(outer))) {
inner = get_child(sib, n_side);
stack[stacksz - 1] = sib;
stack[stacksz++] = inner;
rotate(stack, stacksz);
set_color(sib, RED);
set_color(inner, BLACK);
/* Restore stack state to have N on the top
* and make sib reflect the new sibling
*/
sib = stack[stacksz - 2];
outer = get_child(sib, (n_side == 0U) ? 1U : 0U);
stack[stacksz - 2] = n;
stacksz--;
}
/* Finally, the sibling must have a red child in the
* far/outer slot. We can rotate sib with our parent
* and recolor to produce a valid tree.
*/
CHECK(is_red(outer));
set_color(sib, get_color(parent));
set_color(parent, BLACK);
set_color(outer, BLACK);
stack[stacksz - 1] = sib;
rotate(stack, stacksz);
if (n == null_node) {
set_child(parent, n_side, NULL);
}
return;
}
}
void rb_remove(struct rbtree *tree, struct rbnode *node)
{
struct rbnode *tmp;
#ifdef CONFIG_MISRA_SANE
struct rbnode **stack = &tree->iter_stack[0];
#else
struct rbnode *stack[tree->max_depth + 1];
#endif
int stacksz = find_and_stack(tree, node, stack);
if (node != stack[stacksz - 1]) {
return;
}
/* We can only remove a node with zero or one child, if we
* have two then pick the "biggest" child of side 0 (smallest
* of 1 would work too) and swap our spot in the tree with
* that one
*/
if ((get_child(node, 0U) != NULL) && (get_child(node, 1U) != NULL)) {
int stacksz0 = stacksz;
struct rbnode *hiparent, *loparent;
struct rbnode *node2 = get_child(node, 0U);
hiparent = (stacksz > 1) ? stack[stacksz - 2] : NULL;
stack[stacksz++] = node2;
while (get_child(node2, 1U) != NULL) {
node2 = get_child(node2, 1U);
stack[stacksz++] = node2;
}
loparent = stack[stacksz - 2];
/* Now swap the position of node/node2 in the tree.
* Design note: this is a spot where being an
* intrusive data structure hurts us fairly badly.
* The trees you see in textbooks do this by swapping
* the "data" pointers between the two nodes, but we
* have a few special cases to check. In principle
* this works by swapping the child pointers between
* the nodes and retargeting the nodes pointing to
* them from their parents, but: (1) the upper node
* may be the root of the tree and not have a parent,
* and (2) the lower node may be a direct child of the
* upper node. Remember to swap the color bits of the
* two nodes also. And of course we don't have parent
* pointers, so the stack tracking this structure
* needs to be swapped too!
*/
if (hiparent != NULL) {
set_child(hiparent, get_side(hiparent, node), node2);
} else {
tree->root = node2;
}
if (loparent == node) {
set_child(node, 0U, get_child(node2, 0U));
set_child(node2, 0U, node);
} else {
set_child(loparent, get_side(loparent, node2), node);
tmp = get_child(node, 0U);
set_child(node, 0U, get_child(node2, 0U));
set_child(node2, 0U, tmp);
}
set_child(node2, 1U, get_child(node, 1U));
set_child(node, 1U, NULL);
tmp = stack[stacksz0 - 1];
stack[stacksz0 - 1] = stack[stacksz - 1];
stack[stacksz - 1] = tmp;
enum rb_color ctmp = get_color(node);
set_color(node, get_color(node2));
set_color(node2, ctmp);
}
CHECK((get_child(node, 0U) == NULL) ||
(get_child(node, 1U) == NULL));
struct rbnode *child = get_child(node, 0U);
if (child == NULL) {
child = get_child(node, 1U);
}
/* Removing the root */
if (stacksz < 2) {
tree->root = child;
if (child != NULL) {
set_color(child, BLACK);
} else {
tree->max_depth = 0;
}
return;
}
struct rbnode *parent = stack[stacksz - 2];
/* Special case: if the node to be removed is childless, then
* we leave it in place while we do the missing black
* rotations, which will replace it with a proper NULL when
* they isolate it.
*/
if (child == NULL) {
if (is_black(node)) {
fix_missing_black(stack, stacksz, node);
} else {
/* Red childless nodes can just be dropped */
set_child(parent, get_side(parent, node), NULL);
}
} else {
set_child(parent, get_side(parent, node), child);
/* Check colors, if one was red (at least one must have been
* black in a valid tree), then we're done.
*/
__ASSERT(is_black(node) || is_black(child), "both nodes red?!");
if (is_red(node) || is_red(child)) {
set_color(child, BLACK);
}
}
/* We may have rotated up into the root! */
tree->root = stack[0];
}
#ifndef CONFIG_MISRA_SANE
void z_rb_walk(struct rbnode *node, rb_visit_t visit_fn, void *cookie)
{
if (node != NULL) {
z_rb_walk(get_child(node, 0U), visit_fn, cookie);
visit_fn(node, cookie);
z_rb_walk(get_child(node, 1U), visit_fn, cookie);
}
}
#endif
struct rbnode *z_rb_child(struct rbnode *node, uint8_t side)
{
return get_child(node, side);
}
int z_rb_is_black(struct rbnode *node)
{
return is_black(node);
}
bool rb_contains(struct rbtree *tree, struct rbnode *node)
{
struct rbnode *n = tree->root;
while ((n != NULL) && (n != node)) {
n = get_child(n, tree->lessthan_fn(n, node));
}
return n == node;
}
/* Pushes the node and its chain of left-side children onto the stack
* in the foreach struct, returning the last node, which is the next
* node to iterate. By construction node will always be a right child
* or the root, so is_left must be false.
*/
static inline struct rbnode *stack_left_limb(struct rbnode *n,
struct _rb_foreach *f)
{
f->top++;
f->stack[f->top] = n;
f->is_left[f->top] = 0U;
while ((n = get_child(n, 0U)) != NULL) {
f->top++;
f->stack[f->top] = n;
f->is_left[f->top] = 1;
}
return f->stack[f->top];
}
/* The foreach tracking works via a dynamic stack allocated via
* alloca(). The current node is found in stack[top] (and its parent
* is thus stack[top-1]). The side of each stacked node from its
* parent is stored in is_left[] (i.e. if is_left[top] is true, then
* node/stack[top] is the left child of stack[top-1]). The special
* case of top == -1 indicates that the stack is uninitialized and we
* need to push an initial stack starting at the root.
*/
struct rbnode *z_rb_foreach_next(struct rbtree *tree, struct _rb_foreach *f)
{
struct rbnode *n;
if (tree->root == NULL) {
return NULL;
}
/* Initialization condition, pick the leftmost child of the
* root as our first node, initializing the stack on the way.
*/
if (f->top == -1) {
return stack_left_limb(tree->root, f);
}
/* The next child from a given node is the leftmost child of
* it's right subtree if it has a right child
*/
n = get_child(f->stack[f->top], 1U);
if (n != NULL) {
return stack_left_limb(n, f);
}
/* Otherwise if the node is a left child of its parent, the
* next node is the parent (note that the root is stacked
* above with is_left set to 0, so this condition still works
* even if node has no parent).
*/
if (f->is_left[f->top] != 0U) {
return f->stack[--f->top];
}
/* If we had no left tree and are a right child then our
* parent was already walked, so walk up the stack looking for
* a left child (whose parent is unwalked, and thus next).
*/
while ((f->top > 0) && (f->is_left[f->top] == 0U)) {
f->top--;
}
f->top--;
return (f->top >= 0) ? f->stack[f->top] : NULL;
}