dqn: Document VArray, TicketMutex, fix test ASAN crash
This commit is contained in:
parent
df230539a1
commit
ee477272c4
@ -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
300
dqn.h
@ -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.
@ -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(),
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user