scoutfs: add avl

Add the little avl implementation that we're going to use for indexing
items within the btree blocks.

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2020-04-23 14:39:37 -07:00
committed by Zach Brown
parent ad99636af8
commit f59336085d
4 changed files with 445 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ CFLAGS_scoutfs_trace.o = -I$(src) # define_trace.h double include
-include $(src)/Makefile.kernelcompat
scoutfs-y += \
avl.o \
block.o \
btree.o \
client.o \

403
kmod/src/avl.c Normal file
View File

@@ -0,0 +1,403 @@
/*
* Copyright (C) 2020 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/kernel.h>
#include "format.h"
#include "avl.h"
/*
* We use a simple avl to index items in btree blocks. The interface
* looks a bit like the kernel rbtree interface in that the caller
* manages locking and storage for the nodes. Node references are
* stored as byte offsets from the root so that the implementation
* doesn't have to know anything about the caller's container.
*
* We store the full height in each node, rather than just 2 bits for
* the balance, so that we can use the extra redundancy to verify the
* integrity of the tree.
*/
static struct scoutfs_avl_node *node_ptr(struct scoutfs_avl_root *root,
__le16 off)
{
return off ? (void *)root + le16_to_cpu(off) : NULL;
}
static __le16 node_off(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
return node ? cpu_to_le16((void *)node - (void *)root) : 0;
}
static __u8 node_height(struct scoutfs_avl_node *node)
{
return node ? node->height : 0;
}
struct scoutfs_avl_node *
scoutfs_avl_search(struct scoutfs_avl_root *root,
scoutfs_avl_compare_t compare, void *arg, int *cmp_ret,
struct scoutfs_avl_node **par,
struct scoutfs_avl_node **next,
struct scoutfs_avl_node **prev)
{
struct scoutfs_avl_node *node = node_ptr(root, root->node);
int cmp;
if (cmp_ret)
*cmp_ret = -1;
if (par)
*par = NULL;
if (next)
*next = NULL;
if (prev)
*prev = NULL;
while (node) {
cmp = compare(arg, node);
if (par)
*par = node;
if (cmp_ret)
*cmp_ret = cmp;
if (cmp < 0) {
if (next)
*next = node;
node = node_ptr(root, node->left);
} else if (cmp > 0) {
if (prev)
*prev = node;
node = node_ptr(root, node->right);
} else {
return node;
}
}
return NULL;
}
struct scoutfs_avl_node *scoutfs_avl_first(struct scoutfs_avl_root *root)
{
struct scoutfs_avl_node *node = node_ptr(root, root->node);
while (node && node->left)
node = node_ptr(root, node->left);
return node;
}
struct scoutfs_avl_node *scoutfs_avl_last(struct scoutfs_avl_root *root)
{
struct scoutfs_avl_node *node = node_ptr(root, root->node);
while (node && node->right)
node = node_ptr(root, node->right);
return node;
}
struct scoutfs_avl_node *scoutfs_avl_next(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
struct scoutfs_avl_node *parent;
if (node->right) {
node = node_ptr(root, node->right);
while (node->left)
node = node_ptr(root, node->left);
return node;
}
while ((parent = node_ptr(root, node->parent)) &&
node == node_ptr(root, parent->right))
node = parent;
return parent;
}
struct scoutfs_avl_node *scoutfs_avl_prev(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
struct scoutfs_avl_node *parent;
if (node->left) {
node = node_ptr(root, node->left);
while (node->right)
node = node_ptr(root, node->right);
return node;
}
while ((parent = node_ptr(root, node->parent)) &&
node == node_ptr(root, parent->left))
node = parent;
return parent;
}
static void set_parent_left_right(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *parent,
struct scoutfs_avl_node *old,
struct scoutfs_avl_node *new)
{
__le16 *off;
if (parent == NULL)
off = &root->node;
else if (parent->left == node_off(root, old))
off = &parent->left;
else
off = &parent->right;
*off = node_off(root, new);
}
static void set_height(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
struct scoutfs_avl_node *left = node_ptr(root, node->left);
struct scoutfs_avl_node *right = node_ptr(root, node->right);
node->height = 1 + max(node_height(left), node_height(right));
}
static int node_balance(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
if (node == NULL)
return 0;
return (int)node_height(node_ptr(root, node->right)) -
(int)node_height(node_ptr(root, node->left));
}
/*
* d b
* / \ rotate right -> / \
* b e a d
* / \ <- rotate left / \
* a c c e
*
* The rotate functions are always called with the higher node as the
* earlier argument. Links to a and e are constant. We have to update
* the forward and back refs between parents and nodes for the three links
* along root->[db]->[bd]->c.
*/
static void rotate_right(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *d)
{
struct scoutfs_avl_node *gpa = node_ptr(root, d->parent);
struct scoutfs_avl_node *b = node_ptr(root, d->left);
struct scoutfs_avl_node *c = node_ptr(root, b->right);
set_parent_left_right(root, gpa, d, b);
b->parent = node_off(root, gpa);
b->right = node_off(root, d);
d->parent = node_off(root, b);
d->left = node_off(root, c);
if (c)
c->parent = node_off(root, d);
set_height(root, d);
set_height(root, b);
}
static void rotate_left(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *b)
{
struct scoutfs_avl_node *gpa = node_ptr(root, b->parent);
struct scoutfs_avl_node *d = node_ptr(root, b->right);
struct scoutfs_avl_node *c = node_ptr(root, d->left);
set_parent_left_right(root, gpa, b, d);
d->parent = node_off(root, gpa);
d->left = node_off(root, b);
b->parent = node_off(root, d);
b->right = node_off(root, c);
if (c)
c->parent = node_off(root, b);
set_height(root, b);
set_height(root, d);
}
/*
* Check the balance factor for the given node and perform rotations if
* its two child subtrees are too far out of balance. Return either the
* node again or the root of the newly balanced subtree.
*/
static struct scoutfs_avl_node *
rotate_imbalance(struct scoutfs_avl_root *root, struct scoutfs_avl_node *node)
{
int bal = node_balance(root, node);
struct scoutfs_avl_node *child;
if (bal >= -1 && bal <= 1)
return node;
if (bal > 0) {
/* turn right-left case into right-right */
child = node_ptr(root, node->right);
if (node_balance(root, child) < 0)
rotate_right(root, child);
/* rotate left to address right-right */
rotate_left(root, node);
} else {
/* or do the mirror for the left- cases */
child = node_ptr(root, node->left);
if (node_balance(root, child) > 0)
rotate_left(root, child);
rotate_right(root, node);
}
return node_ptr(root, node->parent);
}
void scoutfs_avl_insert(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *parent,
struct scoutfs_avl_node *node, int cmp)
{
node->parent = 0;
node->left = 0;
node->right = 0;
set_height(root, node);
if (parent == NULL) {
root->node = node_off(root, node);
node->parent = 0;
return;
}
if (cmp < 0)
parent->left = node_off(root, node);
else
parent->right = node_off(root, node);
node->parent = node_off(root, parent);
while (parent) {
set_height(root, parent);
parent = rotate_imbalance(root, parent);
parent = node_ptr(root, parent->parent);
}
}
static struct scoutfs_avl_node *avl_successor(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
node = node_ptr(root, node->right);
while (node->left)
node = node_ptr(root, node->left);
return node;
}
/*
* Find a node next successor and then swap the positions of the two
* nodes with each other in the tree. This is only tricky because the
* successor can be a direct child of the node and if we weren't careful
* we'd be modifying each of the nodes through the pointers between
* them.
*/
static void swap_with_successor(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
struct scoutfs_avl_node *succ = avl_successor(root, node);
struct scoutfs_avl_node *succ_par = node_ptr(root, succ->parent);
struct scoutfs_avl_node *succ_right = node_ptr(root, succ->right);
struct scoutfs_avl_node *parent;
struct scoutfs_avl_node *left;
struct scoutfs_avl_node *right;
/* Link old node's parent and left child with the successor */
succ->parent = node->parent;
parent = node_ptr(root, succ->parent);
set_parent_left_right(root, parent, node, succ);
succ->left = node->left;
left = node_ptr(root, succ->left);
if (left)
left->parent = node_off(root, succ);
/*
* Link the old node's right with successor and the old
* successor's parent with the node, they could have pointed to
* each other.
*/
if (succ_par == node) {
succ->right = node_off(root, node);
node->parent = node_off(root, succ);
} else {
succ->right = node->right;
right = node_ptr(root, succ->right);
if (right)
right->parent = node_off(root, succ);
set_parent_left_right(root, succ_par, succ, node);
node->parent = node_off(root, succ_par);
}
/* Link the old successor's right with the node, it can't have left */
node->right = node_off(root, succ_right);
if (succ_right)
succ_right->parent = node_off(root, node);
node->left = 0;
swap(node->height, succ->height);
}
void scoutfs_avl_delete(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node)
{
struct scoutfs_avl_node *parent;
struct scoutfs_avl_node *child;
if (node->left && node->right)
swap_with_successor(root, node);
parent = node_ptr(root, node->parent);
child = node_ptr(root, node->left ?: node->right);
set_parent_left_right(root, parent, node, child);
if (child)
child->parent = node->parent;
while (parent) {
set_height(root, parent);
parent = rotate_imbalance(root, parent);
parent = node_ptr(root, parent->parent);
}
}
/*
* Move the contents of a node to a new node location in memory. The
* logical position of the node in the tree does not change.
*/
void scoutfs_avl_relocate(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *to,
struct scoutfs_avl_node *from)
{
struct scoutfs_avl_node *parent = node_ptr(root, from->parent);
struct scoutfs_avl_node *left = node_ptr(root, from->left);
struct scoutfs_avl_node *right = node_ptr(root, from->right);
set_parent_left_right(root, parent, from, to);
to->parent = from->parent;
to->left = from->left;
if (left)
left->parent = node_off(root, to);
to->right = from->right;
if (right)
right->parent = node_off(root, to);
to->height = from->height;
}

