Add rbtree validation
This commit is contained in:
parent
79a77cd862
commit
431fd9aa9c
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,3 +9,5 @@ docs/
|
||||
examples/
|
||||
internal_docs
|
||||
build.sh
|
||||
libctru/source/util/rbtree/test/*.o
|
||||
libctru/source/util/rbtree/test/rbtree_test
|
||||
|
@ -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();
|
||||
}
|
||||
|
23
libctru/source/allocator/lock.h
Normal file
23
libctru/source/allocator/lock.h
Normal 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;
|
||||
};
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
136
libctru/source/util/rbtree/rbtree_validate.c
Normal file
136
libctru/source/util/rbtree/rbtree_validate.c
Normal 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;
|
||||
}
|
||||
}
|
23
libctru/source/util/rbtree/test/Makefile
Normal file
23
libctru/source/util/rbtree/test/Makefile
Normal 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)
|
126
libctru/source/util/rbtree/test/main.cpp
Normal file
126
libctru/source/util/rbtree/test/main.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user