diff --git a/.gitignore b/.gitignore index 9c3f938..f24776a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ docs/ examples/ internal_docs build.sh +libctru/source/util/rbtree/test/*.o +libctru/source/util/rbtree/test/rbtree_test diff --git a/libctru/source/allocator/linear.cpp b/libctru/source/allocator/linear.cpp index 8d8182a..8818817 100644 --- a/libctru/source/allocator/linear.cpp +++ b/libctru/source/allocator/linear.cpp @@ -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(); } diff --git a/libctru/source/allocator/lock.h b/libctru/source/allocator/lock.h new file mode 100644 index 0000000..66fefd7 --- /dev/null +++ b/libctru/source/allocator/lock.h @@ -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; +}; diff --git a/libctru/source/allocator/mappable.cpp b/libctru/source/allocator/mappable.cpp index c75d7cd..1c2371c 100644 --- a/libctru/source/allocator/mappable.cpp +++ b/libctru/source/allocator/mappable.cpp @@ -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(); } diff --git a/libctru/source/allocator/vram.cpp b/libctru/source/allocator/vram.cpp index 30498a5..8f23417 100644 --- a/libctru/source/allocator/vram.cpp +++ b/libctru/source/allocator/vram.cpp @@ -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(); } diff --git a/libctru/source/util/rbtree/rbtree_clear.c b/libctru/source/util/rbtree/rbtree_clear.c index fd945f1..fe5c803 100644 --- a/libctru/source/util/rbtree/rbtree_clear.c +++ b/libctru/source/util/rbtree/rbtree_clear.c @@ -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); } diff --git a/libctru/source/util/rbtree/rbtree_empty.c b/libctru/source/util/rbtree/rbtree_empty.c index 5ccdfff..c47170d 100644 --- a/libctru/source/util/rbtree/rbtree_empty.c +++ b/libctru/source/util/rbtree/rbtree_empty.c @@ -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; } diff --git a/libctru/source/util/rbtree/rbtree_find.c b/libctru/source/util/rbtree/rbtree_find.c index 3b5ff99..19c9f27 100644 --- a/libctru/source/util/rbtree/rbtree_find.c +++ b/libctru/source/util/rbtree/rbtree_find.c @@ -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; } diff --git a/libctru/source/util/rbtree/rbtree_init.c b/libctru/source/util/rbtree/rbtree_init.c index a8c30d5..b1a826c 100644 --- a/libctru/source/util/rbtree/rbtree_init.c +++ b/libctru/source/util/rbtree/rbtree_init.c @@ -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); } diff --git a/libctru/source/util/rbtree/rbtree_insert.c b/libctru/source/util/rbtree/rbtree_insert.c index 29950ec..536b9d2 100644 --- a/libctru/source/util/rbtree/rbtree_insert.c +++ b/libctru/source/util/rbtree/rbtree_insert.c @@ -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; } diff --git a/libctru/source/util/rbtree/rbtree_internal.h b/libctru/source/util/rbtree/rbtree_internal.h index 6d0d0a9..8719afe 100644 --- a/libctru/source/util/rbtree/rbtree_internal.h +++ b/libctru/source/util/rbtree/rbtree_internal.h @@ -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); diff --git a/libctru/source/util/rbtree/rbtree_minmax.c b/libctru/source/util/rbtree/rbtree_minmax.c index f74293c..54c9cf4 100644 --- a/libctru/source/util/rbtree/rbtree_minmax.c +++ b/libctru/source/util/rbtree/rbtree_minmax.c @@ -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) diff --git a/libctru/source/util/rbtree/rbtree_remove.c b/libctru/source/util/rbtree/rbtree_remove.c index 8a0359a..412474c 100644 --- a/libctru/source/util/rbtree/rbtree_remove.c +++ b/libctru/source/util/rbtree/rbtree_remove.c @@ -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; } diff --git a/libctru/source/util/rbtree/rbtree_size.c b/libctru/source/util/rbtree/rbtree_size.c index 401d43d..e596366 100644 --- a/libctru/source/util/rbtree/rbtree_size.c +++ b/libctru/source/util/rbtree/rbtree_size.c @@ -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; } diff --git a/libctru/source/util/rbtree/rbtree_validate.c b/libctru/source/util/rbtree/rbtree_validate.c new file mode 100644 index 0000000..431635e --- /dev/null +++ b/libctru/source/util/rbtree/rbtree_validate.c @@ -0,0 +1,136 @@ +#include "rbtree_internal.h" + +#include <3ds/svc.h> + +#include + +#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; + } +} diff --git a/libctru/source/util/rbtree/test/Makefile b/libctru/source/util/rbtree/test/Makefile new file mode 100644 index 0000000..87835e0 --- /dev/null +++ b/libctru/source/util/rbtree/test/Makefile @@ -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) diff --git a/libctru/source/util/rbtree/test/main.cpp b/libctru/source/util/rbtree/test/main.cpp new file mode 100644 index 0000000..3799e1b --- /dev/null +++ b/libctru/source/util/rbtree/test/main.cpp @@ -0,0 +1,126 @@ +extern "C" +{ +#include <3ds/svc.h> +#include <3ds/util/rbtree.h> +#include "../rbtree_internal.h" +} + +#include +#include +#include +#include +#include +#include +#include + +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(std::numeric_limits::min(), std::numeric_limits::max()), std::ref(eng)); + auto remove = std::bind(std::uniform_int_distribution(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 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); +}