Add rbtree validation

This commit is contained in:
Michael Theall 2018-09-18 23:43:59 -05:00
parent 79a77cd862
commit 431fd9aa9c
17 changed files with 364 additions and 0 deletions

2
.gitignore vendored
View File

@ -9,3 +9,5 @@ docs/
examples/
internal_docs
build.sh
libctru/source/util/rbtree/test/*.o
libctru/source/util/rbtree/test/rbtree_test

View File

@ -7,11 +7,13 @@ extern "C"
#include "mem_pool.h"
#include "addrmap.h"
#include "lock.h"
extern u32 __ctru_linear_heap;
extern u32 __ctru_linear_heap_size;
static MemPool sLinearPool;
static LightLock sLock = 1;
static bool linearInit()
{
@ -42,6 +44,7 @@ void* linearMemAlign(size_t size, size_t alignment)
return nullptr;
// Initialize the pool if it is not ready
LockGuard guard(sLock);
if (!sLinearPool.Ready() && !linearInit())
return nullptr;
@ -73,12 +76,14 @@ void* linearRealloc(void* mem, size_t size)
size_t linearGetSize(void* mem)
{
LockGuard guard(sLock);
auto node = getNode(mem);
return node ? node->chunk.size : 0;
}
void linearFree(void* mem)
{
LockGuard guard(sLock);
auto node = getNode(mem);
if (!node) return;
@ -91,5 +96,6 @@ void linearFree(void* mem)
u32 linearSpaceFree()
{
LockGuard guard(sLock);
return sLinearPool.GetFreeSpace();
}

View File

@ -0,0 +1,23 @@
#pragma once
extern "C"
{
#include <3ds/synchronization.h>
}
class LockGuard
{
public:
~LockGuard()
{
LightLock_Unlock(&lock);
}
LockGuard(LightLock &lock) : lock(lock)
{
LightLock_Lock(&lock);
}
private:
LightLock &lock;
};

View File

@ -7,8 +7,10 @@ extern "C"
#include "mem_pool.h"
#include "addrmap.h"
#include "lock.h"
static MemPool sMappablePool;
static LightLock sLock = 1;
static bool mappableInit()
{
@ -25,6 +27,7 @@ static bool mappableInit()
void* mappableAlloc(size_t size)
{
// Initialize the pool if it is not ready
LockGuard guard(sLock);
if (!sMappablePool.Ready() && !mappableInit())
return nullptr;
@ -45,12 +48,14 @@ void* mappableAlloc(size_t size)
size_t mappableGetSize(void* mem)
{
LockGuard guard(sLock);
auto node = getNode(mem);
return node ? node->chunk.size : 0;
}
void mappableFree(void* mem)
{
LockGuard guard(sLock);
auto node = getNode(mem);
if (!node) return;
@ -63,5 +68,6 @@ void mappableFree(void* mem)
u32 mappableSpaceFree()
{
LockGuard guard(sLock);
return sMappablePool.GetFreeSpace();
}

View File

@ -7,8 +7,10 @@ extern "C"
#include "mem_pool.h"
#include "addrmap.h"
#include "lock.h"
static MemPool sVramPool;
static LightLock sLock = 1;
static bool vramInit()
{
@ -39,6 +41,7 @@ void* vramMemAlign(size_t size, size_t alignment)
return nullptr;
// Initialize the pool if it is not ready
LockGuard guard(sLock);
if (!sVramPool.Ready() && !vramInit())
return nullptr;
@ -70,12 +73,14 @@ void* vramRealloc(void* mem, size_t size)
size_t vramGetSize(void* mem)
{
LockGuard guard(sLock);
auto node = getNode(mem);
return node ? node->chunk.size : 0;
}
void vramFree(void* mem)
{
LockGuard guard(sLock);
auto node = getNode(mem);
if (!node) return;
@ -88,5 +93,6 @@ void vramFree(void* mem)
u32 vramSpaceFree()
{
LockGuard guard(sLock);
return sVramPool.GetFreeSpace();
}

View File

@ -5,6 +5,8 @@ void
rbtree_clear(rbtree_t *tree,
rbtree_node_destructor_t destructor)
{
rbtree_validate(tree);
rbtree_node_t *node = tree->root;
while(tree->root != NULL)
@ -31,4 +33,6 @@ rbtree_clear(rbtree_t *tree,
}
tree->size = 0;
rbtree_validate(tree);
}

View File

@ -1,7 +1,10 @@
#include <3ds/util/rbtree.h>
#include "rbtree_internal.h"
int
rbtree_empty(const rbtree_t *tree)
{
rbtree_validate(tree);
return tree->root == NULL;
}

View File

@ -5,6 +5,8 @@ rbtree_node_t*
rbtree_find(const rbtree_t *tree,
const rbtree_node_t *node)
{
rbtree_validate(tree);
rbtree_node_t *tmp = tree->root;
rbtree_node_t *save = NULL;
@ -26,5 +28,7 @@ rbtree_find(const rbtree_t *tree,
}
}
rbtree_validate(tree);
return save;
}

View File

@ -1,4 +1,5 @@
#include <3ds/util/rbtree.h>
#include "rbtree_internal.h"
void
rbtree_init(rbtree_t *tree,
@ -7,4 +8,6 @@ rbtree_init(rbtree_t *tree,
tree->root = NULL;
tree->comparator = comparator;
tree->size = 0;
rbtree_validate(tree);
}

View File

@ -6,6 +6,8 @@ do_insert(rbtree_t *tree,
rbtree_node_t *node,
int multi)
{
rbtree_validate(tree);
rbtree_node_t *original = node;
rbtree_node_t **tmp = &tree->root;
rbtree_node_t *parent = NULL;
@ -31,6 +33,8 @@ do_insert(rbtree_t *tree,
if(save != NULL)
{
rbtree_validate(tree);
return save;
}
@ -78,6 +82,8 @@ do_insert(rbtree_t *tree,
tree->size += 1;
rbtree_validate(tree);
return original;
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "3ds/util/rbtree.h"
#define LEFT 0
#define RIGHT 1
@ -62,3 +63,9 @@ void
rbtree_rotate(rbtree_t *tree,
rbtree_node_t *node,
int left);
void
rbtree_validate(const rbtree_t *tree);
void
rbtree_node_validate(const rbtree_t *tree, const rbtree_node_t *node, size_t *all, size_t *black, size_t *depth);

View File

@ -5,6 +5,8 @@ static inline rbtree_node_t*
do_minmax(const rbtree_t *tree,
int max)
{
rbtree_validate(tree);
rbtree_node_t *node = tree->root;
if(node == NULL)

View File

@ -60,6 +60,8 @@ rbtree_remove(rbtree_t *tree,
rbtree_node_t *node,
rbtree_node_destructor_t destructor)
{
rbtree_validate(tree);
rbtree_color_t color;
rbtree_node_t *child, *parent, *original = node;
rbtree_node_t *next;
@ -136,5 +138,7 @@ rbtree_remove(rbtree_t *tree,
tree->size -= 1;
rbtree_validate(tree);
return next;
}

View File

@ -1,7 +1,10 @@
#include <3ds/util/rbtree.h>
#include "rbtree_internal.h"
size_t
rbtree_size(const rbtree_t *tree)
{
rbtree_validate(tree);
return tree->size;
}

View File

@ -0,0 +1,136 @@
#include "rbtree_internal.h"
#include <3ds/svc.h>
#include <stdlib.h>
#define panic() do { \
svcBreak(USERBREAK_PANIC); \
abort(); \
} while(0)
void
rbtree_validate(const rbtree_t *tree)
{
if(!tree)
panic();
// root node must be black
if(!is_black(tree->root))
panic();
// root node's parent must be null
if(tree->root)
{
if(get_parent(tree->root) != NULL)
panic();
}
// validate subtree starting at root node
size_t size = 0;
rbtree_node_validate(tree, tree->root, &size, NULL, NULL);
// make sure we are tracking the correct number of nodes
if(size != tree->size)
panic();
}
void
rbtree_node_validate(const rbtree_t *tree, const rbtree_node_t *node, size_t *size, size_t *black, size_t *depth)
{
if(!node) // implies is_black
{
if(black)
*black += 1;
if(depth)
*depth = 1;
return;
}
else // non-null
{
rbtree_node_t *parent = get_parent(node);
if(!parent)
{
// only the root node can have a null parent
if(node != tree->root)
panic();
}
else
{
// make sure this node is a child of its parent
if(parent->child[LEFT] != node && parent->child[RIGHT] != node)
panic();
// if the parent is red, this node must be black
if(is_red(parent) && !is_black(node))
panic();
}
}
if(is_red(node))
{
// if this node is red, both children must be black
if(!is_black(node->child[LEFT]))
panic();
if(!is_black(node->child[RIGHT]))
panic();
}
if(node->child[LEFT])
{
// this node must be >= left child
if((*(tree->comparator))(node, node->child[LEFT]) < 0)
panic();
}
if(node->child[RIGHT])
{
// this node must be <= right child
if((*(tree->comparator))(node, node->child[RIGHT]) > 0)
panic();
}
// validate subtree at left child
size_t left_size = 0;
size_t left_black = 0;
size_t left_depth = 0;
rbtree_node_validate(tree, node->child[LEFT], &left_size, &left_black, &left_depth);
// validate subtree at right child
size_t right_size = 0;
size_t right_black = 0;
size_t right_depth = 0;
rbtree_node_validate(tree, node->child[RIGHT], &right_size, &right_black, &right_depth);
// size is left+right subtrees plus self
if(size)
*size += left_size + right_size + 1;
// all possible paths to leaf nodes must have the same number of black nodes along the path
if(left_black != right_black)
panic();
if(black)
*black += left_black + (is_black(node) ? 1 : 0);
// depth of one subtree must not exceed 2x depth of the other subtree
if(left_depth < right_depth)
{
if(right_depth - left_depth > left_depth)
panic();
}
else if(left_depth - right_depth > right_depth)
panic();
if(depth)
{
if(left_depth > right_depth)
*depth = left_depth + 1;
else
*depth = right_depth + 1;
}
}

View File

@ -0,0 +1,23 @@
CFILES := $(wildcard ../*.c)
CXXFILES := $(wildcard *.cpp)
OFILES := $(patsubst ../%,%,$(CFILES:.c=.c.o))
OXXFILES := $(CXXFILES:.cpp=.cpp.o)
CPPFLAGS := -Wall -g -I../../../../include -O2
CFLAGS := $(CPPFLAGS)
CXXFLAGS := $(CPPFLAGS) -std=c++11
all: rbtree_test
rbtree_test: $(OFILES) $(OXXFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
$(OFILES): %.c.o: ../%.c
$(CC) -o $@ -c $< $(CFLAGS)
$(OXXFILES): %.cpp.o: %.cpp
$(CXX) -o $@ -c $< $(CXXFLAGS)
clean:
$(RM) rbtree_test $(OFILES) $(OXXFILES)

View File

@ -0,0 +1,126 @@
extern "C"
{
#include <3ds/svc.h>
#include <3ds/util/rbtree.h>
#include "../rbtree_internal.h"
}
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <limits>
#include <random>
#include <vector>
namespace
{
struct IntNode
{
IntNode(int value) : value(value)
{}
const int value;
rbtree_node_t node;
};
IntNode *getIntNode(rbtree_node_t *node)
{
return rbtree_item(node, IntNode, node);
}
const IntNode *getIntNode(const rbtree_node_t *node)
{
return rbtree_item(node, IntNode, node);
}
void IntNodeDestructor(rbtree_node_t *node)
{
delete getIntNode(node);
}
int IntNodeComparator(const rbtree_node_t *lhs, const rbtree_node_t *rhs)
{
auto left = getIntNode(lhs)->value;
auto right = getIntNode(rhs)->value;
if(left < right)
return -1;
if(right < left)
return 1;
return 0;
}
#if 0
void printNode(const rbtree_node_t *node, int indent)
{
if(!node)
{
std::printf("%*s(nil) black\n", indent, "");
return;
}
std::printf("%*s%d %s\n", indent, "", getIntNode(node)->value, is_black(node) ? "black" : "red");
printNode(node->child[LEFT], indent + 2);
printNode(node->child[RIGHT], indent + 2);
}
void printTree(const rbtree_t *tree)
{
std::printf("==========\n");
printNode(tree->root, 0);
}
#endif
}
void svcBreak(UserBreakType breakReason)
{
}
int main(int argc, char *argv[])
{
std::default_random_engine eng;
{
std::random_device rand;
std::random_device::result_type seedBuffer[32];
for(auto &d : seedBuffer)
d = rand();
std::seed_seq seed(std::begin(seedBuffer), std::end(seedBuffer));
eng.seed(seed);
}
auto dist = std::bind(std::uniform_int_distribution<int>(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()), std::ref(eng));
auto remove = std::bind(std::uniform_int_distribution<std::size_t>(0, 100), std::ref(eng));
rbtree_t tree;
rbtree_init(&tree, IntNodeComparator);
for(std::size_t chance = 0; chance <= 100; chance += 25)
{
std::printf("Chance %zu\n", chance);
std::vector<IntNode*> nodes;
for(std::size_t i = 0; i < 10000; ++i)
{
auto node = new IntNode(dist());
if(rbtree_insert(&tree, &node->node) != &node->node)
delete node;
else
nodes.emplace_back(node);
if(remove() < chance)
{
auto it = std::begin(nodes) + (eng() % nodes.size());
rbtree_remove(&tree, &(*it)->node, IntNodeDestructor);
nodes.erase(it);
}
// printTree(&tree);
}
}
rbtree_clear(&tree, IntNodeDestructor);
}