30
kmod/src/avl.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _SCOUTFS_AVL_H_
#define _SCOUTFS_AVL_H_
#include "format.h"
typedef int (*scoutfs_avl_compare_t)(void *arg,
struct scoutfs_avl_node *node);
struct scoutfs_avl_node *
scoutfs_avl_search(struct scoutfs_avl_root *root,
scoutfs_avl_compare_t compare, void *arg, int *cmp_ret,
struct scoutfs_avl_node **par,
struct scoutfs_avl_node **next,
struct scoutfs_avl_node **prev);
struct scoutfs_avl_node *scoutfs_avl_first(struct scoutfs_avl_root *root);
struct scoutfs_avl_node *scoutfs_avl_last(struct scoutfs_avl_root *root);
struct scoutfs_avl_node *scoutfs_avl_next(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node);
struct scoutfs_avl_node *scoutfs_avl_prev(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node);
void scoutfs_avl_insert(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *parent,
struct scoutfs_avl_node *node, int cmp);
void scoutfs_avl_delete(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *node);
void scoutfs_avl_relocate(struct scoutfs_avl_root *root,
struct scoutfs_avl_node *to,
struct scoutfs_avl_node *from);
#endif

View File

@@ -184,6 +184,17 @@ struct scoutfs_radix_root {
~(__u64)SCOUTFS_RADIX_LG_MASK)
#define SCOUTFS_RADIX_BITS_BYTES (SCOUTFS_RADIX_BITS / 8)
struct scoutfs_avl_root {
__le16 node;
} __packed;
struct scoutfs_avl_node {
__le16 parent;
__le16 left;
__le16 right;
__u8 height;
} __packed;
/* when we split we want to have multiple items on each side */
#define SCOUTFS_BTREE_MAX_VAL_LEN (SCOUTFS_BLOCK_SIZE / 8)