fixing bugs and adding some test scripts
Some checks failed
CodeQL / Analyze (${{ matrix.language }}) (autobuild, c-cpp) (push) Has been cancelled
Microsoft C++ Code Analysis / Analyze (push) Has been cancelled

This commit is contained in:
Dennis Eichhorn 2025-04-27 20:10:58 +00:00
parent 2883ca0841
commit eb9a135ca7
36 changed files with 577 additions and 160 deletions

View File

@ -17,8 +17,8 @@
// Only allowed for data >= 64 bits
bool is_empty(const byte* region, uint64 size, int32 steps = 8)
{
// Quick check of first 8 bytes
if (*((uint64 *) region) != 0) {
// Quick check of first byte
if (*region != 0) {
return false;
}

58
check_cpu_features.sh Normal file
View File

@ -0,0 +1,58 @@
#!/bin/bash
check_cpu_features() {
local FLAGS=""
local MACROS=""
# Check for SSE4.2
if lscpu | grep -qi sse4_2; then
FLAGS+=" -msse4.2"
MACROS+=" -D__SSE4_2__"
fi
# Check for AVX
if lscpu | grep -qi avx; then
FLAGS+=" -mavx"
MACROS+=" -D__AVX__"
fi
# Check for AVX2
if lscpu | grep -qi avx2; then
FLAGS+=" -mavx2"
MACROS+=" -D__AVX2__"
fi
# Check for AVX512
if lscpu | grep -qi avx512; then
FLAGS+=" -mavx512f -mavx512cd -mavx512vl -mavx512bw -mavx512dq -mavx512ifma -mavx512vbmi"
MACROS+=" -D__AVX512F__"
fi
# Check for FMA
if lscpu | grep -qi fma; then
FLAGS+=" -mfma"
MACROS+=" -D__FMA__"
fi
# Check for POPCNT
if lscpu | grep -qi popcnt; then
FLAGS+=" -mpopcnt"
MACROS+=" -D__POPCNT__"
fi
# Check for endianness
if [ "$(lscpu | grep -i 'byte order' | awk '{print $3}')" = "Little" ]; then
MACROS+=" -D__LITTLE_ENDIAN__"
elif [ "$(lscpu | grep -i 'byte order' | awk '{print $3}')" = "Big" ]; then
MACROS+=" -D__BIG_ENDIAN__"
fi
# Return the results (two different methods shown)
# Method 1: Set global variables
CPU_FLAGS="$FLAGS"
CPU_MACROS="$MACROS"
# Method 2: Output to stdout (alternative approach)
echo "$FLAGS $MACROS"
}

View File

@ -34,7 +34,7 @@ struct DatabasePool {
void db_pool_alloc(DatabasePool* pool, uint8 count) {
ASSERT_SIMPLE(count);
PROFILE(PROFILE_DB_POOL_ALLOC, NULL, false, true);
LOG_1("Allocating DatabasePool");
LOG_1("Allocating DatabasePool for %d connections", {{LOG_DATA_BYTE, &count}});
uint64 size = count * sizeof(DatabaseConnection)
+ sizeof(uint64) * CEIL_DIV(count, 64) // free
@ -43,8 +43,6 @@ void db_pool_alloc(DatabasePool* pool, uint8 count) {
pool->connections = (DatabaseConnection *) platform_alloc_aligned(size, 64);
pool->free = (uint64 *) ROUND_TO_NEAREST((uintptr_t) (pool->connections + count * sizeof(DatabaseConnection)), 64);
pool->count = count;
LOG_1("Allocated DatabasePool: %n B", {{LOG_DATA_UINT64, &pool->count}});
}
void db_pool_add(DatabasePool* __restrict pool, DatabaseConnection* __restrict db) noexcept {
@ -53,6 +51,8 @@ void db_pool_add(DatabasePool* __restrict pool, DatabaseConnection* __restrict d
}
void db_pool_free(DatabasePool* pool) {
LOG_1("Freeing DatabasePool");
for (int32 i = 0; i < pool->count; ++i) {
db_close(&pool->connections[i]);
}
@ -60,8 +60,6 @@ void db_pool_free(DatabasePool* pool) {
platform_aligned_free((void **) &pool->connections);
pool->free = NULL;
pool->count = 0;
LOG_1("Freed DatabasePool");
}
// Returns free database connection or null if none could be found

View File

@ -67,7 +67,9 @@ enum LogDataType {
LOG_DATA_NONE,
LOG_DATA_VOID,
LOG_DATA_BYTE,
LOG_DATA_INT16,
LOG_DATA_INT32,
LOG_DATA_UINT16,
LOG_DATA_UINT32,
LOG_DATA_INT64,
LOG_DATA_UINT64,
@ -100,7 +102,7 @@ struct LogDataArray{
LogData data[LOG_DATA_ARRAY];
};
// @bug This probably requires thread safety
// @bug This needs to be thread safe
byte* log_get_memory() noexcept
{
if (_log_memory->pos + MAX_LOG_LENGTH > _log_memory->size) {
@ -116,6 +118,7 @@ byte* log_get_memory() noexcept
}
// @performance This should only be called async to avoid blocking (e.g. render loop)
// @bug This needs to be thread safe
void log_to_file()
{
// we don't log an empty log pool
@ -157,6 +160,7 @@ void log_flush()
_log_memory->pos = 0;
}
// @bug This needs to be thread safe
void log(const char* str, const char* file, const char* function, int32 line)
{
if (!_log_memory) {
@ -199,6 +203,7 @@ void log(const char* str, const char* file, const char* function, int32 line)
}
}
// @bug This needs to be thread safe
void log(const char* format, LogDataArray data, const char* file, const char* function, int32 line)
{
if (!_log_memory) {
@ -236,6 +241,12 @@ void log(const char* format, LogDataArray data, const char* file, const char* fu
case LOG_DATA_BYTE: {
sprintf_fast_iter(msg->message, temp_format, (int32) *((byte *) data.data[i].value));
} break;
case LOG_DATA_INT16: {
sprintf_fast_iter(msg->message, temp_format, (int32) *((int16 *) data.data[i].value));
} break;
case LOG_DATA_UINT16: {
sprintf_fast_iter(msg->message, temp_format, (uint32) *((uint16 *) data.data[i].value));
} break;
case LOG_DATA_INT32: {
sprintf_fast_iter(msg->message, temp_format, *((int32 *) data.data[i].value));
} break;

View File

@ -13,7 +13,6 @@
#include "../utils/TimeUtils.h"
#include "../thread/Spinlock.cpp"
#include "../thread/Atomic.h"
#include "../system/Allocator.h"
#include "../hash/GeneralHash.h"
#include "../architecture/Intrinsics.h"
#include "../compiler/CompilerUtils.h"
@ -24,6 +23,7 @@
enum TimingStats {
PROFILE_TEMP,
PROFILE_MEMORY_ALLOC,
PROFILE_FILE_UTILS,
PROFILE_BUFFER_ALLOC,
PROFILE_CHUNK_ALLOC,

View File

@ -16,6 +16,8 @@
DEBUG_COUNTER_DRIVE_READ,
DEBUG_COUNTER_DRIVE_WRITE,
DEBUG_COUNTER_THREAD,
DEBUG_COUNTER_GPU_VERTEX_UPLOAD,
DEBUG_COUNTER_GPU_UNIFORM_UPLOAD,
DEBUG_COUNTER_GPU_DRAW_CALLS,
@ -50,6 +52,16 @@ void log_increment(int32 id, int64 by = 1) noexcept
atomic_add_acquire(&_stats_counter[id], by);
}
inline
void log_decrement(int32 id, int64 by = 1) noexcept
{
if (!_stats_counter) {
return;
}
atomic_sub_acquire(&_stats_counter[id], by);
}
inline
void log_counter(int32 id, int64 value) noexcept
{
@ -63,11 +75,13 @@ void log_counter(int32 id, int64 value) noexcept
#if (!DEBUG && !INTERNAL) || RELEASE
#define LOG_INCREMENT(a) ((void) 0)
#define LOG_INCREMENT_BY(a, b) ((void) 0)
#define LOG_DECREMENT(a) ((void) 0)
#define LOG_COUNTER(a, b) ((void) 0)
#define RESET_COUNTER(a) ((void) 0)
#else
#define LOG_INCREMENT(a) log_increment((a), 1)
#define LOG_INCREMENT_BY(a, b) log_increment((a), (b))
#define LOG_DECREMENT(a) log_decrement((a), 1)
#define LOG_COUNTER(a, b) log_counter((a), (b))
#define RESET_COUNTER(a) reset_counter((a))
#endif

View File

@ -111,8 +111,8 @@ f32 evaluator_evaluate_function(const char* name, const char* args);
// Shunting-yard algorithm to evaluate the expression
f32 evaluator_evaluate_expression(const char* expr) {
EvaluatorOperatorStack operators = { .top = -1 };
EvaluatorValueStack values = { .top = -1 };
EvaluatorOperatorStack operators = { .items = {}, .top = -1 };
EvaluatorValueStack values = { .items = {}, .top = -1 };
const char* ptr = expr;
while (*ptr) {

View File

@ -66,8 +66,6 @@ void chunk_alloc(ChunkMemory* buf, uint32 count, uint32 chunk_size, int32 alignm
buf->free = (uint64 *) ROUND_TO_NEAREST((uintptr_t) (buf->memory + count * chunk_size), alignment);
memset(buf->memory, 0, buf->size);
LOG_1("Allocated ChunkMemory: %n B", {{LOG_DATA_UINT64, &buf->size}});
}
inline
@ -110,8 +108,7 @@ void chunk_init(ChunkMemory* buf, byte* data, uint32 count, uint32 chunk_size, i
+ sizeof(uint64) * CEIL_DIV(count, alignment) // free
+ alignment * 2; // overhead for alignment
// @bug what if an alignment is defined?
buf->memory = data;
buf->memory = (byte *) ROUND_TO_NEAREST((uintptr_t) data, alignment);
buf->count = count;
buf->size = size;
@ -307,7 +304,7 @@ int32 chunk_reserve(ChunkMemory* buf, uint32 elements = 1) noexcept
}
if (free_element < 0) {
ASSERT_SIMPLE(false);
LOG_3("No free chunk memory index found");
return -1;
}

View File

@ -60,8 +60,6 @@ void ring_alloc(RingMemory* ring, uint64 size, uint32 alignment = 64)
ring->alignment = alignment;
memset(ring->memory, 0, ring->size);
LOG_1("Allocated RingMemory: %n B", {{LOG_DATA_UINT64, &ring->size}});
}
inline

View File

@ -17,6 +17,7 @@
#include "../../utils/TestUtils.h"
#include "../../log/DebugMemory.h"
#include "../../log/Stats.h"
#include "../../log/Log.h"
// @todo Currently alignment only effects the starting position, but it should also effect the ending/size
@ -24,11 +25,16 @@
// does this have a negative impact on caching?
// Our Memory doesn't start at the cache line beginning but at least offset by sizeof(size_t)
static int32 _page_size = 0;
inline
void* platform_alloc(size_t size)
{
ssize_t page_size = sysconf(_SC_PAGESIZE);
size = (size + sizeof(size_t) + page_size - 1) & ~(page_size - 1);
if (!_page_size) {
_page_size = (int32) sysconf(_SC_PAGESIZE);
}
size = ROUND_TO_NEAREST(size + sizeof(size_t), _page_size);
void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_SIMPLE(ptr != MAP_FAILED);
@ -37,6 +43,7 @@ void* platform_alloc(size_t size)
DEBUG_MEMORY_INIT((uintptr_t) ptr, size);
LOG_INCREMENT_BY(DEBUG_COUNTER_MEM_ALLOC, size);
LOG_3("Allocated %n B", {{LOG_DATA_UINT64, &size}});
return (void *) ((uintptr_t) ptr + sizeof(size_t));
}
@ -44,13 +51,12 @@ void* platform_alloc(size_t size)
inline
void* platform_alloc_aligned(size_t size, int32 alignment)
{
ssize_t page_size = sysconf(_SC_PAGESIZE);
if (alignment < page_size) {
alignment = page_size;
if (!_page_size) {
_page_size = (int32) sysconf(_SC_PAGESIZE);
}
size = ROUND_TO_NEAREST(size, alignment);
size += alignment + sizeof(void *) + sizeof(size_t);
size = ROUND_TO_NEAREST(size + sizeof(void *) + sizeof(size_t) + alignment - 1, alignment);
size = ROUND_TO_NEAREST(size, _page_size);
void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_SIMPLE(ptr != MAP_FAILED);
@ -60,13 +66,14 @@ void* platform_alloc_aligned(size_t size, int32 alignment)
// However, when freeing the pointer later on we need the actual start of the memory area, not the manually offset one.
// We do the same with the size, which is required when freeing
uintptr_t raw_address = (uintptr_t) ptr + sizeof(void *) + sizeof(size_t);
void* aligned_ptr = (void *) ((raw_address + alignment - 1) & ~(alignment - 1));
void* aligned_ptr = (void *) ROUND_TO_NEAREST(raw_address, alignment);
*((void **) ((uintptr_t) aligned_ptr - sizeof(void *) - sizeof(size_t))) = ptr;
*((size_t *) ((uintptr_t) aligned_ptr - sizeof(size_t))) = size;
DEBUG_MEMORY_INIT((uintptr_t) aligned_ptr, size);
LOG_INCREMENT_BY(DEBUG_COUNTER_MEM_ALLOC, size);
LOG_3("Aligned allocated %n B", {{LOG_DATA_UINT64, &size}});
return aligned_ptr;
}
@ -92,11 +99,14 @@ void platform_aligned_free(void** aligned_ptr) {
inline
void* platform_shared_alloc(int32* fd, const char* name, size_t size)
{
if (!_page_size) {
_page_size = (int32) sysconf(_SC_PAGESIZE);
}
*fd = shm_open(name, O_CREAT | O_RDWR, 0666);
ASSERT_SIMPLE(*fd != -1);
ssize_t page_size = sysconf(_SC_PAGESIZE);
size = (size + sizeof(size_t) + page_size - 1) & ~(page_size - 1);
size = ROUND_TO_NEAREST(size + sizeof(size_t), _page_size);
ftruncate(*fd, size);
@ -107,6 +117,7 @@ void* platform_shared_alloc(int32* fd, const char* name, size_t size)
DEBUG_MEMORY_INIT((uintptr_t) shm_ptr, size);
LOG_INCREMENT_BY(DEBUG_COUNTER_MEM_ALLOC, size);
LOG_3("Shared allocated %n B", {{LOG_DATA_UINT64, &size}});
return (void *) ((uintptr_t) shm_ptr + sizeof(size_t));
}
@ -117,11 +128,11 @@ void* platform_shared_open(int32* fd, const char* name, size_t size)
*fd = shm_open(name, O_RDWR, 0666);
ASSERT_SIMPLE(*fd != -1);
ssize_t page_size = sysconf(_SC_PAGESIZE);
size = (size + sizeof(size_t) + page_size - 1) & ~(page_size - 1);
size = ROUND_TO_NEAREST(size + sizeof(size_t), _page_size);
void* shm_ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, *fd, 0);
ASSERT_SIMPLE(shm_ptr);
LOG_3("Shared opened %n B", {{LOG_DATA_UINT64, &size}});
*((size_t *) shm_ptr) = size;

View File

@ -39,9 +39,17 @@ int32 coms_pthread_create(coms_pthread_t* thread, void*, ThreadJobFunc start_rou
return 1;
}
const uint64 stack_size = 1 * MEGABYTE;
thread->stack = platform_alloc_aligned(stack_size, 64);
int32 flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM;
*thread = clone((int32 (*)(void*))start_routine, NULL, flags, arg);
if (*thread == -1) {
// The + stack_size is required since the stack is used "downwards"
thread->h = clone((int32 (*)(void*))start_routine, (void *) ((uintptr_t) thread->stack + stack_size), flags, arg);
if (thread->h == -1) {
LOG_1("Thread creation faild with error %d", {{LOG_DATA_INT32, &errno}});
return 1;
}
@ -50,9 +58,13 @@ int32 coms_pthread_create(coms_pthread_t* thread, void*, ThreadJobFunc start_rou
FORCE_INLINE
int32 coms_pthread_join(coms_pthread_t thread, void** retval) {
return syscall(SYS_waitid, P_PID, thread, retval, WEXITED, NULL) == -1
int32 res = syscall(SYS_waitid, P_PID, thread, retval, WEXITED, NULL) == -1
? 1
: 0;
platform_aligned_free((void **) &thread.stack);
return res;
}
FORCE_INLINE
@ -126,7 +138,7 @@ int32 mutex_condimedwait(mutex_cond* cond, mutex* mutex, const struct timespec*)
return 0;
}
inline
FORCE_INLINE
int32 coms_pthread_cond_wait(mutex_cond* cond, mutex* mutex) {
return mutex_condimedwait(cond, mutex, NULL);
}

View File

@ -34,6 +34,9 @@ struct coms_pthread_rwlock_t {
bool exclusive;
};
typedef int coms_pthread_t;
struct coms_pthread_t {
int h;
void* stack;
};
#endif

View File

@ -15,15 +15,26 @@
#include "../../utils/TestUtils.h"
#include "../../log/DebugMemory.h"
#include "../../log/Stats.h"
#include "../../log/Log.h"
// @todo Currently alignment only effects the starting position, but it should also effect the ending/size
static int32 _page_size = 0;
inline
void* platform_alloc(size_t size)
{
if (!_page_size) {
// @todo Fix and get system page size
_page_size = 1;
}
size = ROUND_TO_NEAREST(size, _page_size);
void* ptr = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
DEBUG_MEMORY_INIT((uintptr_t) ptr, size);
LOG_INCREMENT_BY(DEBUG_COUNTER_MEM_ALLOC, size);
LOG_3("Allocated %n B", {{LOG_DATA_UINT64, &size}});
return ptr;
}
@ -31,7 +42,12 @@ void* platform_alloc(size_t size)
inline
void* platform_alloc_aligned(size_t size, int32 alignment)
{
size = ROUND_TO_NEAREST(size, alignment);
if (!_page_size) {
_page_size = 1;
}
size = ROUND_TO_NEAREST(size + sizeof(void*) + alignment - 1, alignment);
size = ROUND_TO_NEAREST(size, _page_size);
void* ptr = VirtualAlloc(NULL, size + alignment + sizeof(void*), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
ASSERT_SIMPLE(ptr);
@ -39,11 +55,12 @@ void* platform_alloc_aligned(size_t size, int32 alignment)
// We want an aligned memory area but mmap doesn't really support that.
// That's why we have to manually offset our memory area.
// However, when freeing the pointer later on we need the actual start of the memory area, not the manually offset one.
void* aligned_ptr = (void *) (((uintptr_t) ptr + alignment + sizeof(void*) - 1) & ~(alignment - 1));
void* aligned_ptr = (void *) ROUND_TO_NEAREST((uintptr_t) ptr + sizeof(void*), alignment);
((void**) aligned_ptr)[-1] = ptr;
DEBUG_MEMORY_INIT((uintptr_t) aligned_ptr, size);
LOG_INCREMENT_BY(DEBUG_COUNTER_MEM_ALLOC, size);
LOG_3("Aligned allocated %n B", {{LOG_DATA_UINT64, &size}});
return aligned_ptr;
}
@ -67,6 +84,12 @@ void platform_aligned_free(void** aligned_ptr) {
inline
void* platform_shared_alloc(HANDLE* fd, const char* name, size_t size)
{
if (!_page_size) {
_page_size = 1;
}
size = ROUND_TO_NEAREST(size, _page_size);
*fd = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD) size, name);
ASSERT_SIMPLE(*fd);
@ -75,6 +98,7 @@ void* platform_shared_alloc(HANDLE* fd, const char* name, size_t size)
DEBUG_MEMORY_INIT((uintptr_t) shm_ptr, size);
LOG_INCREMENT_BY(DEBUG_COUNTER_MEM_ALLOC, size);
LOG_3("Shared allocated %n B", {{LOG_DATA_UINT64, &size}});
return shm_ptr;
}
@ -87,6 +111,7 @@ void* platform_shared_open(HANDLE* fd, const char* name, size_t size)
void* shm_ptr = MapViewOfFile(*fd, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, (DWORD) size);
ASSERT_SIMPLE(shm_ptr);
LOG_3("Shared opened %n B", {{LOG_DATA_UINT64, &size}});
return shm_ptr;
}

View File

@ -18,7 +18,12 @@ typedef CRITICAL_SECTION mutex;
typedef void mutexattr_t;
typedef void coms_pthread_condattr_t;
typedef void coms_pthread_rwlockattr_t;
typedef HANDLE coms_pthread_t;
struct coms_pthread_t {
HANDLE h;
void* stack;
};
typedef CONDITION_VARIABLE mutex_cond;
// Thread local variable Already exists in c++11

View File

@ -153,7 +153,7 @@ void hashmap_alloc(HashMap* hm, int32 count, int32 element_size, int32 alignment
);
hm->table = (uint16 *) data;
chunk_init(&hm->buf, data + sizeof(uint16) * count, count, element_size, 8);
chunk_init(&hm->buf, data + sizeof(uint16) * count, count, element_size, alignment);
}
inline
@ -161,21 +161,22 @@ void hashmap_alloc(HashMapRef* hmr, int32 count, int32 data_element_size, int32
{
int32 element_size = sizeof(HashEntryInt32Int32);
LOG_1("Allocate HashMap for %n elements with %n B per element", {{LOG_DATA_INT32, &count}, {LOG_DATA_INT32, &element_size}});
byte* data = (byte *) platform_alloc(
byte* data = (byte *) platform_alloc_aligned(
count * (sizeof(uint16) + element_size)
+ CEIL_DIV(count, alignment) * sizeof(hmr->hm.buf.free)
+ count * data_element_size
+ count * data_element_size,
alignment
);
hmr->hm.table = (uint16 *) data;
chunk_init(&hmr->hm.buf, data + sizeof(uint16) * count, count, element_size, 8);
chunk_init(&hmr->data, data + hmr->hm.buf.size, count, data_element_size);
chunk_init(&hmr->hm.buf, data + sizeof(uint16) * count, count, element_size, alignment);
chunk_init(&hmr->data, data + hmr->hm.buf.size, count, data_element_size, alignment);
}
inline
void hashmap_free(HashMap* hm)
{
platform_free((void **) &hm->table);
platform_aligned_free((void **) &hm->table);
hm->table = NULL;
hm->buf.size = 0;
@ -194,7 +195,7 @@ void hashmap_create(HashMap* hm, int32 count, int32 element_size, RingMemory* ri
);
hm->table = (uint16 *) data;
chunk_init(&hm->buf, data + sizeof(uint16) * count, count, element_size, 8);
chunk_init(&hm->buf, data + sizeof(uint16) * count, count, element_size, alignment);
}
// WARNING: element_size = element size + remaining HashEntry data size
@ -209,16 +210,16 @@ void hashmap_create(HashMap* hm, int32 count, int32 element_size, BufferMemory*
);
hm->table = (uint16 *) data;
chunk_init(&hm->buf, data + sizeof(uint16) * count, count, element_size, 8);
chunk_init(&hm->buf, data + sizeof(uint16) * count, count, element_size, alignment);
}
// WARNING: element_size = element size + remaining HashEntry data size
inline
void hashmap_create(HashMap* hm, int32 count, int32 element_size, byte* buf) noexcept
void hashmap_create(HashMap* hm, int32 count, int32 element_size, byte* buf, int32 alignment = 64) noexcept
{
LOG_1("Create HashMap for %n elements with %n B per element", {{LOG_DATA_INT32, &count}, {LOG_DATA_INT32, &element_size}});
hm->table = (uint16 *) buf;
chunk_init(&hm->buf, buf + sizeof(uint16) * count, count, element_size, 8);
chunk_init(&hm->buf, buf + sizeof(uint16) * count, count, element_size, alignment);
}
inline
@ -256,6 +257,8 @@ void hashmap_insert(HashMap* hm, const char* key, int32 value) noexcept {
int32 element = chunk_reserve(&hm->buf, 1);
HashEntryInt32* entry = (HashEntryInt32 *) chunk_get_element(&hm->buf, element, true);
ASSERT_SIMPLE(((uintptr_t) entry) % 32 == 0);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
@ -908,7 +911,11 @@ int64 hashmap_dump(const HashMap* hm, byte* data, [[maybe_unused]] int32 steps =
} else if (value_size == 8) {
*((int64 *) data) = SWAP_ENDIAN_LITTLE(((HashEntryInt64 *) entry)->value);
} else {
memcpy(data, entry->value, value_size);
if (entry->value) {
memcpy(data, entry->value, value_size);
} else {
memset(data, 0, value_size);
}
}
data += value_size;
} else {

View File

@ -171,22 +171,23 @@ PerfectHashMap* perfect_hashmap_prepare(PerfectHashMap* hm, const char* keys, in
return NULL;
}
void perfect_hashmap_alloc(PerfectHashMap* hm, int32 count, int32 element_size)
void perfect_hashmap_alloc(PerfectHashMap* hm, int32 count, int32 element_size, int32 alignment = 64)
{
LOG_1("Allocating PerfectHashMap for %n elements with %n B per element", {{LOG_DATA_INT32, &count}, {LOG_DATA_INT32, &element_size}});
hm->map_count = count;
hm->entry_size = element_size;
hm->hash_entries = (byte *) platform_alloc(count * element_size);
hm->hash_entries = (byte *) platform_alloc_aligned(count * element_size, alignment);
}
void perfect_hashmap_alloc(PerfectHashMapRef* hmr, int32 count, int32 total_data_size)
void perfect_hashmap_alloc(PerfectHashMapRef* hmr, int32 count, int32 total_data_size, int32 alignment = 64)
{
hmr->hm.entry_size = sizeof(PerfectHashEntryInt32Int32);
LOG_1("Allocating PerfectHashMap for %n elements with %n B per element", {{LOG_DATA_INT32, &count}, {LOG_DATA_INT32, &hmr->hm.entry_size}});
hmr->hm.map_count = count;
hmr->hm.hash_entries = (byte *) platform_alloc(
hmr->hm.hash_entries = (byte *) platform_alloc_aligned(
count * hmr->hm.entry_size
+ total_data_size
+ total_data_size,
alignment
);
hmr->data_pos = 0;
@ -194,6 +195,14 @@ void perfect_hashmap_alloc(PerfectHashMapRef* hmr, int32 count, int32 total_data
hmr->data = hmr->hm.hash_entries + count * hmr->hm.entry_size;
}
void perfect_hashmap_free(PerfectHashMap* hm) {
platform_aligned_free((void **) &hm->hash_entries);
}
void perfect_hashmap_free(PerfectHashMapRef* hmr) {
platform_aligned_free((void **) &hmr->hm.hash_entries);
}
// WARNING: element_size = element size + remaining HashEntry data size
void perfect_hashmap_create(PerfectHashMap* hm, int32 count, int32 element_size, BufferMemory* buf)
{
@ -203,7 +212,7 @@ void perfect_hashmap_create(PerfectHashMap* hm, int32 count, int32 element_size,
hm->hash_entries = buffer_get_memory(
buf,
count * element_size,
0, true
64, true
);
}

View File

@ -9,6 +9,7 @@
#ifndef COMS_SYSTEM_INFO_C
#define COMS_SYSTEM_INFO_C
#include <inttypes.h>
#if _WIN32
#include "../platform/win32/SystemInfo.cpp"
#elif __linux__
@ -47,7 +48,7 @@ void system_info_render(char* buf, const SystemInfo* info) {
"L3: Size %u Line %u\n"
"L4: Size %u Line %u\n"
"\n"
"Features: %lld\n"
"Features: %" PRId64 "\n"
"\n"
"GPU:\n"
"==============\n"
@ -78,7 +79,7 @@ void system_info_render(char* buf, const SystemInfo* info) {
info->cpu.cache[1].size, (uint32) info->cpu.cache[1].line_size,
info->cpu.cache[2].size, (uint32) info->cpu.cache[2].line_size,
info->cpu.cache[3].size, (uint32) info->cpu.cache[3].line_size,
(long long) info->cpu.features,
info->cpu.features,
info->gpu[0].name, info->gpu[0].vram,
info->gpu_count < 2 ? "" : info->gpu[1].name, info->gpu_count < 2 ? 0 : info->gpu[1].vram,
info->gpu_count < 3 ? "" : info->gpu[2].name, info->gpu_count < 3 ? 0 : info->gpu[2].vram,

69
test.sh Executable file
View File

@ -0,0 +1,69 @@
#!/bin/bash
start_time=$(date +%s.%N)
clear
EXE_NAME="tests"
DESTINATION_DIR="../build/tests"
mkdir -p "../build"
mkdir -p "$DESTINATION_DIR"
# Clean up previous build files
rm -f "$DESTINATION_DIR"/*.pdb 2>/dev/null
rm -f "$DESTINATION_DIR"/*.idb 2>/dev/null
# Parse command-line arguments
BUILD_TYPE="DEBUG"
BUILD_FLAGS="-g -O0 -Wall -Werror -Wextra -DDEBUG -DDEBUG -ggdb -Werror"
DEBUG_DATA="-g"
if [ "$1" = "-r" ]; then
BUILD_TYPE="RELEASE"
BUILD_FLAGS="-O3 -DNDEBUG"
DEBUG_DATA=""
elif [ "$1" = "-d" ]; then
BUILD_TYPE="DEBUG"
BUILD_FLAGS="-g -O0 -Wall -Werror -Wextra -DDEBUG -ggdb -Werror"
DEBUG_DATA="-g"
fi
source ./check_cpu_features.sh
# Detect CPU features
CPU_FEATURES=$(check_cpu_features)
# Compile the test program
cd "$DESTINATION_DIR" || exit 1
g++ $BUILD_FLAGS -std=c++23 -m64 \
-DUNICODE -D_UNICODE -D__linux__ \
-Wno-unused-result \
-fsanitize=address \
-fsanitize=undefined \
${CPU_FEATURES} \
-o MainTest \
../../cOMS/tests/MainTest.cpp \
$DEBUG_DATA
# Check if the compilation was successful
if [ $? -ne 0 ]; then
echo "Compilation failed for MainTest.cpp"
exit 1
fi
# Run the compiled executable
./MainTest
# Calculate runtime
end_time=$(date +%s.%N)
elapsed_time=$(echo "$end_time - $start_time" | bc)
hours=$(printf "%.0f" $(echo "$elapsed_time / 3600" | bc))
remaining=$(echo "$elapsed_time % 3600" | bc)
minutes=$(printf "%.0f" $(echo "$remaining / 60" | bc))
seconds=$(echo "$remaining % 60" | bc)
# Format the output
printf "Test (incl. build) time %02d:%02d:%05.2f (%.2fs total)\n" \
$hours $minutes $seconds $elapsed_time

View File

@ -44,7 +44,7 @@ if "%1"=="-d" (
REM Compile each .cpp file into an executable
cl ^
%BUILD_FLAGS% /MT /nologo /Gm- /GR- /EHsc /W4 /wd4201 /wd4706 /wd4324 ^
/fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++20 ^
/fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++23 ^
/D WIN32 /D _WINDOWS /D _UNICODE /D UNICODE ^
/D _CRT_SECURE_NO_WARNINGS ^
/Fo"%DESTINATION_DIR%/" /Fe"%DESTINATION_DIR%\MainTest.exe" %DEBUG_DATA% ^

View File

@ -1,16 +1,18 @@
#define UBER_TEST 1
#define PERFORMANCE_TEST 0
#include "math/EvaluatorTest.cpp"
//#include "math/EvaluatorTest.cpp"
#include "memory/ChunkMemoryTest.cpp"
#include "memory/RingMemoryTest.cpp"
#include "stdlib/HashMapTest.cpp"
#include "ui/UILayoutTest.cpp"
#include "ui/UIThemeTest.cpp"
//#include "ui/UILayoutTest.cpp"
//#include "ui/UIThemeTest.cpp"
#include "utils/BitUtilsTest.cpp"
#include "utils/EndianUtilsTest.cpp"
#include "utils/StringUtilsTest.cpp"
#include "utils/MathUtilsTest.cpp"
#include "utils/UtilsTest.cpp"
#include "utils/TimeUtilsTest.cpp"
#ifdef UBER_TEST
#ifdef main
@ -21,16 +23,18 @@
int main() {
TEST_HEADER();
MathEvaluatorTest();
//MathEvaluatorTest();
MemoryChunkMemoryTest();
MemoryRingMemoryTest();
StdlibHashMapTest();
UIUILayoutTest();
UIUIThemeTest();
//UIUILayoutTest();
//UIUIThemeTest();
UtilsBitUtilsTest();
UtilsEndianUtilsTest();
UtilsStringUtilsTest();
UtilsMathUtilsTest();
UtilsUtilsTest();
UtilsTimeUtilsTest();
TEST_FOOTER();

View File

@ -11,6 +11,7 @@
#include <stdio.h>
#include <math.h>
#include <inttypes.h>
#include "../architecture/Intrinsics.h"
static char **_test_log;
@ -18,28 +19,31 @@ static int32_t _test_assert_count;
static int32_t _test_assert_error_count;
static int32_t _test_count = -1;
static int32_t _test_suit_count = 0;
static int32_t _test_global_assert_count = 0;
static int32_t _test_global_assert_error_count = 0;
static int32_t _test_global_count = 0;
static int64_t _test_start;
static int64_t _test_total_start;
#define TEST_PROFILING_LOOPS 1000
#define TEST_HEADER() \
int64_t _test_total_start = test_start_time(); \
_test_total_start = test_start_time(); \
printf("\nStat Tests Assert(OK/NG) Time(ms) Details\n"); \
printf("========================================================================================================================\n")
#define TEST_FOOTER() \
printf("========================================================================================================================\n"); \
printf( \
"%s %5d (%5d/%5d) %8.0f\n\n", \
"%s %5d (%5d/%5d) %8.2f\n\n", \
_test_global_assert_count ? "[NG]" : "[OK]", \
_test_global_count, \
_test_global_assert_count - _test_global_assert_error_count, \
_test_global_assert_count, \
test_duration_time(_test_total_start) / 1000000)
test_duration_time(_test_total_start) / 1000000); \
printf("%d test suits\n\n", _test_suit_count)
#ifdef UBER_TEST
#define TEST_INIT_HEADER() (void)0
@ -70,11 +74,7 @@ int64_t test_start_time()
double test_duration_time(int64_t start)
{
LARGE_INTEGER frequency, end;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&end);
return (double)(end.QuadPart - start) * 1e9 / frequency.QuadPart;
return (double)(test_start_time() - start) / frequency.QuadPart;
}
double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *para)
@ -111,6 +111,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
} while (0)
#else
#include "../platform/linux/ExceptionHandler.h"
#include <time.h>
void test_exception_handler(int signum)
{
printf("Received signal: %d\n", signum);
@ -120,7 +121,7 @@ void test_exception_handler(int signum)
int64_t test_start_time()
{
struct timespec start, end;
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
return start.tv_sec * 1e9 + start.tv_nsec;
@ -128,14 +129,9 @@ int64_t test_start_time()
double test_duration_time(int64_t start)
{
LARGE_INTEGER frequency, end;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&end);
return (double)(end.tv_sec * 1e9 + end.tv_nsec - start);
return (double) (test_start_time() - start);
}
#include <time.h>
double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *para)
{
struct timespec start, end;
@ -151,6 +147,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
#define TEST_INIT(test_count) \
do \
{ \
++_test_suit_count; \
TEST_INIT_HEADER(); \
setvbuf(stdout, NULL, _IONBF, 0); \
signal(SIGSEGV, test_exception_handler); \
@ -176,7 +173,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
if (_test_assert_error_count) \
{ \
printf( \
"[NG] %5d (%5d/%5d) %8.0f %s\n", \
"[NG] %5d (%5d/%5d) %8.2f %s\n", \
_test_count, _test_assert_count - _test_assert_error_count, _test_assert_count, test_duration_time(_test_start) / 1000000, __FILE__); \
for (int i = 0; i < _test_assert_error_count; ++i) \
{ \
@ -187,7 +184,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
else \
{ \
printf( \
"[OK] %5d (%5d/%5d) %8.0f %s\n", \
"[OK] %5d (%5d/%5d) %8.2f %s\n", \
_test_count, _test_assert_count - _test_assert_error_count, _test_assert_count, test_duration_time(_test_start) / 1000000, __FILE__); \
} \
fflush(stdout); \
@ -204,34 +201,62 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
++_test_global_count; \
func()
#define ASSERT_EQUALS(a, b) \
#define ASSERT_EQUALS(a, b) \
do \
{ \
++_test_assert_count; \
++_test_global_assert_count; \
if ((a) != (b)) \
{ \
++_test_global_assert_error_count; \
snprintf( \
_test_log[_test_assert_error_count++], 1024, \
"%4i: %" PRId64 " != %" PRId64, __LINE__, (int64)(a), (int64)(b)); \
} \
} while (0)
#define ASSERT_GREATER_THAN(a, b) \
do \
{ \
++_test_assert_count; \
++_test_global_assert_count; \
if ((a) != (b)) \
if ((a) <= (b)) \
{ \
++_test_global_assert_error_count; \
snprintf( \
_test_log[_test_assert_error_count++], 1024, \
"%4i: %lld != %lld", __LINE__, (int64)(a), (int64)(b)); \
"%4i: %.3f <= %.3f", __LINE__, (float)(a), (float)(b)); \
} \
} while (0)
#define ASSERT_NOT_EQUALS(a, b) \
#define ASSERT_LESSER_THAN(a, b) \
do \
{ \
++_test_assert_count; \
++_test_global_assert_count; \
if ((a) == (b)) \
if ((a) >= (b)) \
{ \
++_test_global_assert_error_count; \
snprintf( \
_test_log[_test_assert_error_count++], 1024, \
"%4i: %lld == %lld", __LINE__, (int64)(a), (int64)(b)); \
"%4i: %.3f >= %.3f", __LINE__, (float)(a), (float)(b)); \
} \
} while (0)
#define ASSERT_NOT_EQUALS(a, b) \
do \
{ \
++_test_assert_count; \
++_test_global_assert_count; \
if ((a) == (b)) \
{ \
++_test_global_assert_error_count; \
snprintf( \
_test_log[_test_assert_error_count++], 1024, \
"%4i: %" PRId64 " == %" PRId64, __LINE__, (int64)(a), (int64)(b)); \
} \
} while (0)
#define ASSERT_EQUALS_WITH_DELTA(a, b, delta) \
do \
{ \
@ -329,7 +354,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
{ \
++_test_global_assert_error_count; \
snprintf(_test_log[_test_global_assert_error_count], 1024, \
"%4i: mismatch at offset %lld", __LINE__, i); \
"%4i: mismatch at offset %" PRId64, __LINE__, i); \
break; \
} \
} \
@ -371,7 +396,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
++_test_global_assert_error_count; \
snprintf( \
_test_log[_test_assert_error_count++], 1024, \
"%4i: %.2f%% (%s: %llu cycles, %s: %llu cycles)", \
"%4i: %.2f%% (%s: %" PRIu64 " cycles, %s: %" PRIu64 " cycles)", \
__LINE__, percent_diff + 100.0f, #func1, (uint64_t)cycles_func1, #func2, (uint64_t)cycles_func2); \
} \
ASSERT_TRUE((a && b) || a == b); \
@ -400,7 +425,7 @@ double test_measure_func_time_ns(void (*func)(volatile void *), volatile void *p
++_test_global_assert_error_count; \
snprintf( \
_test_log[_test_assert_error_count++], 1024, \
"%4i: %.2f%% (%s: %llu cycles)", \
"%4i: %.2f%% (%s: %" PRIu64 " cycles)", \
__LINE__, percent_diff + 100.0f, #func, (uint64_t)cycles_func); \
} \
} while (0)

View File

@ -1,3 +1,4 @@
#include <string.h>
#include "../TestFramework.h"
#include "../../utils/EndianUtils.h"
@ -85,7 +86,7 @@ static void test_endian_swap_uint64() {
static void test_endian_swap_int64() {
int64 val = 0x123456789ABCDEF0;
int64 swapped = endian_swap(val);
ASSERT_EQUALS(0xF0DEBC9A78563412, swapped);
ASSERT_EQUALS((int64) 0xF0DEBC9A78563412, swapped);
}
static void test_endian_swap_float() {

View File

@ -136,6 +136,7 @@ static void test_pow_approx_f64() {
ASSERT_EQUALS_WITH_DELTA(pow_approx(10.0, 0.5), pow(10.0, 0.5), 0.001);
}
#if PERFORMANCE_TEST
// Performance tests for f32 (float) approximate functions
static void _sin_approx_f32(volatile void* val) {
f32* res = (f32*)val;
@ -555,6 +556,7 @@ static void test_pow_approx_performance_f64() {
COMPARE_FUNCTION_TEST_TIME(_pow_approx_f64, _pow_f64, 5.0);
COMPARE_FUNCTION_TEST_CYCLE(_pow_approx_f64, _pow_f64, 5.0);
}
#endif
#ifdef UBER_TEST
#ifdef main
@ -592,31 +594,33 @@ int main() {
TEST_RUN(test_log_approx_f64);
TEST_RUN(test_pow_approx_f64);
// Run performance tests for f32 functions
TEST_RUN(test_sin_approx_performance_f32);
TEST_RUN(test_cos_approx_performance_f32);
TEST_RUN(test_tan_approx_performance_f32);
TEST_RUN(test_sqrt_approx_performance_f32);
TEST_RUN(test_asin_approx_performance_f32);
TEST_RUN(test_acos_approx_performance_f32);
TEST_RUN(test_atan_approx_performance_f32);
TEST_RUN(test_rsqrt_approx_performance_f32);
TEST_RUN(test_exp_approx_performance_f32);
TEST_RUN(test_log_approx_performance_f32);
TEST_RUN(test_pow_approx_performance_f32);
#if PERFORMANCE_TEST
// Run performance tests for f32 functions
TEST_RUN(test_sin_approx_performance_f32);
TEST_RUN(test_cos_approx_performance_f32);
TEST_RUN(test_tan_approx_performance_f32);
TEST_RUN(test_sqrt_approx_performance_f32);
TEST_RUN(test_asin_approx_performance_f32);
TEST_RUN(test_acos_approx_performance_f32);
TEST_RUN(test_atan_approx_performance_f32);
TEST_RUN(test_rsqrt_approx_performance_f32);
TEST_RUN(test_exp_approx_performance_f32);
TEST_RUN(test_log_approx_performance_f32);
TEST_RUN(test_pow_approx_performance_f32);
// Run performance tests for f64 functions
TEST_RUN(test_sin_approx_performance_f64);
TEST_RUN(test_cos_approx_performance_f64);
TEST_RUN(test_tan_approx_performance_f64);
TEST_RUN(test_sqrt_approx_performance_f64);
TEST_RUN(test_asin_approx_performance_f64);
TEST_RUN(test_acos_approx_performance_f64);
TEST_RUN(test_atan_approx_performance_f64);
TEST_RUN(test_rsqrt_approx_performance_f64);
TEST_RUN(test_exp_approx_performance_f64);
TEST_RUN(test_log_approx_performance_f64);
TEST_RUN(test_pow_approx_performance_f64);
// Run performance tests for f64 functions
TEST_RUN(test_sin_approx_performance_f64);
TEST_RUN(test_cos_approx_performance_f64);
TEST_RUN(test_tan_approx_performance_f64);
TEST_RUN(test_sqrt_approx_performance_f64);
TEST_RUN(test_asin_approx_performance_f64);
TEST_RUN(test_acos_approx_performance_f64);
TEST_RUN(test_atan_approx_performance_f64);
TEST_RUN(test_rsqrt_approx_performance_f64);
TEST_RUN(test_exp_approx_performance_f64);
TEST_RUN(test_log_approx_performance_f64);
TEST_RUN(test_pow_approx_performance_f64);
#endif
TEST_FINALIZE();

View File

@ -1,3 +1,5 @@
#include <stdio.h>
#include <ctype.h>
#include "../TestFramework.h"
#include "../../utils/StringUtils.h"
#include "../../utils/EndianUtils.h"
@ -83,6 +85,7 @@ static void test_str_length()
ASSERT_EQUALS(str_length("2asdf dw"), 8);
}
#if PERFORMANCE_TEST
static void _str_length(volatile void* val) {
volatile int64* res = (volatile int64 *) val;
@ -107,7 +110,9 @@ static void test_str_length_performance() {
COMPARE_FUNCTION_TEST_TIME(_str_length, _strlen, 5.0);
COMPARE_FUNCTION_TEST_CYCLE(_str_length, _strlen, 5.0);
}
#endif
#if PERFORMANCE_TEST
static void _str_is_alphanum(volatile void* val) {
bool* res = (bool *) val;
srand(0);
@ -136,6 +141,7 @@ static void test_str_is_alphanum_performance() {
COMPARE_FUNCTION_TEST_TIME(_str_is_alphanum, _isalnum, 5.0);
COMPARE_FUNCTION_TEST_CYCLE(_str_is_alphanum, _isalnum, 5.0);
}
#endif
static void test_sprintf_fast()
{
@ -144,6 +150,7 @@ static void test_sprintf_fast()
ASSERT_TRUE(strcmp(buffer, "This 1337 is a test with 3.00000 values") == 0);
}
#if PERFORMANCE_TEST
static void _sprintf_fast(volatile void* val) {
volatile bool* res = (volatile bool *) val;
@ -164,6 +171,7 @@ static void test_sprintf_fast_performance() {
COMPARE_FUNCTION_TEST_TIME(_sprintf_fast, _sprintf, 5.0);
COMPARE_FUNCTION_TEST_CYCLE(_sprintf_fast, _sprintf, 5.0);
}
#endif
static void test_str_to_float()
{
@ -179,8 +187,6 @@ static void test_str_to_float()
#define main UtilsStringUtilsTest
#endif
#include <windows.h>
int main() {
TEST_INIT(100);
@ -196,9 +202,11 @@ int main() {
TEST_RUN(test_str_length);
TEST_RUN(test_str_to_float);
TEST_RUN(test_str_length_performance);
TEST_RUN(test_str_is_alphanum_performance);
TEST_RUN(test_sprintf_fast_performance);
#if PERFORMANCE_TEST
TEST_RUN(test_str_length_performance);
TEST_RUN(test_str_is_alphanum_performance);
TEST_RUN(test_sprintf_fast_performance);
#endif
TEST_FINALIZE();

View File

@ -0,0 +1,34 @@
#include <string.h>
#include "../TestFramework.h"
#include "../../utils/TimeUtils.h"
static void test_time_index() {
ASSERT_GREATER_THAN(time_index(), 0.0f);
}
static void test_system_time() {
ASSERT_GREATER_THAN(system_time(), 0.0f);
}
static void test_time_mu() {
ASSERT_GREATER_THAN(time_mu(), 0.0f);
}
#ifdef UBER_TEST
#ifdef main
#undef main
#endif
#define main UtilsTimeUtilsTest
#endif
int main() {
TEST_INIT(10);
TEST_RUN(test_time_index);
TEST_RUN(test_system_time);
TEST_RUN(test_time_mu);
TEST_FINALIZE();
return 0;
}

View File

@ -5,7 +5,6 @@ static void test_is_equal() {
uint8_t region1[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
uint8_t region2[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
uint8_t region3[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09};
uint8_t region4[] = {0x01, 0x02, 0x03, 0x04};
// Test equal regions
ASSERT_TRUE(is_equal(region1, region2, sizeof(region1)));
@ -54,6 +53,7 @@ static void test_is_empty() {
ASSERT_TRUE(is_empty(region1, 0));
}
#if PERFORMANCE_TEST
static void _is_equal(volatile void* val) {
volatile bool* res = (volatile bool *) val;
@ -124,6 +124,7 @@ static void test_is_empty_performance() {
COMPARE_FUNCTION_TEST_TIME(_is_empty2, _memcmp_empty2, 10.0);
COMPARE_FUNCTION_TEST_CYCLE(_is_empty2, _memcmp_empty2, 10.0);
}
#endif
#ifdef UBER_TEST
#ifdef main
@ -138,8 +139,10 @@ int main() {
TEST_RUN(test_is_equal);
TEST_RUN(test_is_empty);
TEST_RUN(test_is_equal_performance);
TEST_RUN(test_is_empty_performance);
#if PERFORMANCE_TEST
TEST_RUN(test_is_equal_performance);
TEST_RUN(test_is_empty_performance);
#endif
TEST_FINALIZE();

View File

@ -48,7 +48,7 @@ for /R %%f in (*Test.cpp) do (
REM Compile each .cpp file into an executable
cl ^
%BUILD_FLAGS% /MT /nologo /Gm- /GR- /EHsc /W4 /wd4201 /wd4706 /wd4324 ^
/fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++20 ^
/fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++23 ^
/D WIN32 /D _WINDOWS /D _UNICODE /D UNICODE ^
/D _CRT_SECURE_NO_WARNINGS ^
/Fo"%DESTINATION_DIR%/" /Fe"%DESTINATION_DIR%\%BASENAME%.exe" %DEBUG_DATA% ^

70
tests_iter.sh Executable file
View File

@ -0,0 +1,70 @@
#!/bin/bash
clear
EXE_NAME="tests"
DESTINATION_DIR="../build/tests"
# Create build directories if they don't exist
mkdir -p "../build"
mkdir -p "$DESTINATION_DIR"
# Clean up previous build files
rm -f "$DESTINATION_DIR"/*.pdb 2>/dev/null
rm -f "$DESTINATION_DIR"/*.idb 2>/dev/null
# Default build configuration
BUILD_TYPE="DEBUG"
BUILD_FLAGS="-g -O0 -Wall -Werror -Wextra -DDEBUG -ggdb -Werror"
DEBUG_DATA="-g"
# Parse command-line arguments
if [ "$1" = "-r" ]; then
BUILD_TYPE="RELEASE"
BUILD_FLAGS="-O3 -DNDEBUG"
DEBUG_DATA=""
elif [ "$1" = "-d" ]; then
BUILD_TYPE="DEBUG"
BUILD_FLAGS="-g -O0 -Wall -Werror -Wextra -DDEBUG -ggdb -Werror"
DEBUG_DATA="-g"
fi
source ./check_cpu_features.sh
# Detect CPU features
CPU_FEATURES=$(check_cpu_features)
# Find all *Test.cpp files in the ./ directory and subdirectories
find ./ -name "*Test.cpp" | while read -r test_file; do
# Get the base name of the file without extension
basename=$(basename "$test_file" .cpp)
echo "Compiling $test_file..."
# Compile each test file
g++ $BUILD_FLAGS -std=c++23 -m64 \
-DUNICODE -D_UNICODE -D__linux__ \
-Wno-unused-result \
-fsanitize=address \
-fsanitize=undefined \
${CPU_FEATURES} \
-o "$DESTINATION_DIR/$basename" \
"$test_file" \
$DEBUG_DATA
# Check if compilation was successful
if [ $? -ne 0 ]; then
echo "Compilation failed for $test_file"
exit 1
fi
# Run the compiled test
echo "Running $basename..."
"$DESTINATION_DIR/$basename"
# Check if test ran successfully
if [ $? -ne 0 ]; then
echo "Test $basename failed"
exit 1
fi
done

View File

@ -12,6 +12,7 @@
#include <stdio.h>
#include "../stdlib/Types.h"
#include "../log/Log.h"
#include "../log/Stats.h"
#include "Atomic.h"
#if _WIN32
@ -26,14 +27,21 @@
void thread_create(Worker* worker, ThreadJobFunc routine, void* arg)
{
LOG_1("Thread starting");
coms_pthread_create(&worker->thread, NULL, routine, arg);
LOG_INCREMENT(DEBUG_COUNTER_THREAD);
LOG_2("%d threads running", {{LOG_DATA_INT64, (void *) &_stats_counter[DEBUG_COUNTER_THREAD]}});
}
void thread_stop(Worker* worker)
{
atomic_set_release(&worker->state, 0);
coms_pthread_join(worker->thread, NULL);
LOG_1("Thread ended");
LOG_DECREMENT(DEBUG_COUNTER_THREAD);
LOG_2("%d threads running", {{LOG_DATA_INT64, (void *) &_stats_counter[DEBUG_COUNTER_THREAD]}});
}
#endif

View File

@ -16,6 +16,7 @@
#include "Thread.h"
#include "Atomic.h"
#include "ThreadJob.h"
#include "../log/DebugContainer.h"
struct ThreadPool {
// This is not a threaded queue since we want to handle the mutex in here, not in the queue for finer control
@ -25,29 +26,50 @@ struct ThreadPool {
mutex_cond work_cond;
mutex_cond working_cond;
// By design the working_cnt is <= thread_cnt
alignas(4) atomic_32 int32 working_cnt;
alignas(4) atomic_32 int32 thread_cnt;
int32 size;
int32 element_size;
// 0 = down, 1 = shutting down, 2 = running
alignas(4) atomic_32 int32 state;
alignas(4) atomic_32 int32 id_counter;
DebugContainer* debug_container;
};
// @performance Can we optimize this? This is a critical function
// @performance Can we optimize this? This is a critical function.
// If we have a small worker the "spinup"/"re-activation" time is from utmost importance
static
THREAD_RETURN thread_pool_worker(void* arg)
{
ThreadPool* pool = (ThreadPool *) arg;
if (pool->debug_container) {
_log_fp = pool->debug_container->log_fp;
_log_memory = pool->debug_container->log_memory;
_dmc = pool->debug_container->dmc;
_perf_stats = pool->debug_container->perf_stats;
_perf_active = pool->debug_container->perf_active;
_stats_counter = pool->debug_container->stats_counter;
*_perf_active = *pool->debug_container->perf_active;
}
// @bug Why doesn't this work? There must be some threading issue
LOG_2("Thread pool worker starting up");
LOG_INCREMENT(DEBUG_COUNTER_THREAD);
PoolWorker* work;
while (true) {
mutex_lock(&pool->work_mutex);
while (queue_is_empty(&pool->work_queue) && !pool->state) {
while (pool->state > 1 && queue_is_empty(&pool->work_queue)) {
coms_pthread_cond_wait(&pool->work_cond, &pool->work_mutex);
}
if (pool->state == 1) {
if (pool->state < 2) {
mutex_unlock(&pool->work_mutex);
break;
@ -55,7 +77,8 @@ THREAD_RETURN thread_pool_worker(void* arg)
// We define a queue element as free based on it's id
// So even if we "keep" it in the queue the pool will not overwrite it as long as the id > 0 (see pool_add)
// This is only a ThreadPool specific queue behavior to avoid additional copies
// This is only a ThreadPool specific queue behavior to avoid additional memory copy
// @bug this needs to be a threaded queue
work = (PoolWorker *) queue_dequeue_keep(&pool->work_queue);
mutex_unlock(&pool->work_mutex);
@ -63,7 +86,7 @@ THREAD_RETURN thread_pool_worker(void* arg)
continue;
}
atomic_increment_relaxed(&pool->working_cnt);
atomic_increment_release(&pool->working_cnt);
atomic_set_release(&work->state, 2);
LOG_2("ThreadPool worker started");
work->func(work);
@ -79,15 +102,21 @@ THREAD_RETURN thread_pool_worker(void* arg)
atomic_set_release(&work->id, 0);
}
atomic_decrement_relaxed(&pool->working_cnt);
atomic_decrement_release(&pool->working_cnt);
if (atomic_get_relaxed(&pool->state) == 0 && atomic_get_relaxed(&pool->working_cnt) == 0) {
// Signal that we ran out of work (maybe the main thread needs this info)
// This is not required for the thread pool itself but maybe some other part of the main thread wants to know
if (atomic_get_relaxed(&pool->working_cnt) == 0) {
coms_pthread_cond_signal(&pool->working_cond);
}
}
// We tell the thread pool taht this worker thread is shutting down
atomic_decrement_release(&pool->thread_cnt);
coms_pthread_cond_signal(&pool->working_cond);
atomic_decrement_relaxed(&pool->thread_cnt);
LOG_2("Thread pool worker shutting down");
LOG_DECREMENT(DEBUG_COUNTER_THREAD);
return (THREAD_RETURN) NULL;
}
@ -119,11 +148,15 @@ void thread_pool_alloc(
coms_pthread_cond_init(&pool->work_cond, NULL);
coms_pthread_cond_init(&pool->working_cond, NULL);
pool->state = 2;
coms_pthread_t thread;
for (pool->size = 0; pool->size < thread_count; ++pool->size) {
coms_pthread_create(&thread, NULL, thread_pool_worker, pool);
coms_pthread_detach(thread);
}
LOG_2("%d threads running", {{LOG_DATA_INT64, (void *) &_stats_counter[DEBUG_COUNTER_THREAD]}});
}
void thread_pool_create(
@ -154,17 +187,23 @@ void thread_pool_create(
coms_pthread_cond_init(&pool->work_cond, NULL);
coms_pthread_cond_init(&pool->working_cond, NULL);
pool->state = 2;
coms_pthread_t thread;
for (pool->size = 0; pool->size < thread_count; ++pool->size) {
coms_pthread_create(&thread, NULL, thread_pool_worker, pool);
coms_pthread_detach(thread);
}
LOG_2("%d threads running", {{LOG_DATA_INT64, (void *) &_stats_counter[DEBUG_COUNTER_THREAD]}});
}
void thread_pool_wait(ThreadPool* pool)
{
mutex_lock(&pool->work_mutex);
while ((!pool->state && pool->working_cnt != 0) || (pool->state && pool->thread_cnt != 0)) {
// @question We removed some state checks here, not sure if they were really necessary
// remove this comment once we are sure everything works as expected
while (pool->working_cnt != 0 || pool->thread_cnt != 0) {
coms_pthread_cond_wait(&pool->working_cond, &pool->work_mutex);
}
mutex_unlock(&pool->work_mutex);
@ -184,6 +223,9 @@ void thread_pool_destroy(ThreadPool* pool)
mutex_destroy(&pool->work_mutex);
coms_pthread_cond_destroy(&pool->work_cond);
coms_pthread_cond_destroy(&pool->working_cond);
// This sets the state to "down"
atomic_set_release(&pool->state, 0);
}
PoolWorker* thread_pool_add_work(ThreadPool* pool, const PoolWorker* job)

View File

@ -89,7 +89,7 @@ void ui_layout_assign_children(
break;
}
str_copy_move_until(&pos, block_name, ":");
str_copy_move_until(block_name, &pos, ":");
str_move_past(&pos, '\n');
// Children array (located after the UIElement)
@ -182,8 +182,8 @@ void layout_from_file_txt(
continue;
}
str_copy_move_until(&pos, block_name, ":"); ++pos;
str_copy_move_until(&pos, block_type, " \r\n");
str_copy_move_until(block_name, &pos, ":"); ++pos;
str_copy_move_until(block_type, &pos, " \r\n");
str_move_past(&pos, '\n');
// Insert new element
@ -235,7 +235,7 @@ void layout_from_file_txt(
continue;
}
str_copy_move_until(&pos, block_name, ":");
str_copy_move_until(block_name, &pos, ":");
str_move_past(&pos, '\n');
UIElement* element = (UIElement *) (layout->data + ((HashEntryInt32 *) hashmap_get_entry(&layout->hash_map, block_name))->value);
@ -268,7 +268,7 @@ void layout_from_file_txt(
continue;
}
str_copy_move_until(&pos, block_name, ":");
str_copy_move_until(block_name, &pos, ":");
str_move_past(&pos, '\n');
root_children[child++] = ((HashEntryInt32 *) hashmap_get_entry(&layout->hash_map, block_name))->value;
}

View File

@ -137,7 +137,7 @@ void theme_from_file_txt(
// Handle new group
/////////////////////////////////////////////////////////
if (!block_open) {
str_copy_move_until(&pos, block_name, " \r\n");
str_copy_move_until(block_name, &pos, " \r\n");
// All blocks need to start with #. In the past this wasn't the case and may not be in the future. This is why we keep this if here.
if (*block_name == '#' || *block_name == '.') {
@ -164,7 +164,7 @@ void theme_from_file_txt(
// Handle new attribute for previously found group
/////////////////////////////////////////////////////////
str_copy_move_until(&pos, attribute_name, " :\n");
str_copy_move_until(attribute_name, &pos, " :\n");
// Skip any white spaces or other delimeter
str_skip_list(&pos, " \t:", sizeof(" \t:") - 1);

View File

@ -209,7 +209,7 @@ void ui_attribute_parse_value(UIAttribute* attr, const char* attribute_name, con
attr->attribute_id = (UIAttributeType) ui_attribute_type_to_id(attribute_name);
char value[64];
str_copy_until(pos, value, "\r\n");
str_copy_until(value, pos, "\r\n");
if (str_is_integer(value)) {
attr->value_int = (int32) str_to_int(value);
@ -222,7 +222,7 @@ void ui_attribute_parse_value(UIAttribute* attr, const char* attribute_name, con
hexstr_to_rgba(&attr->value_v4_f32, pos);
attr->datatype = UI_ATTRIBUTE_DATA_TYPE_V4_F32;
} else {
str_copy_until(value, attr->value_str, "\r\n");
str_copy_until(attr->value_str, value, "\r\n");
attr->datatype = UI_ATTRIBUTE_DATA_TYPE_STR;
}
}

View File

@ -23,7 +23,7 @@
#define BIT_SET_L2R(num, pos, bits) ((num) | (1U << ((bits - 1) - (pos))))
#define BIT_UNSET_L2R(num, pos, bits) ((num) & ~(1U << ((bits - 1) - (pos))))
#define BIT_FLIP_L2R(num, pos, bits) ((num) ^ (1U << ((bits - 1) - (pos))))
#define BIT_SET_TO_L2R(num, pos, x, bits) ((num) & ~((uint32) 1 << ((bits - 1) - (pos))) | ((uint32) (x) << ((bits - 1) - (pos))))
#define BIT_SET_TO_L2R(num, pos, x, bits) (((num) & ~((uint32_t)(1) << ((bits) - 1 - (pos)))) | (((uint32_t)(x) & 1U) << ((bits) - 1 - (pos))))
#define BITS_GET_8_L2R(num, pos, to_read) (((num) >> (8 - (pos) - (to_read))) & ((1U << (to_read)) - 1))
#define BITS_GET_16_L2R(num, pos, to_read) (((num) >> (16 - (pos) - (to_read))) & ((1U << (to_read)) - 1))
#define BITS_GET_32_L2R(num, pos, to_read) (((num) >> (32 - (pos) - (to_read))) & ((1U << (to_read)) - 1))
@ -41,7 +41,7 @@
#define BIT_SET_R2L(num, pos) ((num) | ((uint32) 1 << (pos)))
#define BIT_UNSET_R2L(num, pos) ((num) & ~((uint32) 1 << (pos)))
#define BIT_FLIP_R2L(num, pos) ((num) ^ ((uint32) 1 << (pos)))
#define BIT_SET_TO_R2L(num, pos, x) ((num) & ~((uint32) 1 << (pos)) | ((uint32) (x) << (pos)))
#define BIT_SET_TO_R2L(num, pos, x) (((num) & ~((uint32_t)(1) << (pos))) | ((uint32_t)(x) << (pos)))
// @performance Try to use this version over the L2R version for performance reasons
#define BITS_GET_8_R2L(num, pos, to_read) (((num) >> (pos)) & ((1U << (to_read)) - 1))
#define BITS_GET_16_R2L(num, pos, to_read) (((num) >> (pos)) & ((1U << (to_read)) - 1))

View File

@ -941,7 +941,7 @@ void str_copy_long(char* __restrict dest, const char* __restrict src) noexcept
}
inline
void str_copy_move_until(const char** __restrict src, char* __restrict dest, char delim) noexcept
void str_copy_move_until(char* __restrict dest, const char* __restrict* __restrict src, char delim) noexcept
{
while (**src != delim && **src != '\0') {
*dest++ = **src;
@ -952,7 +952,7 @@ void str_copy_move_until(const char** __restrict src, char* __restrict dest, cha
}
inline
void str_copy_move_until(const char** __restrict src, char* __restrict dest, const char* __restrict delim) noexcept
void str_copy_move_until(char* __restrict dest, const char* __restrict* __restrict src, const char* __restrict delim) noexcept
{
size_t len = str_length(delim);
@ -1055,7 +1055,7 @@ str_concat_append(char* dst, size_t dst_length, const char* src) noexcept
inline int64
str_concat_new(
char* dst,
char* __restrict dst,
const char* src1, size_t src1_length,
const char* src2, size_t src2_length
) noexcept {
@ -1092,7 +1092,7 @@ void str_concat_append(
}
inline void
str_concat_new(char* dst, const char* src, int64 data) noexcept
str_concat_new(char* __restrict dst, const char* __restrict src, int64 data) noexcept
{
size_t src_len = str_length(src);
memcpy(dst, src, src_len);
@ -1115,7 +1115,7 @@ void str_remove(char* __restrict dst, size_t remove_pos, size_t remove_length) n
}
inline
char* strtok(char* str, const char* __restrict delim, char* *key) noexcept {
char* strtok(char* str, const char* __restrict delim, char** key) noexcept {
char* result;
if (str == NULL) {
str = *key;
@ -1139,7 +1139,7 @@ char* strtok(char* str, const char* __restrict delim, char* *key) noexcept {
}
inline constexpr
bool str_contains(const char* haystack, const char* needle) noexcept
bool str_contains(const char* __restrict haystack, const char* __restrict needle) noexcept
{
// @performance would it make sense to only check until haystack - strlen(needle)?
// I'm not sure the strlen overhead is worth it
@ -1163,7 +1163,7 @@ bool str_contains(const char* haystack, const char* needle) noexcept
}
inline constexpr
bool str_contains(const char* haystack, const char* needle, size_t length) noexcept
bool str_contains(const char* __restrict haystack, const char* __restrict needle, size_t length) noexcept
{
while (*haystack != '\0' && length > 0) {
const char* p1 = haystack;
@ -1320,7 +1320,7 @@ int32 str_compare_caseless(const char* str1, const char* str2, size_t n) noexcep
}
inline constexpr
bool str_ends_with(const char* str, const char* suffix) noexcept {
bool str_ends_with(const char* __restrict str, const char* __restrict suffix) noexcept {
if (!str || !suffix) {
return false;
}
@ -1434,7 +1434,7 @@ void str_move_to_pos(const char** str, int32 pos) noexcept
}
inline
void str_move_past(const char** str, char delim) noexcept
void str_move_past(const char* __restrict* __restrict str, char delim) noexcept
{
while (**str != delim && **str != '\0') {
++(*str);
@ -1549,7 +1549,7 @@ void str_skip_until_list(const char** __restrict str, const char* __restrict del
}
inline
void hexstr_to_rgba(v4_f32* rgba, const char* hex) noexcept
void hexstr_to_rgba(v4_f32* __restrict rgba, const char* __restrict hex) noexcept
{
if (*hex == '#') {
++hex;
@ -1886,7 +1886,7 @@ void sprintf_fast(char* __restrict buffer, int32 buffer_length, const char* __re
}
// There are situations where you only want to replace a certain amount of %
void sprintf_fast_iter(char* buffer, const char* format, ...) noexcept {
void sprintf_fast_iter(char* __restrict buffer, const char* __restrict format, ...) noexcept {
va_list args;
va_start(args, format);

View File

@ -10,13 +10,13 @@
#define COMS_UTILS_TEST_UTILS_H
#if DEBUG
#define ASSERT_SIMPLE(a) if (!(a)) { \
#define ASSERT_SIMPLE(a) if (!(a)) { \
/* cppcheck-suppress nullPointer */ \
*(volatile int *)0 = 0; \
*(volatile int *)0 = 0; \
}
#define ASSERT_SIMPLE_CONST(a) if constexpr (!(a)) { \
/* cppcheck-suppress nullPointer */ \
*(volatile int *)0 = 0; \
#define ASSERT_SIMPLE_CONST(a) if constexpr (!(a)) { \
/* cppcheck-suppress nullPointer */ \
*(volatile int *)0 = 0; \
}
#if DEBUG_STRICT