Add SSO for strings, memStack quick initialiser

This commit is contained in:
Doyle Thai 2018-01-19 21:52:34 +11:00
parent feeec0c610
commit 398ca0bc96
2 changed files with 176 additions and 95 deletions

258
dqn.h
View File

@ -152,6 +152,14 @@ typedef float f32;
#define DQN_MIN(a, b) ((a) < (b) ? (a) : (b))
#define DQN_SWAP(type, a, b) do { type tmp = a; a = b; b = tmp; } while(0)
// NOTE: Directives don't get replaced if there's a stringify or paste (# or ##) so TOKEN_COMBINE2 is needed
// to let directives get expanded (i.e. __COUNTER__), then we can combine.
#define DQN_TOKEN_COMBINE(x, y) x ## y
#define DQN_TOKEN_COMBINE2(x, y) DQN_TOKEN_COMBINE(x, y)
// Produce a unique name with prefix and counter. i.e. data => data1
#define DQN_UNIQUE_NAME(prefix) DQN_TOKEN_COMBINE2(prefix, __COUNTER__)
// #DqnAssert API
// =================================================================================================
// DQN_ASSERT() & DQN_ASSERT_MSG() will hard break the program but it can be
@ -188,9 +196,9 @@ DQN_FILE_SCOPE void *DqnMem_Calloc (const size_t size);
DQN_FILE_SCOPE void DqnMem_Clear (void *const memory, const u8 clearValue, const size_t size);
DQN_FILE_SCOPE void *DqnMem_Realloc(void *memory, const size_t newSize);
DQN_FILE_SCOPE void DqnMem_Free (void *memory);
DQN_FILE_SCOPE void DqnMem_Copy (void *const dest, void *const src, const i64 numBytesToCopy);
DQN_FILE_SCOPE void *DqnMem_Set (void *const dest, u8 value, const i64 numBytesToSet);
DQN_FILE_SCOPE void *DqnMem_Set64 (void *const dest, u8 value, const i64 numBytesToSet);
DQN_FILE_SCOPE void DqnMem_Copy (void *const dest, void const *const src, i64 const numBytesToCopy);
DQN_FILE_SCOPE void *DqnMem_Set (void *const dest, u8 const value, i64 const numBytesToSet);
DQN_FILE_SCOPE void *DqnMem_Set64 (void *const dest, u8 const value, i64 const numBytesToSet);
// #DqnMemAPI API
// =================================================================================================
@ -288,8 +296,19 @@ public:
// BeginTempRegion and EndTempRegion functions. Specifically freeing
// individual items is typically not generalisable in this scheme.
// Usage: DqnMemStack example = DQN_MEM_STACK_FIXED_MEM(example, DQN_KILOBYTES(512), 4);
#define DQN_MEM_STACK_FIXED_MEM(memStack, sizeInBytes, align) \
DQN_MEM_STACK_FIXED_MEM_INTERNAL(memStack, sizeInBytes, align, DQN_UNIQUE_NAME(memory_))
#define DQN_MEM_STACK_FIXED_MEM_INTERNAL(memStack, sizeInBytes, align, uniqueName) \
{}; \
u8 uniqueName[sizeInBytes]; \
memStack.InitWithFixedMem(uniqueName, sizeInBytes, align);
struct DqnMemStack
{
static const i32 MINIMUM_BLOCK_SIZE = DQN_KILOBYTE(32);
enum Flag
{
IsNotExpandable = (1 << 0),
@ -390,7 +409,7 @@ struct DqnMemStack
TempRegionGuard_ TempRegionGuard (); // RAII Temp Region
// Keep allocations that have occurred since Begin(). End() does not need to be called anymore.
void TempRegionKeepChanges(TempRegion region);
void TempRegionKeepChanges(TempRegion region);
// Advanced
// =============================================================================================
@ -410,28 +429,25 @@ struct DqnMemStack
// String allocates +1 extra byte for the null-terminator to be completely compatible with
// C style strings, but this is not reflected in the capacity or len, and is hidden from the user.
// NOTE: Pasting tokens doesn't do anything with preprocessor directives IF the preprocessor finds
// a stringify or paste operation (# or ##) so we need this level of indirection.
#define DQN_TOKEN_COMBINE(x, y) x ## y
#define DQN_TOKEN_COMBINE2(x, y) DQN_TOKEN_COMBINE(x, y)
// Usage: DqnString example = DQN_STRING_LITERAL(example, "hello world");
#define DQN_STRING_LITERAL(srcVariable, literal) \
DQN_STRING_LITERAL_INTERNAL1(srcVariable, literal, DQN_TOKEN_COMBINE2(dqnstring_, __COUNTER__))
DQN_STRING_LITERAL_INTERNAL(srcVariable, literal, DQN_UNIQUE_NAME(dqnstring_))
class DqnString
{
char sso[128];
public:
char *str;
i32 len; // Len of the string in bytes not including null-terminator
i32 max; // The maximum capacity not including space for null-terminator.
DqnMemAPI memAPI;
char *str;
i32 len; // Len of the string in bytes not including null-terminator
i32 max; // The maximum capacity not including space for null-terminator.
// Initialisation API
// =============================================================================================
// return: False if (size < 0) or (memAPI allocation failed).
bool InitSize (const i32 size, DqnMemStack *const stack);
bool InitSize (const i32 size, DqnMemAPI api = DqnMemAPI::HeapAllocator());
bool InitSize (i32 size, DqnMemStack *const stack);
bool InitSize (i32 size, DqnMemAPI api = DqnMemAPI::HeapAllocator());
// return: False if arguments are invalid.
bool InitFixedMem (char *const memory, const i32 sizeInBytes);
@ -465,6 +481,11 @@ public:
wchar_t *ToWChar(DqnMemAPI api = DqnMemAPI::HeapAllocator());
};
struct DqnSmartString : public DqnString
{
~DqnSmartString() { this->Free(); }
};
// TODO(doyle): Remove this? I only want it to return the string if we can guarantee initialisation.
// returns: Initialised string,
DQN_FILE_SCOPE DqnString DqnString_(i32 const len, DqnMemAPI const api = DqnMemAPI::HeapAllocator());
@ -475,10 +496,7 @@ DQN_FILE_SCOPE DqnString DqnString_(DqnMemStack *const stack);
// NOTE: First level of indirection needs to turn the combined dqnstring_(guid) into a name. Otherwise
// each use of literalVarName will increment __COUNTER__
#define DQN_STRING_LITERAL_INTERNAL1(srcVariable, literal, literalVarName) \
DQN_STRING_LITERAL_INTERNAL2(srcVariable, literal, literalVarName)
#define DQN_STRING_LITERAL_INTERNAL2(srcVariable, literal, literalVarName) \
#define DQN_STRING_LITERAL_INTERNAL(srcVariable, literal, literalVarName) \
{}; \
char literalVarName[] = literal; \
srcVariable.InitLiteralNoAlloc(literalVarName, DQN_CHAR_COUNT(literalVarName))
@ -1661,9 +1679,27 @@ public:
DQN_FILE_SCOPE DqnRndPCG DqnRndPCG_(); // Uses rdtsc to create a seed
DQN_FILE_SCOPE DqnRndPCG DqnRndPCG_(u32 seed);
// #Dqn API
// #Dqn_ API
// =================================================================================================
inline bool Dqn_BitIsSet(u32 const bits, u32 const flag)
{
bool result = ((bits & flag) == flag);
return result;
}
inline u32 Dqn_BitSet(u32 const bits, u32 const flag)
{
u32 result = (bits | flag);
return result;
}
inline u32 Dqn_BitUnset(u32 const bits, u32 const flag)
{
u32 result = (bits & ~flag);
return result;
}
template <typename T>
using Dqn_QuickSortLessThanCallback = bool (*)(const T *const , const T *const);
@ -2780,7 +2816,7 @@ DQN_FILE_SCOPE void DqnMem_Free(void *memory)
if (memory) free(memory);
}
DQN_FILE_SCOPE void DqnMem_Copy(void *const dest, void *const src, const i64 numBytesToCopy)
DQN_FILE_SCOPE void DqnMem_Copy(void *const dest, void const *const src, i64 const numBytesToCopy)
{
auto *to = (u8 *)dest;
auto *from = (u8 *)src;
@ -2788,7 +2824,7 @@ DQN_FILE_SCOPE void DqnMem_Copy(void *const dest, void *const src, const i64 num
to[i] = from[i];
}
DQN_FILE_SCOPE void *DqnMem_Set(void *const dest, u8 value, const i64 numBytesToSet)
DQN_FILE_SCOPE void *DqnMem_Set(void *const dest, u8 const value, i64 const numBytesToSet)
{
auto *ptr = (u8 *)dest;
for (auto i = 0; i < numBytesToSet; i++)
@ -2797,7 +2833,7 @@ DQN_FILE_SCOPE void *DqnMem_Set(void *const dest, u8 value, const i64 numBytesTo
return dest;
}
DQN_FILE_SCOPE void *DqnMem_Set64(void *const dest, u8 value, const i64 numBytesToCopy)
DQN_FILE_SCOPE void *DqnMem_Set64(void *const dest, u8 const value, i64 const numBytesToCopy)
{
#if defined(DQN_WIN32_PLATFORM)
u64 valueU64 = value;
@ -3111,6 +3147,7 @@ bool DqnMemStack::InitWithFixedSize(size_t size, bool const zeroClear, u32 const
bool DqnMemStack::Init(size_t size, bool const zeroClear, u32 const byteAlign_, DqnMemAPI memAPI_)
{
if (!this || size < 0) return false;
if (!DQN_ASSERT_MSG(!this->block, "MemStack has pre-existing block already attached"))
return false;
@ -3139,7 +3176,8 @@ void *DqnMemStack::Push(size_t size)
if (size == 0) return nullptr;
size_t alignedSize = DQN_ALIGN_POW_N(size, this->byteAlign);
if (!this->block || (this->block->used + alignedSize) > this->block->size)
size_t newUsage = this->block->used + alignedSize;
if (!this->block || newUsage > this->block->size)
{
size_t newBlockSize;
@ -3147,9 +3185,17 @@ void *DqnMemStack::Push(size_t size)
// a minimum block size? Not allocate based on the current block
// size
if (this->block)
{
#if 0
newBlockSize = DQN_MAX(alignedSize, this->block->size);
#else
newBlockSize = DQN_MAX(alignedSize, DqnMemStack::MINIMUM_BLOCK_SIZE);
#endif
}
else
{
newBlockSize = alignedSize;
}
DqnMemStack::Block *newBlock = this->AllocateCompatibleBlock(newBlockSize, true);
if (newBlock)
@ -3188,32 +3234,50 @@ void *DqnMemStack::Push(size_t size)
return result;
}
bool DqnMemStack::Pop(void *const ptr, size_t size)
FILE_SCOPE bool DqnMemStackInternal_TryPopPtrInBlock(DqnMemStack::Block *block, u32 const byteAlign,
void *const ptr, size_t size)
{
if (!this->block) return false;
u8 *currPtr = block->memory + block->used;
if (DQN_ASSERT_MSG((u8 *)ptr >= block->memory && ptr < currPtr,
"'ptr' to pop does not belong to current memStack attached block"))
if ((size_t)ptr >= (size_t)block->memory && (size_t)ptr < (size_t)currPtr)
{
size_t calcSize = (size_t)currPtr - (size_t)ptr;
size_t sizeAligned = DQN_ALIGN_POW_N(size, this->byteAlign);
auto calcSize = (size_t)currPtr - (size_t)ptr;
size_t sizeAligned = DQN_ALIGN_POW_N(size, byteAlign);
if (calcSize == sizeAligned)
{
this->block->used -= sizeAligned;
if (this->block->used == 0 && block->prevBlock)
{
bool result = this->FreeLastBlock();
return result;
}
block->used -= sizeAligned;
return true;
}
else
{
// printf("The ptr to free and its size aligned to byteAlign does not match.
// This
// ptr is not a valid address to free. We got: %d, expected: %d", calcSize,
// alignedSized);
DQN_ASSERT(DQN_INVALID_CODE_PATH);
}
}
return false;
}
bool DqnMemStack::Pop(void *const ptr, size_t size)
{
if (!this->block) return false;
if (DqnMemStackInternal_TryPopPtrInBlock(this->block, this->byteAlign, ptr, size))
{
if (this->block->used == 0 && this->block->prevBlock)
{
bool result = this->FreeLastBlock();
return result;
}
return true;
}
// printf("Ptr (%x) does not belong to the newest memory blocks", ptr);
return false;
}
void DqnMemStack::Free()
{
// NOTE(doyle): User is in charge of freeing this memory, so all we need to
@ -3247,7 +3311,7 @@ bool DqnMemStack::FreeMemBlock(DqnMemStack::Block *memBlock)
{
DqnMemStack::Block *blockToFree = *blockPtr;
(*blockPtr) = blockToFree->prevBlock;
DqnMem_Free(blockToFree);
this->memAPI.Free(blockToFree, blockToFree->size);
// No more blocks, then last block has been freed
if (!this->block) DQN_ASSERT_HARD(this->tempRegionCount == 0);
@ -5257,31 +5321,22 @@ bool DqnString::InitSize(const i32 size, DqnMemStack *const stack)
return result;
}
bool DqnString::InitSize(const i32 size, DqnMemAPI api)
bool DqnString::InitSize(i32 size, DqnMemAPI api)
{
// NOTE: CHAR_COUNT is (ARRAY_COUNT - 1) to leave the last spot as the implicit null-terminator.
DQN_ASSERT(size >= 0);
if (size < 0)
if (size < DQN_CHAR_COUNT(this->sso))
{
DqnString nullString = {};
*this = nullString;
return false;
this->str = &(this->sso[0]);
}
if (size > 0)
else
{
size_t allocSize = sizeof(*(this->str)) * (size + 1);
this->str = (char *)api.Alloc(allocSize, /*zeroClear*/false);
if (!this->str)
{
return false;
}
}
else // size == 0
{
this->str = nullptr;
if (!this->str) return false;
}
this->str[0] = 0;
this->max = size;
this->len = 0;
this->memAPI = api;
@ -5314,25 +5369,15 @@ bool DqnString::InitLiteral(char const *const cstr, i32 const lenInBytes, DqnMem
bool DqnString::InitLiteral(char const *const cstr, i32 const lenInBytes, DqnMemAPI api)
{
if (lenInBytes < 0) return false;
if (lenInBytes > 0)
if (!this->InitSize(lenInBytes, api))
{
size_t allocSize = sizeof(*(this->str)) * (lenInBytes + 1);
this->str = (char *)api.Alloc(allocSize, /*zeroClear*/ false);
if (!this->str) return false;
this->str[lenInBytes] = 0;
return false;
}
this->str[lenInBytes] = 0;
this->len = lenInBytes;
this->max = lenInBytes;
this->memAPI = api;
for (i32 i = 0; i < this->len; i++)
{
this->str[i] = cstr[i];
}
this->max = this->len;
DqnMem_Copy(this->str, cstr, lenInBytes);
return true;
}
@ -5354,20 +5399,18 @@ bool DqnString::InitLiteral(wchar_t const *const cstr, DqnMemStack *const stack)
bool DqnString::InitLiteral(wchar_t const *const cstr, DqnMemAPI api)
{
#if defined(DQN_IS_WIN32) && defined(DQN_WIN32_IMPLEMENTATION)
i32 requiredLen = DqnWin32_WCharToUTF8(cstr, nullptr, 0);
this->len = requiredLen - 1;
size_t allocSize = sizeof(*(this->str)) * (this->len + 1);
this->str = (char *)api.Alloc(allocSize, /*zeroClear*/false);
if (!this->str) return false;
this->max = this->len;
this->memAPI = api;
i32 reqLenInclNullTerminator = DqnWin32_WCharToUTF8(cstr, nullptr, 0);
if (!this->InitSize(reqLenInclNullTerminator - 1, api))
{
return false;
}
this->len = reqLenInclNullTerminator - 1;
this->max = this->len;
i32 convertResult = DqnWin32_WCharToUTF8(cstr, this->str, this->len + 1);
DQN_ASSERT(convertResult != -1);
this->str[this->len] = 0;
return true;
#else
@ -5394,14 +5437,35 @@ bool DqnString::InitLiteralNoAlloc(char *const cstr, i32 cstrLen)
}
this->max = this->len;
return true;
}
bool DqnString::Expand(const i32 newMax)
{
if (!this->memAPI.IsValid()) return false;
if (newMax < this->max) return true;
if (newMax < this->max)
{
return true;
}
if (!this->memAPI.IsValid())
{
return false;
}
if (newMax < DQN_CHAR_COUNT(this->sso))
{
DQN_ASSERT(this->memAPI.IsValid());
DQN_ASSERT(this->sso == this->str);
this->max = newMax;
return true;
}
bool usingSSO = (this->str == this->sso);
if (usingSSO)
{
DQN_ASSERT(newMax >= DQN_CHAR_COUNT(this->sso));
this->str = nullptr;
}
size_t allocSize = sizeof(*(this->str)) * (newMax + 1);
char *result = nullptr;
@ -5411,10 +5475,19 @@ bool DqnString::Expand(const i32 newMax)
if (result)
{
if (usingSSO)
DqnMem_Copy(result, this->sso, this->max);
this->str = (char *)result;
this->max = newMax;
return true;
}
else
{
// Restore the pointer to the SSO to return to the original state from before this call.
if (usingSSO)
this->str = this->sso;
}
return false;
}
@ -5435,8 +5508,8 @@ DQN_FILE_SCOPE bool DqnStringInternal_Append(DqnString *const str, char const *c
if (!result) return false;
}
// Append
for (i32 i = 0; i < bytesToCopy; i++) str->str[str->len + i] = cstr[i];
char *dest = str->str + str->len;
DqnMem_Copy(dest, cstr, bytesToCopy);
str->len = totalLen;
str->str[totalLen] = 0;
@ -5512,10 +5585,19 @@ void DqnString::Clear()
void DqnString::Free()
{
if (this->str && this->memAPI.IsValid())
if (this->str)
{
this->memAPI.Free(this->str, this->len);
this->str = nullptr;
if (this->str == this->sso)
{
this->sso[0] = '\0';
this->str = nullptr;
}
else if (this->memAPI.IsValid())
{
this->memAPI.Free(this->str, this->len);
this->str = nullptr;
}
this->len = 0;
this->max = 0;
}

View File

@ -1573,11 +1573,10 @@ void DqnMemStack_Test()
DqnMemStack::Block *blockA = stack.block;
// Alocate B
size_t sizeB = (size_t)(allocSize * 2.0f);
size_t sizeB = DqnMemStack::MINIMUM_BLOCK_SIZE;
void *resultB = stack.Push(sizeB);
DQN_ASSERT(((intptr_t)resultB % ALIGNMENT) == 0);
DQN_ASSERT(stack.block && stack.block->memory);
DQN_ASSERT(stack.block->size == DQN_KILOBYTE(2));
// Since we alignment the pointers we return they can be within 0-3
// bytes of what we expect and since this is in a new block as well used
@ -1598,7 +1597,7 @@ void DqnMemStack_Test()
// Check temp regions work
DqnMemStack::TempRegion tempBuffer = stack.TempRegionBegin();
size_t sizeC = 1024 + 1;
size_t sizeC = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE + 1, ALIGNMENT);
void *resultC = stack.Push(sizeC);
DQN_ASSERT(((intptr_t)resultC % ALIGNMENT) == 0);
DQN_ASSERT(stack.block != blockB && stack.block != blockA);
@ -1607,7 +1606,7 @@ void DqnMemStack_Test()
DQN_ASSERT(stack.byteAlign == ALIGNMENT);
// NOTE: Allocation should be aligned to 4 byte boundary
DQN_ASSERT(tempBuffer.stack->block->size == 2048);
DQN_ASSERT(tempBuffer.stack->block->size == sizeC);
u8 *ptrC = (u8 *)resultC;
for (u32 i = 0; i < sizeC; i++)
ptrC[i] = 3;
@ -1769,19 +1768,19 @@ void DqnMemStack_Test()
}
// Force it to allocate three new blocks and write out data to each
size_t secondBlockSize = DQN_KILOBYTE(2);
size_t secondBlockSize = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE, stack.byteAlign);
u8 *second = (u8 *)stack.Push(secondBlockSize);
DqnMemStack::Block *secondBlock = stack.block;
for (u32 i = 0; i < secondBlockSize; i++)
second[i] = 'd';
size_t thirdBlockSize = DQN_KILOBYTE(3);
size_t thirdBlockSize = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE, stack.byteAlign);
u8 *third = (u8 *)stack.Push(thirdBlockSize);
DqnMemStack::Block *thirdBlock = stack.block;
for (u32 i = 0; i < thirdBlockSize; i++)
third[i] = 'e';
size_t fourthBlockSize = DQN_KILOBYTE(4);
size_t fourthBlockSize = DQN_ALIGN_POW_N(DqnMemStack::MINIMUM_BLOCK_SIZE, stack.byteAlign);
u8 *fourth = (u8 *)stack.Push(fourthBlockSize);
DqnMemStack::Block *fourthBlock = stack.block;
for (u32 i = 0; i < fourthBlockSize; i++)