Browse Source

Various stuff
First mutex wrapper
Valgrind support
BitArray bugfix
Refactoring
No memory test mode (for valgrind)

Kajetan Johannes Hammerle 2 months ago
parent
commit
05281845c0

+ 1 - 0
CMakeLists.txt

@@ -21,6 +21,7 @@ set(SRC
     "src/Frustum.cpp"
     "src/View.cpp"
     "src/Thread.cpp"
+    "src/Mutex.cpp"
     "src/FileReader.cpp"
     "src/ErrorSimulator.cpp"
 )

+ 27 - 0
include/core/thread/Mutex.hpp

@@ -0,0 +1,27 @@
+#ifndef CORE_MUTEX_HPP
+#define CORE_MUTEX_HPP
+
+#include "core/utils/AlignedData.hpp"
+#include "core/utils/Check.hpp"
+#include "core/utils/Error.hpp"
+
+namespace Core {
+    class Mutex final {
+        AlignedData<40, 8> mutex;
+
+    public:
+        Mutex();
+        Mutex(const Mutex& other) = delete;
+        Mutex(Mutex&& other) = delete;
+        ~Mutex();
+        Mutex& operator=(const Mutex& other) = delete;
+        Mutex& operator=(Mutex&& other) = delete;
+
+        check_return Error init();
+
+        check_return Error lock();
+        check_return Error unlock();
+    };
+}
+
+#endif

+ 2 - 2
include/core/utils/AlignedData.hpp

