dqn: Document VArray, TicketMutex, fix test ASAN crash

This commit is contained in:
doyle 2023-04-12 00:27:32 +10:00
parent df230539a1
commit ee477272c4
5 changed files with 309 additions and 181 deletions

View File

@ -16,7 +16,7 @@ pushd Build
REM Tp Treat header file as CPP source file
set compile_flags=-MT -EHa -GR- -Od -Oi -Z7 -wd4201 -D DQN_TEST_WITH_MAIN -nologo
set linker_flags=-link -nologo
set msvc_flags=
set msvc_flags=-fsanitize=address
set clang_flags=-fsanitize=address -fsanitize=undefined
REM Compiler: MSVC cl
@ -31,7 +31,7 @@ pushd Build
REM ------------------------------------------------------------------------
where /q clang-cl || (
echo [WARN] Optional clang compile via clang-cl if it's in the path, please put clang-cl on the path for this feature
exit /b 0
exit /b 1
)
clang-cl %compile_flags% %clang_flags% %code_dir%dqn_unit_tests.cpp /Fe:dqn_unit_tests_clang %link_flags%
popd

300
dqn.h
View File

@ -12,6 +12,7 @@
// [$GSTR] Global Structs | | Forward declare useful structs
// [$W32H] Win32 minimal header | DQN_NO_WIN32_MINIMAL_HEADER | Minimal windows.h subset
// [$INTR] Intrinsics | | Atomics, cpuid, ticket mutex
// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics
// [$STBS] stb_sprintf | | Portable sprintf
// [$CALL] Dqn_CallSite | | Source code location/tracing
// [$ALLO] Dqn_Allocator | | Generic allocator interface
@ -135,8 +136,7 @@
// =================================================================================================
#define Dqn_PowerOfTwoRoundUp(value, power_of_two) (((value) + ((power_of_two) - 1)) & ~((power_of_two) - 1))
// NOTE: Memory allocation dependencies
// -----------------------------------------------------------------------------
// NOTE: Alloc Macros ==============================================================================
#if !defined(DQN_ALLOC)
#define DQN_ALLOC(size) Dqn_VMem_Reserve(size, Dqn_VMemCommit_Yes)
#endif
@ -145,8 +145,7 @@
#define DQN_DEALLOC(ptr, size) Dqn_VMem_Release(ptr, size)
#endif
// NOTE: string.h dependencies
// -----------------------------------------------------------------------------
// NOTE: String.h Dependnecies =====================================================================
#if !defined(DQN_MEMCPY) || !defined(DQN_MEMSET) || !defined(DQN_MEMCMP) || !defined(DQN_MEMMOVE)
#include <string.h>
#if !defined(DQN_MEMCPY)
@ -163,8 +162,7 @@
#endif
#endif
// NOTE: math.h dependencies
// -----------------------------------------------------------------------------
// NOTE: Math.h Dependnecies =======================================================================
#if !defined(DQN_SQRTF) || !defined(DQN_SINF) || !defined(DQN_COSF) || !defined(DQN_TANF)
#include <math.h>
#define DQN_SQRTF(val) sqrtf(val)
@ -179,8 +177,7 @@
#endif
#endif
// NOTE: Math macros
// -----------------------------------------------------------------------------
// NOTE: Math Macros ===============================================================================
#define DQN_PI 3.14159265359f
#define DQN_DEGREE_TO_RADIAN(degrees) ((degrees) * (DQN_PI / 180.0f))
@ -192,8 +189,7 @@
#define DQN_CLAMP(val, lo, hi) DQN_MAX(DQN_MIN(val, hi), lo)
#define DQN_SQUARED(val) ((val) * (val))
// NOTE: Function/variable annotations
// -----------------------------------------------------------------------------
// NOTE: Function/Variable Annotations =============================================================
#if defined(DQN_STATIC_API)
#define DQN_API static
#else
@ -210,8 +206,7 @@
#define DQN_FORCE_INLINE inline __attribute__((always_inline))
#endif
// NOTE: Preprocessor token tricks
// -----------------------------------------------------------------------------
// NOTE: Preprocessor Token Tricks =================================================================
#define DQN_TOKEN_COMBINE2(x, y) x ## y
#define DQN_TOKEN_COMBINE(x, y) DQN_TOKEN_COMBINE2(x, y)
#define DQN_UNIQUE_NAME(prefix) DQN_TOKEN_COMBINE(prefix, __LINE__)
@ -224,30 +219,26 @@
b = temp; \
} while (0)
// NOTE: Compile time evaluation macros
// -----------------------------------------------------------------------------
// NOTE: Size Macros ===============================================================================
#define DQN_ISIZEOF(val) DQN_CAST(ptrdiff_t)sizeof(val)
#define DQN_ARRAY_UCOUNT(array) (sizeof(array)/(sizeof((array)[0])))
#define DQN_ARRAY_ICOUNT(array) (Dqn_isize)DQN_ARRAY_UCOUNT(array)
#define DQN_CHAR_COUNT(string) (sizeof(string) - 1)
// NOTE: SI byte units
// -----------------------------------------------------------------------------
// NOTE: SI Byte Macros ============================================================================
#define DQN_BYTES(val) (val)
#define DQN_KILOBYTES(val) (1024ULL * DQN_BYTES(val))
#define DQN_MEGABYTES(val) (1024ULL * DQN_KILOBYTES(val))
#define DQN_GIGABYTES(val) (1024ULL * DQN_MEGABYTES(val))
// NOTE: Duration to sections
// -----------------------------------------------------------------------------
// NOTE: Time Macros ===============================================================================
#define DQN_SECONDS_TO_MS(val) ((val) * 1000.0f)
#define DQN_MINS_TO_S(val) ((val) * 60ULL)
#define DQN_HOURS_TO_S(val) (DQN_MINS_TO_S(val) * 60ULL)
#define DQN_DAYS_TO_S(val) (DQN_HOURS_TO_S(val) * 24ULL)
#define DQN_YEARS_TO_S(val) (DQN_DAYS_TO_S(val) * 365ULL)
// NOTE: Debug macros
// -----------------------------------------------------------------------------
// NOTE: Debug Macros ==============================================================================
#if !defined(DQN_DEBUG_BREAK)
#if defined(NDEBUG)
#define DQN_DEBUG_BREAK
@ -267,37 +258,36 @@
#define DQN_MEMSET_BYTE 0
#endif
// NOTE: Assert macros
// ------------------------------------------------------------------------------------------------
#define DQN_HARD_ASSERT(expr) DQN_HARD_ASSERT_MSG(expr, "")
#define DQN_HARD_ASSERT_MSG(expr, fmt, ...) \
if (!(expr)) { \
// NOTE: Assert Macros =============================================================================
#define DQN_HARD_ASSERT(expr) DQN_HARD_ASSERTF(expr, "")
#define DQN_HARD_ASSERTF(expr, fmt, ...) \
if (!(expr)) { \
Dqn_Log_ErrorF("Hard assert triggered " #expr ". " fmt, ##__VA_ARGS__); \
DQN_DEBUG_BREAK; \
DQN_DEBUG_BREAK; \
}
#if defined(DQN_NO_ASSERT)
#define DQN_ASSERTF(...)
#define DQN_ASSERT(...)
#else
#define DQN_ASSERT(expr) DQN_ASSERTF(expr, "")
#define DQN_ASSERTF(expr, fmt, ...) \
if (!(expr)) { \
Dqn_Log_ErrorF("Assert triggered " #expr ". " fmt, ##__VA_ARGS__); \
DQN_DEBUG_BREAK; \
}
#endif
#if defined(__cplusplus__)
#define DQN_ZERO_INIT {}
#else
#define DQN_ZERO_INIT {0}
#endif
#if defined(DQN_NO_ASSERT)
#define DQN_ASSERT(expr)
#define DQN_ASSERT_MSG(expr, fmt, ...)
#else
#define DQN_ASSERT(expr) DQN_HARD_ASSERT_MSG(expr, "")
#define DQN_ASSERT_MSG(expr, fmt, ...) DQN_HARD_ASSERT_MSG(expr, fmt, ##__VA_ARGS__)
#endif
#define DQN_INVALID_CODE_PATHF(fmt, ...) DQN_ASSERTF(0, fmt, ##__VA_ARGS__)
#define DQN_INVALID_CODE_PATH DQN_INVALID_CODE_PATHF("Invalid code path triggered")
#define DQN_INVALID_CODE_PATH_MSG(fmt, ...) DQN_ASSERT_MSG(0, fmt, ##__VA_ARGS__)
#define DQN_INVALID_CODE_PATH DQN_INVALID_CODE_PATH_MSG("Invalid code path triggered")
#define DQN_HARD_INVALID_CODE_PATH_MSG(fmt, ...) DQN_HARD_ASSERT_MSG(0, fmt, ##__VA_ARGS__)
#define DQN_HARD_INVALID_CODE_PATH DQN_HARD_INVALID_CODE_PATH_MSG("Invalid code path triggered")
// NOTE: Defer macros
// ------------------------------------------------------------------------------------------------
// NOTE: Defer Macro ===============================================================================
#if 0
#include <stdio.h>
int main()
@ -479,50 +469,57 @@ struct Dqn_CPUIDRegisters
/// Execute 'CPUID' instruction to query the capabilities of the current CPU.
Dqn_CPUIDRegisters Dqn_CPUID(int function_id);
/// A mutex implemented using an atomic compare and swap on tickets handed out
/// for each critical section.
///
/// This mutex serves ticket in order and will block all other threads until the
/// tickets are returned in order. The thread with the oldest ticket that has
/// not been returned has right of way to execute, all other threads will be
/// blocked in an atomic compare and swap loop. block execution by going into an
/// atomic
///
/// When a thread is blocked by this mutex, a spinlock intrinsic `_mm_pause` is
/// used to yield the CPU and reduce spinlock on the thread. This mutex is not
/// ideal for long blocking operations. This mutex does not issue any syscalls
/// and relies entirely on atomic instructions.
// =================================================================================================
// [$TMUT] Dqn_TicketMutex | | Userland mutex via spinlocking atomics
// =================================================================================================
// A mutex implemented using an atomic compare and swap on tickets handed out
// for each critical section.
//
// This mutex serves ticket in order and will block all other threads until the
// tickets are returned in order. The thread with the oldest ticket that has
// not been returned has right of way to execute, all other threads will be
// blocked in an atomic compare and swap loop. block execution by going into an
// atomic
//
// When a thread is blocked by this mutex, a spinlock intrinsic `_mm_pause` is
// used to yield the CPU and reduce spinlock on the thread. This mutex is not
// ideal for long blocking operations. This mutex does not issue any syscalls
// and relies entirely on atomic instructions.
//
// NOTE: API
//
// @proc Dqn_TicketMutex_Begin, End
// @desc Lock and unlock the mutex respectively
//
// @proc Dqn_TicketMutex_MakeTicket
// @desc Allocate the next available ticket from the mutex for locking using
// Dqn_TicketMutex_BeginTicket().
// @param[in] mutex The mutex
// @code
// Dqn_TicketMutex mutex = {};
// unsigned int ticket = Dqn_TicketMutex_MakeTicket(&mutex);
// Dqn_TicketMutex_BeginTicket(&mutex, ticket); // Blocking call until we attain the lock
// Dqn_TicketMutex_End(&mutex);
// @endcode
//
// @proc Dqn_TicketMutex_BeginTicket
// @desc Lock the mutex using the given ticket if possible, otherwise block
// waiting until the mutex can be locked.
//
// @proc Dqn_TicketMutex_CanLock
// @desc Determine if the mutex can be locked using the given ticket number
struct Dqn_TicketMutex
{
unsigned int volatile ticket; ///< The next ticket to give out to the thread taking the mutex
unsigned int volatile serving; ///< The ticket ID to block the mutex on until it is returned
};
/// Lock the mutex
void Dqn_TicketMutex_Begin(Dqn_TicketMutex *mutex);
/// Unlock the mutex
void Dqn_TicketMutex_End(Dqn_TicketMutex *mutex);
/// Allocate the next available ticket from the mutex for locking using
/// Dqn_TicketMutex_BeginTicket().
///
/// @param[in] mutex The mutex
///
/// @code
/// Dqn_TicketMutex mutex = {};
/// unsigned int ticket = Dqn_TicketMutex_MakeTicket(&mutex);
/// Dqn_TicketMutex_BeginTicket(&mutex, ticket); // Blocking call until we attain the lock
/// Dqn_TicketMutex_End(&mutex);
/// @endcode
Dqn_uint Dqn_TicketMutex_MakeTicket(Dqn_TicketMutex *mutex);
/// Lock the mutex using the given ticket if possible, otherwise block
/// waiting until the mutex can be locked.
void Dqn_TicketMutex_BeginTicket(Dqn_TicketMutex const *mutex, Dqn_uint ticket);
/// Determine if the mutex can be locked using the given ticket number.
bool Dqn_TicketMutex_CanLock(Dqn_TicketMutex const *mutex, Dqn_uint ticket);
void Dqn_TicketMutex_Begin (Dqn_TicketMutex *mutex);
void Dqn_TicketMutex_End (Dqn_TicketMutex *mutex);
Dqn_uint Dqn_TicketMutex_MakeTicket (Dqn_TicketMutex *mutex);
void Dqn_TicketMutex_BeginTicket(Dqn_TicketMutex const *mutex, Dqn_uint ticket);
bool Dqn_TicketMutex_CanLock (Dqn_TicketMutex const *mutex, Dqn_uint ticket);
// =================================================================================================
// [$STBS] stb_sprintf | | Portable sprintf
@ -1526,6 +1523,67 @@ DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocF (Dqn_ArenaCatalog *catalog, Dqn_usize
// =================================================================================================
// [$VARR] Dqn_VArray | DQN_NO_VARRAY | Array backed by virtual memory arena
// =================================================================================================
//
// An array that is backed by virtual memory by reserving addressing space and
// comitting pages as items are allocated in the array. This array never
// reallocs, instead you should reserve the upper bound of the memory you will
// possibly ever need (e.g. 16GB) and let the array commit physical pages on
// demand. On 64 bit operating systems you are given 48 bits of addressable
// space giving you 256 TB of reservable memory. This gives you practically
// an unlimited array capacity that avoids reallocs and only consumes memory
// that is actually occupied by the array.
//
// Each page that is committed into the array will be at page/allocation
// granularity which are always cache aligned. This array essentially retains
// all the benefits of normal arrays,
//
// - contiguous memory
// - O(1) random access
// - O(N) iterate
//
// In addition to no realloc on expansion or shrinking.
//
// NOTE: API
//
// @proc Dqn_VArray_InitByteSize, Dqn_VArray_Init
// @desc Initialise an array with the requested byte size or item capacity
// respectively. The returned array may have a higher capacity than the
// requested amount since requested memory from the OS may have a certain
// alignment requirement (e.g. on Windows reserve/commit are 64k/4k aligned).
//
// @proc Dqn_VArray_IsValid
// @desc Verify if the array has been initialised
//
// @proc Dqn_VArray_Make, Dqn_VArray_Add
// @desc Allocate items into the array
// 'Make' creates the `count` number of requested items
// 'Add' adds the array of items into the array
// @return The array of items allocated. Null pointer if the array is invalid
// or the array has insufficient space for the requested items.
//
// @proc Dqn_VArray_EraseRange
// @desc Erase the next `count` items at `begin_index` in the array. `count`
// can be positive or negative which dictates the if we erase forward from the
// `begin_index` or in reverse.
//
// This operation will invalidate all pointers to the array!
//
// @param erase The erase method, stable erase will shift all elements after
// the erase ranged into the range. Unstable erase will copy the tail elements
// into the range to delete.
//
// @proc Dqn_VArray_Reserve
// @desc Ensure that the requested number of items are backed by physical
// pages from the OS. Calling this pre-emptively will minimise syscalls into
// the kernel to request memory. The requested items will be rounded up to the
// in bytes to the allocation granularity of OS allocation APIs hence the
// reserved space may be greater than the requested amount (e.g. this is 4k
// on Windows).
//
// TODO(doyle)
//
// Add an API for shrinking the array by decomitting pages back to the OS.
template <typename T> struct Dqn_VArray
{
Dqn_ArenaBlock *block; ///< Block of memory from the allocator for this array
@ -3070,7 +3128,7 @@ struct Dqn_OSTimedBlock
// Dump the timing block via Dqn_Log
#define DQN_OS_TIMED_BLOCK_DUMP \
DQN_ASSERT_MSG(timings_size_ < sizeof(timings_) / sizeof(timings_[0]), \
DQN_ASSERTF(timings_size_ < sizeof(timings_) / sizeof(timings_[0]), \
"Timings array indexed out-of-bounds, use a bigger size"); \
for (int timings_index_ = 0; timings_index_ < (timings_size_ - 1); timings_index_++) { \
Dqn_OSTimedBlock t1 = timings_[timings_index_ + 0]; \
@ -4006,10 +4064,9 @@ DQN_FSTRING8_API
Dqn_String8 Dqn_FString8_ToString8(DQN_FSTRING8 const *string)
{
Dqn_String8 result = {};
if (!string)
if (!string || string->size <= 0)
return result;
DQN_HARD_ASSERT(string->size >= 0);
result.data = DQN_CAST(char *)string->data;
result.size = string->size;
return result;
@ -4831,11 +4888,11 @@ DQN_API Dqn_uint Dqn_TicketMutex_MakeTicket(Dqn_TicketMutex *mutex)
DQN_API void Dqn_TicketMutex_BeginTicket(Dqn_TicketMutex const *mutex, Dqn_uint ticket)
{
DQN_ASSERT_MSG(mutex->serving <= ticket,
"Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, "
"mutex->serving = %u",
ticket,
mutex->serving);
DQN_ASSERTF(mutex->serving <= ticket,
"Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, "
"mutex->serving = %u",
ticket,
mutex->serving);
while (ticket != mutex->serving) {
// NOTE: Use spinlock intrinsic
_mm_pause();
@ -6177,7 +6234,7 @@ DQN_API void Dqn_Library_LeakTraceAdd(Dqn_CallSite call_site, void *ptr, Dqn_usi
// TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it already existed.
Dqn_LeakTrace *trace = Dqn_DSMap_Find(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr));
if (trace) {
DQN_HARD_ASSERT_MSG(trace->freed, "This pointer is already in the leak tracker, however it"
DQN_HARD_ASSERTF(trace->freed, "This pointer is already in the leak tracker, however it"
" has not been freed yet. Somehow this pointer has been"
" given to the allocation table and has not being marked"
" freed with an equivalent call to LeakTraceMarkFree()"
@ -6206,12 +6263,12 @@ DQN_API void Dqn_Library_LeakTraceMarkFree(Dqn_CallSite call_site, void *ptr)
Dqn_TicketMutex_Begin(&dqn_library.alloc_table_mutex);
Dqn_LeakTrace *trace = Dqn_DSMap_Find(&dqn_library.alloc_table, Dqn_DSMap_KeyU64(DQN_CAST(uintptr_t)ptr));
DQN_HARD_ASSERT_MSG(trace, "Allocated pointer can not be removed as it does not exist in the"
DQN_HARD_ASSERTF(trace, "Allocated pointer can not be removed as it does not exist in the"
" allocation table. When this memory was allocated, the pointer was"
" not added to the allocation table [ptr=%p]",
ptr);
DQN_HARD_ASSERT_MSG(!trace->freed,
DQN_HARD_ASSERTF(!trace->freed,
"Double free detected, pointer was previously allocated at [ptr=%p, %_$$d, file=\"%.*s:%u\", function=\"%.*s\"]",
ptr,
trace->size,
@ -6328,22 +6385,28 @@ DQN_API void Dqn_Arena_CommitFromBlock(Dqn_ArenaBlock *block, Dqn_isize size, Dq
DQN_API void *Dqn_Arena_AllocateFromBlock(Dqn_ArenaBlock *block, Dqn_isize size, uint8_t align, Dqn_ZeroMem zero_mem)
{
Dqn_isize allocation_size = size + (align - 1);
if ((block->used + allocation_size) > block->size)
return nullptr;
DQN_ASSERT(block->hwm_used <= block->commit);
DQN_ASSERT_MSG(block->commit >= block->used,
DQN_ASSERTF(block->commit >= block->used,
"Internal error: Committed size must always be greater than the used size [commit=%_$$zd, used=%_$$zd]",
block->commit, block->used);
void *unaligned_result = DQN_CAST(char *)block->memory + block->used;
void *result = DQN_CAST(void *)Dqn_PowerOfTwoRoundUp(DQN_CAST(uintptr_t)unaligned_result, align);
// NOTE: Calculate how much we need to pad the next pointer to divvy out
// (only if it is unaligned)
uintptr_t next_ptr = DQN_CAST(uintptr_t)block->memory + block->used;
Dqn_isize align_offset = 0;
if (next_ptr & (align - 1))
align_offset = (align - (next_ptr & (align - 1)));
Dqn_isize allocation_size = size + align_offset;
if ((block->used + allocation_size) > block->size)
return nullptr;
void *result = DQN_CAST(char *)next_ptr + align_offset;
if (zero_mem == Dqn_ZeroMem_Yes) {
// NOTE: Newly commit pages are always 0-ed out, we only need to
// memset the memory that was used before from the arena.
// memset the memory that are being reused.
Dqn_isize const reused_bytes = DQN_MIN(block->hwm_used - block->used, allocation_size);
DQN_MEMSET(unaligned_result, DQN_MEMSET_BYTE, reused_bytes);
DQN_MEMSET(DQN_CAST(void *)next_ptr, DQN_MEMSET_BYTE, reused_bytes);
}
// NOTE: Ensure requested bytes are backed by physical pages from the OS
@ -6356,9 +6419,9 @@ DQN_API void *Dqn_Arena_AllocateFromBlock(Dqn_ArenaBlock *block, Dqn_isize size,
block->used += allocation_size;
block->hwm_used = DQN_MAX(block->hwm_used, block->used);
DQN_ASSERT_MSG(block->used <= block->commit, "Internal error: Committed size must be greater than used size [used=%_$$zd, commit=%_$$zd]", block->used, block->commit);
DQN_ASSERT_MSG(block->commit <= block->size, "Internal error: Allocation exceeded block capacity [commit=%_$$zd, size=%_$$zd]", block->commit, block->size);
DQN_ASSERT_MSG(((DQN_CAST(uintptr_t)result) & (align - 1)) == 0, "Internal error: Pointer alignment failed [address=%p, align=%x]", result, align);
DQN_ASSERTF(block->used <= block->commit, "Internal error: Committed size must be greater than used size [used=%_$$zd, commit=%_$$zd]", block->used, block->commit);
DQN_ASSERTF(block->commit <= block->size, "Internal error: Allocation exceeded block capacity [commit=%_$$zd, size=%_$$zd]", block->commit, block->size);
DQN_ASSERTF(((DQN_CAST(uintptr_t)result) & (align - 1)) == 0, "Internal error: Pointer alignment failed [address=%p, align=%x]", result, align);
return result;
}
@ -6588,7 +6651,7 @@ DQN_API Dqn_ArenaBlock *Dqn_Arena_Grow_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena
DQN_API void *Dqn_Arena_Allocate_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_isize size, uint8_t align, Dqn_ZeroMem zero_mem)
{
DQN_ASSERT_MSG((align & (align - 1)) == 0, "Power of two alignment required");
DQN_ASSERTF((align & (align - 1)) == 0, "Power of two alignment required");
Dqn_isize allocation_size = size + (align - 1);
while (!arena->curr ||
(arena->curr->flags & Dqn_ArenaBlockFlags_Private) ||
@ -6869,9 +6932,9 @@ DQN_API Dqn_M4 Dqn_M4_Transpose(Dqn_M4 mat)
DQN_API Dqn_M4 Dqn_M4_Rotate(Dqn_V3 axis01, Dqn_f32 radians)
{
DQN_ASSERT_MSG(DQN_ABS(Dqn_V3Length(axis01) - 1.f) <= 0.01f,
"Rotation axis must be normalised, length = %f",
Dqn_V3Length(axis01));
DQN_ASSERTF(DQN_ABS(Dqn_V3Length(axis01) - 1.f) <= 0.01f,
"Rotation axis must be normalised, length = %f",
Dqn_V3Length(axis01));
Dqn_f32 sin = DQN_SINF(radians);
Dqn_f32 cos = DQN_COSF(radians);
@ -7554,7 +7617,7 @@ DQN_API bool Dqn_Char_IsHex(char ch)
DQN_API uint8_t Dqn_Char_HexToU8(char ch)
{
DQN_ASSERT_MSG(Dqn_Char_IsHex(ch), "Hex character not valid '%c'", ch);
DQN_ASSERTF(Dqn_Char_IsHex(ch), "Hex character not valid '%c'", ch);
uint8_t result = 0;
if (ch >= 'a' && ch <= 'f')
@ -7824,7 +7887,7 @@ DQN_API Dqn_isize Dqn_Hex_CString8ToByteBuffer(char const *hex, Dqn_isize hex_si
Dqn_isize trim_size_rounded_up = trim_size + (trim_size % 2);
Dqn_isize min_buffer_size = trim_size_rounded_up / 2;
if (dest_size < min_buffer_size || trim_size <= 0) {
DQN_ASSERT_MSG(dest_size >= min_buffer_size, "Insufficient buffer size for converting hex to binary");
DQN_ASSERTF(dest_size >= min_buffer_size, "Insufficient buffer size for converting hex to binary");
return result;
}
@ -8173,7 +8236,7 @@ DQN_API Dqn_isize Dqn_Win_EXEDirW(wchar_t *buffer, Dqn_isize size)
{
wchar_t module_path[DQN_OS_WIN32_MAX_PATH];
int module_size = GetModuleFileNameW(nullptr /*module*/, module_path, DQN_ARRAY_UCOUNT(module_path));
DQN_HARD_ASSERT_MSG(GetLastError() != ERROR_INSUFFICIENT_BUFFER, "How the hell?");
DQN_HARD_ASSERTF(GetLastError() != ERROR_INSUFFICIENT_BUFFER, "How the hell?");
Dqn_isize result = 0;
for (int index = module_size - 1; !result && index >= 0; index--)
@ -8191,7 +8254,7 @@ DQN_API Dqn_String16 Dqn_Win_EXEDirWArena(Dqn_Arena *arena)
{
wchar_t dir[DQN_OS_WIN32_MAX_PATH];
Dqn_isize dir_size = Dqn_Win_EXEDirW(dir, DQN_ARRAY_ICOUNT(dir));
DQN_HARD_ASSERT_MSG(dir_size <= DQN_ARRAY_ICOUNT(dir), "How the hell?");
DQN_HARD_ASSERTF(dir_size <= DQN_ARRAY_ICOUNT(dir), "How the hell?");
Dqn_String16 result = {};
if (dir_size > 0) {
@ -8767,9 +8830,9 @@ DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size)
}
#else
DQN_ASSERT_MSG(size <= 32,
"We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix guarantees 32 "
"bytes can always be fulfilled by this system at a time");
DQN_ASSERTF(size <= 32,
"We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix guarantees 32 "
"bytes can always be fulfilled by this system at a time");
// TODO(doyle): https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c
// TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html
int read_bytes = 0;
@ -8825,7 +8888,7 @@ DQN_API Dqn_String8 Dqn_OS_EXEDir(Dqn_Allocator allocator)
// try_buf around, memcopy the byte and trash the try_buf from the
// arena. Instead we just get the size and redo the call one last
// time after this "calculate" step.
DQN_ASSERT_MSG(bytes_written < try_size, "bytes_written can never be greater than the try size, function writes at most try_size");
DQN_ASSERTF(bytes_written < try_size, "bytes_written can never be greater than the try size, function writes at most try_size");
required_size_wo_null_terminator = bytes_written;
for (int index_of_last_slash = bytes_written;
@ -8948,7 +9011,7 @@ DQN_API uint64_t Dqn_OS_PerfCounterNow()
LARGE_INTEGER integer = {};
int qpc_result = QueryPerformanceCounter(&integer);
(void)qpc_result;
DQN_ASSERT_MSG(qpc_result, "MSDN says this can only fail when running on a version older than Windows XP");
DQN_ASSERTF(qpc_result, "MSDN says this can only fail when running on a version older than Windows XP");
result = integer.QuadPart;
#else
struct timespec ts;
@ -9540,9 +9603,8 @@ DQN_API Dqn_FsFile Dqn_Fs_OpenFile(Dqn_String8 path, Dqn_FsFileOpen open_mode, u
unsigned long access_mode = 0;
if (access & Dqn_FsFileAccess_AppendOnly) {
DQN_ASSERT_MSG(
(access & ~Dqn_FsFileAccess_AppendOnly) == 0,
"Append can only be applied exclusively to the file, other access modes not permitted");
DQN_ASSERTF((access & ~Dqn_FsFileAccess_AppendOnly) == 0,
"Append can only be applied exclusively to the file, other access modes not permitted");
access_mode = FILE_APPEND_DATA;
} else {
if (access & Dqn_FsFileAccess_Read)
@ -9762,9 +9824,11 @@ DQN_API uint32_t Dqn_Thread_GetID()
DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext_(DQN_LEAK_TRACE_FUNCTION_NO_COMMA)
{
thread_local Dqn_ThreadContext result = {};
if (!result.init) {
result.init = true;
DQN_ASSERTF(dqn_library.lib_init, "Library must be initialised by calling Dqn_Library_Init(nullptr)");
// NOTE: Setup permanent arena
Dqn_ArenaCatalog *catalog = &dqn_library.arena_catalog;

Binary file not shown.

View File

@ -9,6 +9,15 @@
*/
#if defined(DQN_TEST_WITH_MAIN)
#if defined(_MSC_VER) && !defined(__clang__)
// NOTE: C-strings declared in a ternary cause global-buffer-overflow in
// MSVC2022.
// stb_sprintf assumes c-string literals are 4 byte aligned which is always
// true, however, reading past the end of a string whose size is not a multiple
// of 4 is UB causing ASAN to complain.
#define STBSP__ASAN __declspec(no_sanitize_address)
#endif
#define DQN_IMPLEMENTATION
#include "dqn.h"
#endif
@ -1362,81 +1371,134 @@ Dqn_Tester TestTicketMutex()
Dqn_Tester TestVArray()
{
Dqn_Tester test = {};
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_VArray<uint32_t> array = Dqn_VArray_InitByteSize<uint32_t>(scratch.arena, DQN_KILOBYTES(64));
DQN_TESTER_GROUP(test, "Dqn_VArray") {
DQN_TESTER_TEST("Test adding an array of items to the array") {
uint32_t array_literal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
Dqn_VArray_Add<uint32_t>(&array, array_literal, DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
{
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_VArray<uint32_t> array = Dqn_VArray_InitByteSize<uint32_t>(scratch.arena, DQN_KILOBYTES(64));
DQN_TESTER_TEST("Test stable erase, 1 item, the '2' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, 1 /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test adding an array of items to the array") {
uint32_t array_literal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
Dqn_VArray_Add<uint32_t>(&array, array_literal, DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test unstable erase, 1 item, the '1' value from the array") {
Dqn_VArray_EraseRange(&array, 1 /*begin_index*/, 1 /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test stable erase, 1 item, the '2' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, 1 /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
Dqn_VArrayErase erase_enums[] = {Dqn_VArrayErase_Stable, Dqn_VArrayErase_Unstable};
DQN_TESTER_TEST("Test un/stable erase, OOB") {
for (Dqn_VArrayErase erase : erase_enums) {
DQN_TESTER_TEST("Test unstable erase, 1 item, the '1' value from the array") {
Dqn_VArray_EraseRange(&array, 1 /*begin_index*/, 1 /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
Dqn_VArray_EraseRange(&array, DQN_ARRAY_UCOUNT(array_literal) /*begin_index*/, DQN_ARRAY_UCOUNT(array_literal) + 100 /*count*/, erase);
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
Dqn_VArrayErase erase_enums[] = {Dqn_VArrayErase_Stable, Dqn_VArrayErase_Unstable};
DQN_TESTER_TEST("Test un/stable erase, OOB") {
for (Dqn_VArrayErase erase : erase_enums) {
uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
Dqn_VArray_EraseRange(&array, DQN_ARRAY_UCOUNT(array_literal) /*begin_index*/, DQN_ARRAY_UCOUNT(array_literal) + 100 /*count*/, erase);
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
}
DQN_TESTER_TEST("Test flipped begin/end index stable erase, 2 items, the '15, 3' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test flipped begin/end index unstable erase, 2 items, the '4, 5' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10, 11, 12};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test stable erase range, 2+1 (oob) item, the '13, 14, +1 OOB' value from the array") {
Dqn_VArray_EraseRange(&array, 8 /*begin_index*/, 3 /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test unstable erase range, 3+1 (oob) item, the '11, 12, +1 OOB' value from the array") {
Dqn_VArray_EraseRange(&array, 6 /*begin_index*/, 3 /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {0, 13, 14, 6, 7, 8};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test stable erase -overflow OOB, erasing the '0, 13' value from the array") {
Dqn_VArray_EraseRange(&array, 1 /*begin_index*/, -DQN_ISIZE_MAX /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {14, 6, 7, 8};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Test unstable erase +overflow OOB, erasing the '7, 8' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, DQN_ISIZE_MAX /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {14, 6};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
}
DQN_TESTER_TEST("Test flipped begin/end index stable erase, 2 items, the '15, 3' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
DQN_TESTER_TEST("Array of unaligned objects are contiguously laid out in memory") {
// NOTE: Since we allocate from a virtual memory block, each time
// we request memory from the block we can demand some alignment
// on the returned pointer from the memory block. If there's
// additional alignment done in that function then we can no
// longer access the items in the array contiguously leading to
// confusing memory "corruption" errors.
//
// This test makes sure that the unaligned objects are allocated
// from the memory block (and hence the array) contiguously
// when the size of the object is not aligned with the required
// alignment of the object.
DQN_TESTER_TEST("Test flipped begin/end index unstable erase, 2 items, the '4, 5' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10, 11, 12};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 4324) // warning C4324: 'TestVArray::UnalignedObject': structure was padded due to alignment specifier
struct alignas(8) UnalignedObject {
char data[511];
};
#pragma warning(pop)
#endif // _MSC_VER
DQN_TESTER_TEST("Test stable erase range, 2+1 (oob) item, the '13, 14, +1 OOB' value from the array") {
Dqn_VArray_EraseRange(&array, 8 /*begin_index*/, 3 /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr);
Dqn_VArray<UnalignedObject> array = Dqn_VArray_InitByteSize<UnalignedObject>(scratch.arena, DQN_KILOBYTES(64));
DQN_TESTER_TEST("Test unstable erase range, 3+1 (oob) item, the '11, 12, +1 OOB' value from the array") {
Dqn_VArray_EraseRange(&array, 6 /*begin_index*/, 3 /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {0, 13, 14, 6, 7, 8};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
// NOTE: Verify that the items returned from the data array are
// contiguous in memory.
UnalignedObject *make_item_a = Dqn_VArray_Make(&array, 1, Dqn_ZeroMem_Yes);
UnalignedObject *make_item_b = Dqn_VArray_Make(&array, 1, Dqn_ZeroMem_Yes);
DQN_MEMSET(make_item_a->data, 'a', sizeof(make_item_a->data));
DQN_MEMSET(make_item_b->data, 'b', sizeof(make_item_b->data));
DQN_TESTER_ASSERT(&test, (uintptr_t)make_item_b == (uintptr_t)(make_item_a + 1));
DQN_TESTER_TEST("Test stable erase, negative overflow OOB items, erasing the '0, 13' value from the array") {
Dqn_VArray_EraseRange(&array, 1 /*begin_index*/, -DQN_ISIZE_MAX /*count*/, Dqn_VArrayErase_Stable);
uint32_t array_literal[] = {14, 6, 7, 8};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
}
// NOTE: Verify that accessing the items from the data array yield
// the same object.
DQN_TESTER_ASSERT(&test, array.size == 2);
UnalignedObject *data_item_a = array.data + 0;
UnalignedObject *data_item_b = array.data + 1;
DQN_TESTER_ASSERT(&test, (uintptr_t)data_item_b == (uintptr_t)(data_item_a + 1));
DQN_TESTER_ASSERT(&test, (uintptr_t)data_item_b == (uintptr_t)(make_item_a + 1));
DQN_TESTER_ASSERT(&test, (uintptr_t)data_item_b == (uintptr_t)make_item_b);
DQN_TESTER_TEST("Test unstable erase, positive overflow OOB items, erasing the '0, 13' value from the array") {
Dqn_VArray_EraseRange(&array, 2 /*begin_index*/, DQN_ISIZE_MAX /*count*/, Dqn_VArrayErase_Unstable);
uint32_t array_literal[] = {14, 6};
DQN_TESTER_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_TESTER_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
for (int i = 0; i < sizeof(data_item_a->data); i++) {
DQN_TESTER_ASSERT(&test, data_item_a->data[i] == 'a');
}
for (int i = 0; i < sizeof(data_item_b->data); i++) {
DQN_TESTER_ASSERT(&test, data_item_b->data[i] == 'b');
}
}
}
return test;
@ -1505,6 +1567,7 @@ Dqn_Tester TestWin()
void TestRunSuite()
{
Dqn_Library_Init(nullptr);
Dqn_Tester tests[]
{
TestArena(),

View File

@ -1,2 +1,3 @@
# Dqn
Personal utility library that provides allocator aware data structures, custom memory allocators and various miscellaneous helpers.
Personal utility library that provides allocator aware data structures, custom
memory allocators and various miscellaneous helpers.