@@ -17,12 +17,12 @@ namespace Core {
 
     public:
         template<typename T>
-        T* as() {
+        constexpr T* as() {
             return reinterpret_cast<T*>(this);
         }
 
         template<typename T>
-        const T* as() const {
+        constexpr const T* as() const {
             return reinterpret_cast<T*>(this);
         }
 

+ 1 - 1
include/core/utils/Clock.hpp

@@ -21,8 +21,8 @@ namespace Core {
         // the first invocation will always return 0 nanos
         check_return Error update(Nanos& n);
         float getUpdatesPerSecond() const;
-        check_return Error wait(Nanos nanos) const;
 
+        check_return static Error wait(Nanos nanos);
         check_return static Error getNanos(Nanos& n);
     };
 }

+ 1 - 0
include/core/utils/Error.hpp

@@ -16,6 +16,7 @@ namespace Core {
         TIME_NOT_AVAILABLE,
         SLEEP_INTERRUPTED,
         THREAD_ERROR,
+        MUTEX_ERROR,
         EXISTING_KEY,
         CANNOT_OPEN_FILE,
         END_OF_FILE

+ 3 - 3
src/BitArray.cpp

@@ -15,7 +15,7 @@ static constexpr int DIVIDE_BITS = Core::Math::roundUpLog2(INT_BITS);
 
 static int readBits(const int* data, int index, int bits) {
     int dataIndexA = (index * bits) >> DIVIDE_BITS;
-    int dataIndexB = ((index + 1) * bits) >> DIVIDE_BITS;
+    int dataIndexB = ((index * bits) + (bits - 1)) >> DIVIDE_BITS;
     int shifts = (index * bits) & (INT_BITS - 1);
     if(dataIndexA == dataIndexB) {
         return (data[dataIndexA] >> shifts) & ((1 << bits) - 1);
@@ -30,7 +30,7 @@ static void setBits(int* data, int index, int bits, int value) {
     int mask = (1 << bits) - 1;
     value &= mask;
     int dataIndexA = (index * bits) >> DIVIDE_BITS;
-    int dataIndexB = ((index + 1) * bits) >> DIVIDE_BITS;
+    int dataIndexB = ((index * bits) + (bits - 1)) >> DIVIDE_BITS;
     int shifts = (index * bits) & (INT_BITS - 1);
     data[dataIndexA] &= ~(mask << shifts);
     data[dataIndexA] |= (value << shifts);
@@ -42,7 +42,7 @@ static void setBits(int* data, int index, int bits, int value) {
 }
 
 static int getArrayLength(int length, int bits) {
-    return roundUpDivide(length * bits, sizeof(int) * 8);
+    return roundUpDivide(length * bits, INT_BITS);
 }
 
 Core::BitArray::BitArray() : length(0), bits(0), data(nullptr) {

+ 1 - 1
src/Clock.cpp

@@ -39,7 +39,7 @@ Core::Error Core::Clock::getNanos(Clock::Nanos& n) {
     return Error::NONE;
 }
 
-Core::Error Core::Clock::wait(Nanos nanos) const {
+Core::Error Core::Clock::wait(Nanos nanos) {
     timespec t;
     t.tv_nsec = nanos % 1'000'000'000;
     t.tv_sec = nanos / 1'000'000'000;

+ 20 - 16
src/Error.cpp

@@ -1,23 +1,27 @@
 #include "core/utils/Error.hpp"
 
+#define CASE_RETURN_ENUM_NAME(type)                                            \
+    case Error::type: return #type
+
 const char* Core::getErrorName(Error e) {
     switch(e) {
-        case Error::NONE: return "NONE";
-        case Error::NEGATIVE_ARGUMENT: return "NEGATIVE_ARGUMENT";
-        case Error::CAPACITY_REACHED: return "CAPACITY_REACHED";
-        case Error::BLOCKED_STDOUT: return "BLOCKED_STDOUT";
-        case Error::OUT_OF_MEMORY: return "OUT_OF_MEMORY";
-        case Error::INVALID_CHAR: return "INVALID_CHAR";
-        case Error::NOT_FOUND: return "NOT_FOUND";
-        case Error::INVALID_STATE: return "INVALID_STATE";
-        case Error::INVALID_INDEX: return "INVALID_INDEX";
-        case Error::INVALID_ARGUMENT: return "INVALID_ARGUMENT";
-        case Error::TIME_NOT_AVAILABLE: return "TIME_NOT_AVAILABLE";
-        case Error::SLEEP_INTERRUPTED: return "SLEEP_INTERRUPTED";
-        case Error::THREAD_ERROR: return "THREAD_ERROR";
-        case Error::EXISTING_KEY: return "EXISTING_KEY";
-        case Error::CANNOT_OPEN_FILE: return "CANNOT_OPEN_FILE";
-        case Error::END_OF_FILE: return "END_OF_FILE";
+        CASE_RETURN_ENUM_NAME(NONE);
+        CASE_RETURN_ENUM_NAME(NEGATIVE_ARGUMENT);
+        CASE_RETURN_ENUM_NAME(CAPACITY_REACHED);
+        CASE_RETURN_ENUM_NAME(BLOCKED_STDOUT);
+        CASE_RETURN_ENUM_NAME(OUT_OF_MEMORY);
+        CASE_RETURN_ENUM_NAME(INVALID_CHAR);
+        CASE_RETURN_ENUM_NAME(NOT_FOUND);
+        CASE_RETURN_ENUM_NAME(INVALID_STATE);
+        CASE_RETURN_ENUM_NAME(INVALID_INDEX);
+        CASE_RETURN_ENUM_NAME(INVALID_ARGUMENT);
+        CASE_RETURN_ENUM_NAME(TIME_NOT_AVAILABLE);
+        CASE_RETURN_ENUM_NAME(SLEEP_INTERRUPTED);
+        CASE_RETURN_ENUM_NAME(THREAD_ERROR);
+        CASE_RETURN_ENUM_NAME(MUTEX_ERROR);
+        CASE_RETURN_ENUM_NAME(EXISTING_KEY);
+        CASE_RETURN_ENUM_NAME(CANNOT_OPEN_FILE);
+        CASE_RETURN_ENUM_NAME(END_OF_FILE);
     }
     return "?";
 }

+ 9 - 1
src/ErrorSimulator.hpp

@@ -1,14 +1,22 @@
 #ifndef CORE_ERROR_SIMULATOR_HPP
 #define CORE_ERROR_SIMULATOR_HPP
 
+#include "core/utils/Utility.hpp"
+
 #ifdef ERROR_SIMULATOR
 namespace Core::Fail {
     extern bool realloc;
     extern bool fileClose;
     extern bool timeGet;
     extern int leftAllocations;
+
+    inline bool freeAndReturn(void* p) {
+        Core::free(p);
+        return true;
+    }
 }
-#define CORE_REALLOC_FAIL Core::Fail::realloc
+#define CORE_REALLOC_FAIL(pointer)                                             \
+    (Core::Fail::realloc && Core::Fail::freeAndReturn(pointer))
 #define CORE_FILE_CLOSE_FAIL Core::Fail::fileClose
 #define CORE_TIME_GET_FAIL Core::Fail::timeGet
 #else

+ 45 - 0
src/Mutex.cpp

@@ -0,0 +1,45 @@
+#include "core/thread/Mutex.hpp"
+
+#include <threads.h>
+
+#include "core/utils/Meta.hpp"
+#include "core/utils/Utility.hpp"
+
+static void reset(mtx_t* m) {
+    Core::memorySet(m, 0, sizeof(mtx_t));
+}
+
+Core::Mutex::Mutex() : mutex() {
+    CORE_ASSERT_ALIGNED_DATA(mutex, mtx_t);
+    reset(mutex.as<mtx_t>());
+}
+
+static bool doesExist(mtx_t* t) {
+    mtx_t zero{};
+    return !Core::memoryCompare(&zero, t, sizeof(mtx_t));
+}
+
+Core::Mutex::~Mutex() {
+    if(doesExist(mutex.as<mtx_t>())) {
+        mtx_destroy(mutex.as<mtx_t>());
+    }
+}
+
+check_return Core::Error Core::Mutex::init() {
+    if(doesExist(mutex.as<mtx_t>())) {
+        return Error::INVALID_STATE;
+    }
+    return mtx_init(mutex.as<mtx_t>(), mtx_plain) != thrd_success
+               ? Error::MUTEX_ERROR
+               : Error::NONE;
+}
+
+check_return Core::Error Core::Mutex::lock() {
+    return mtx_lock(mutex.as<mtx_t>()) != thrd_success ? Error::MUTEX_ERROR
+                                                       : Error::NONE;
+}
+
+check_return Core::Error Core::Mutex::unlock() {
+    return mtx_unlock(mutex.as<mtx_t>()) != thrd_success ? Error::MUTEX_ERROR
+                                                         : Error::NONE;
+}

+ 6 - 5
src/Thread.cpp

@@ -5,7 +5,13 @@
 #include "core/utils/Meta.hpp"
 #include "core/utils/Utility.hpp"
 
+static void reset(thrd_t* t) {
+    Core::memorySet(t, 0, sizeof(thrd_t));
+}
+
 Core::Thread::Thread() : thread() {
+    CORE_ASSERT_ALIGNED_DATA(thread, thrd_t);
+    reset(thread.as<thrd_t>());
 }
 
 Core::Thread::Thread(Thread&& other) : thread() {
@@ -17,10 +23,6 @@ static bool doesExist(thrd_t* t) {
     return !Core::memoryCompare(&zero, t, sizeof(thrd_t));
 }
 
-static void reset(thrd_t* t) {
-    Core::memorySet(t, 0, sizeof(thrd_t));
-}
-
 Core::Thread::~Thread() {
     if(doesExist(thread.as<thrd_t>())) {
         (void)join(nullptr);
@@ -39,7 +41,6 @@ Core::Thread& Core::Thread::operator=(Thread&& other) {
 }
 
 check_return Core::Error Core::Thread::start(Function f, void* p) {
-    CORE_ASSERT_ALIGNED_DATA(thread, thrd_t);
     return thrd_create(thread.as<thrd_t>(), f, p) != thrd_success
                ? Error::THREAD_ERROR
                : Error::NONE;

+ 1 - 1
src/Utility.cpp

@@ -77,7 +77,7 @@ Core::Error Core::reallocate(char*& p, int n) {
         return Error::NONE;
     }
     char* newP = static_cast<char*>(realloc(p, static_cast<unsigned int>(n)));
-    if(newP == nullptr || CORE_REALLOC_FAIL) {
+    if(newP == nullptr || CORE_REALLOC_FAIL(newP)) {
         return Error::OUT_OF_MEMORY;
     }
     p = newP;

+ 16 - 2
tasks

@@ -8,11 +8,12 @@ printHelpExit() {
     echo "$0 build       | build everything"
     echo "$0 install     | move build results into the install folder"
     echo "$0 test        | run the tests"
+    echo "$0 valgrind    | run the tests with valgrind"
     echo "$0 coverage    | generate code coverage"
     echo "$0 performance | run the performance tests"
     echo "$0 final       | find classes / structs which are not final"
     echo "$0 macro       | find macros without CORE" 
-    echo "$0 include     | find system includes in header files" 
+    echo "$0 include     | find system includes" 
     echo "$0 time        | check build time"
     exit 0
 }
@@ -27,6 +28,7 @@ fi
 # tasks
 build=false
 test=false
+valgrind=false
 performance=false
 time=false
 install=false
@@ -49,6 +51,9 @@ elif [ "$task" = "coverage" ]; then
 elif [ "$task" = "test" ]; then
     build=true
     test=true
+elif [ "$task" = "valgrind" ]; then
+    build=true
+    valgrind=true
 elif [ "$task" = "performance" ]; then
     build=true
     performance=true
@@ -63,7 +68,11 @@ elif [ "$task" = "macro" ]; then
     grep -r "#define" src include | grep -v " CORE" || true
     exit 0
 elif [ "$task" = "include" ]; then
+    echo "System includes in header files:"
     grep -r "#include <" src include | grep "\.hpp" || true
+    echo "-------------------------------------------------" 
+    echo "System includes in source files:"
+    grep -r "#include <" src include | grep "\.cpp" || true
     exit 0
 else
     echo "unknown task"
@@ -83,7 +92,12 @@ if $install; then
 fi
 if $test; then
     cd build
-    ./test || true
+    ./test light || true
+    cd ..
+fi
+if $valgrind; then
+    cd build
+    valgrind ./test light valgrind || true
     cd ..
 fi
 if $performance; then

+ 17 - 7
test/Main.cpp

@@ -1,3 +1,5 @@
+#include <string.h>
+
 #include "../src/ErrorSimulator.hpp"
 #include "Test.hpp"
 #include "Tests.hpp"
@@ -13,30 +15,38 @@ static void onExit(int code, void* data) {
     Core::Test::finalize();
 }
 
-int main(int argAmount, const char**) {
-    bool light = argAmount <= 1;
+int main(int argAmount, const char** args) {
+    bool light = false;
+    bool outOfMemoryTest = true;
+    for(int i = 0; i < argAmount; i++) {
+        if(strcmp(args[i], "light") == 0) {
+            light = true;
+        } else if(strcmp(args[i], "valgrind") == 0) {
+            outOfMemoryTest = false;
+        }
+    }
     Core::testArrayList(light);
     Core::testArrayString();
     Core::testArray();
-    Core::testBitArray();
+    Core::testBitArray(outOfMemoryTest);
     Core::testBox();
     Core::testBuffer(light);
     Core::testBufferedValue();
     Core::testClock(light);
     Core::testColor();
-    Core::testComponents();
+    Core::testComponents(outOfMemoryTest);
     Core::testError();
     Core::testFileReader();
     Core::testFrustum();
-    Core::testHashMap(light);
-    Core::testLinkedList(light);
+    Core::testHashMap(light, outOfMemoryTest);
+    Core::testLinkedList(light, outOfMemoryTest);
     Core::testList(light);
     Core::testMath();
     Core::testMatrixStack(light);
     Core::testMatrix();
     Core::testNew();
     Core::testPlane();
-    Core::testProbingHashMap(light);
+    Core::testProbingHashMap(light, outOfMemoryTest);
     Core::testQuaternion();
     Core::testRandom(light);
     Core::testRingBuffer();

+ 5 - 5
test/Tests.hpp

@@ -7,25 +7,25 @@ namespace Core {
     void testArrayList(bool light);
     void testArrayString();
     void testArray();
-    void testBitArray();
+    void testBitArray(bool outOfMemoryTest);
     void testBox();
     void testBuffer(bool light);
     void testBufferedValue();
     void testClock(bool light);
     void testColor();
-    void testComponents();
+    void testComponents(bool outOfMemoryTest);
     void testError();
     void testFileReader();
     void testFrustum();
-    void testHashMap(bool light);
-    void testLinkedList(bool light);
+    void testHashMap(bool light, bool outOfMemoryTest);
+    void testLinkedList(bool light, bool outOfMemoryTest);
     void testList(bool light);
     void testMath();
     void testMatrixStack(bool light);
     void testMatrix();
     void testNew();
     void testPlane();
-    void testProbingHashMap(bool light);
+    void testProbingHashMap(bool light, bool outOfMemoryTest);
     void testQuaternion();
     void testRandom(bool light);
     void testRingBuffer();

+ 4 - 2
test/modules/BitArrayTests.cpp

@@ -149,7 +149,7 @@ static void testOutOfMemory() {
     Core::Fail::leftAllocations = -1;
 }
 
-void Core::testBitArray() {
+void Core::testBitArray(bool outOfMemoryTest) {
     testSetRead();
     testOutOfBoundsSetRead();
     testBigSetRead();
@@ -162,5 +162,7 @@ void Core::testBitArray() {
     testResizeExact();
     testMoveAssignment();
     testNegativeArgument();
-    testOutOfMemory();
+    if(outOfMemoryTest) {
+        testOutOfMemory();
+    }
 }

+ 2 - 2
test/modules/ClockTests.cpp

@@ -31,10 +31,10 @@ static void testWait(Core::Clock::Nanos wait) {
     Core::Clock c;
     Core::Clock::Nanos n = 0;
     CORE_TEST_ERROR(c.update(n));
-    CORE_TEST_ERROR(c.wait(wait));
+    CORE_TEST_ERROR(Core::Clock::wait(wait));
     Core::Clock::Nanos n2 = 0;
     CORE_TEST_ERROR(c.update(n2));
-    CORE_TEST_TRUE(n2 >= wait && n2 <= wait * 12 / 10);
+    CORE_TEST_TRUE(n2 >= wait && n2 <= wait * 13 / 10);
 }
 
 static void testFail() {

+ 4 - 2
test/modules/ComponentsTests.cpp

@@ -213,11 +213,13 @@ static void testConstSearch() {
     CORE_TEST_NULL(cc.search(2));
 }
 
-void Core::testComponents() {
+void Core::testComponents(bool outOfMemoryTest) {
     testAddForEach();
     testAddConstForEach();
     testAddComponentForEach();
     testRemove();
-    testOutOfMemory();
+    if(outOfMemoryTest) {
+        testOutOfMemory();
+    }
     testConstSearch();
 }

+ 1 - 0
test/modules/ErrorTests.cpp

@@ -19,6 +19,7 @@ void Core::testError() {
     test(Error::TIME_NOT_AVAILABLE, "TIME_NOT_AVAILABLE");
     test(Error::SLEEP_INTERRUPTED, "SLEEP_INTERRUPTED");
     test(Error::THREAD_ERROR, "THREAD_ERROR");
+    test(Error::MUTEX_ERROR, "MUTEX_ERROR");
     test(Error::EXISTING_KEY, "EXISTING_KEY");
     test(Error::CANNOT_OPEN_FILE, "CANNOT_OPEN_FILE");
     test(Error::END_OF_FILE, "END_OF_FILE");

+ 4 - 2
test/modules/HashMapTests.cpp

@@ -330,7 +330,7 @@ static void testOutOfMemory() {
     CORE_TEST_TRUE(memFails != 0);
 }
 
-void Core::testHashMap(bool light) {
+void Core::testHashMap(bool light, bool outOfMemoryTest) {
     testAdd();
     testMultipleAdd();
     testSearch();
@@ -349,5 +349,7 @@ void Core::testHashMap(bool light) {
     testKeyForEach();
     testValueForEach();
     testTypes();
-    testOutOfMemory();
+    if(outOfMemoryTest) {
+        testOutOfMemory();
+    }
 }

+ 4 - 2
test/modules/LinkedListTests.cpp

@@ -291,7 +291,7 @@ static void testOutOfMemory() {
     CORE_TEST_TRUE(memFails != 0);
 }
 
-void Core::testLinkedList(bool light) {
+void Core::testLinkedList(bool light, bool outOfMemoryTest) {
     testWithoutCopyOrMove();
     testAdd();
     testMultipleAdd();
@@ -306,5 +306,7 @@ void Core::testLinkedList(bool light) {
     testRemove();
     testRemoveFirst();
     testRemoveLast();
-    testOutOfMemory();
+    if(outOfMemoryTest) {
+        testOutOfMemory();
+    }
 }

+ 4 - 2
test/modules/ProbingHashMapTests.cpp

@@ -347,7 +347,7 @@ static void testAddCollisions() {
     }
 }
 
-void Core::testProbingHashMap(bool light) {
+void Core::testProbingHashMap(bool light, bool outOfMemoryTest) {
     testAdd();
     testMultipleAdd();
     testSearch();
@@ -365,7 +365,9 @@ void Core::testProbingHashMap(bool light) {
     testKeyForEach();
     testValueForEach();
     testTypes();
-    testOutOfMemory();
+    if(outOfMemoryTest) {
+        testOutOfMemory();
+    }
     testInsertInvalid();
     testAddCollisions();
 }

+ 31 - 0
test/modules/ThreadTests.cpp

@@ -1,4 +1,7 @@
+#include <threads.h>
+
 #include "../Tests.hpp"
+#include "core/thread/Mutex.hpp"
 #include "core/thread/Thread.hpp"
 
 static int runDone = 0;
@@ -75,6 +78,33 @@ static void testDoubleJoin() {
     CORE_TEST_EQUAL(Core::Error::THREAD_ERROR, t.join(nullptr));
 }
 
+struct MutexCounter {
+    Core::Mutex m{};
+    int counter = 0;
+};
+
+static int incrementMutexCounter(void* p) {
+    MutexCounter* mcp = static_cast<MutexCounter*>(p);
+    for(int i = 0; i < 10000; i++) {
+        (void)mcp->m.lock();
+        mcp->counter++;
+        (void)mcp->m.unlock();
+    }
+    return 0;
+}
+
+static void testMutex() {
+    MutexCounter mc;
+    CORE_TEST_ERROR(mc.m.init());
+    CORE_TEST_EQUAL(Core::Error::INVALID_STATE, mc.m.init());
+    Core::Thread t[2];
+    CORE_TEST_ERROR(t[0].start(incrementMutexCounter, &mc));
+    CORE_TEST_ERROR(t[1].start(incrementMutexCounter, &mc));
+    CORE_TEST_ERROR(t[0].join(nullptr));
+    CORE_TEST_ERROR(t[1].join(nullptr));
+    CORE_TEST_EQUAL(20000, mc.counter);
+}
+
 void Core::testThread() {
     testStart();
     testLambda();
@@ -84,4 +114,5 @@ void Core::testThread() {
     testMoveAssignment();
     testMoveIntoActive();
     testDoubleJoin();
+    testMutex();
 }