diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2018-09-12 16:27:42 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-09-12 16:27:42 -0400 |
| commit | f60135cec62c91a9d7923397fe8796d2b3eaa5cb (patch) | |
| tree | 777646cb3611bf5809dc18e120e506117e143e11 /source | |
| parent | 9a9733091cc7c9628e445313785d561deb229072 (diff) | |
Feature/memory arena (#631)
* First pass at MemoryArena.
* First pass at RandomGenerator.
* Extract TestContext into external source file.
* Fix warning on printf.
* Use enum classes for Test enums.
OutputMode -> TestOutputMode.
* First pass at FreeList unit test.
* Auto registering tests.
Improvements to RandomGenerator.
* Remove the need for unitTest headers - cos can use registering.
* Added unitTest for MemoryArena.
* Do unit tests.
* Fix typo.
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/core.vcxproj | 4 | ||||
| -rw-r--r-- | source/core/core.vcxproj.filters | 14 | ||||
| -rw-r--r-- | source/core/list.h | 9 | ||||
| -rw-r--r-- | source/core/slang-memory-arena.cpp | 244 | ||||
| -rw-r--r-- | source/core/slang-memory-arena.h | 393 | ||||
| -rw-r--r-- | source/core/slang-random-generator.cpp | 143 | ||||
| -rw-r--r-- | source/core/slang-random-generator.h | 97 | ||||
| -rw-r--r-- | source/core/slang-string-util.cpp | 48 | ||||
| -rw-r--r-- | source/core/slang-string-util.h | 14 |
9 files changed, 964 insertions, 2 deletions
diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj index 3dbfaac3f..2944ee9dd 100644 --- a/source/core/core.vcxproj +++ b/source/core/core.vcxproj @@ -185,6 +185,8 @@ <ClInclude Include="slang-free-list.h" /> <ClInclude Include="slang-io.h" /> <ClInclude Include="slang-math.h" /> + <ClInclude Include="slang-memory-arena.h" /> + <ClInclude Include="slang-random-generator.h" /> <ClInclude Include="slang-string-util.h" /> <ClInclude Include="slang-string.h" /> <ClInclude Include="smart-pointer.h" /> @@ -197,6 +199,8 @@ <ClCompile Include="platform.cpp" /> <ClCompile Include="slang-free-list.cpp" /> <ClCompile Include="slang-io.cpp" /> + <ClCompile Include="slang-memory-arena.cpp" /> + <ClCompile Include="slang-random-generator.cpp" /> <ClCompile Include="slang-string-util.cpp" /> <ClCompile Include="slang-string.cpp" /> <ClCompile Include="stream.cpp" /> diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters index 27b0fe82f..9e90aaa77 100644 --- a/source/core/core.vcxproj.filters +++ b/source/core/core.vcxproj.filters @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <Filter Include="Header Files"> @@ -75,6 +75,12 @@ <ClInclude Include="type-traits.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-memory-arena.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="slang-random-generator.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="platform.cpp"> @@ -101,6 +107,12 @@ <ClCompile Include="token-reader.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-memory-arena.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="slang-random-generator.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="core.natvis"> diff --git a/source/core/list.h b/source/core/list.h index 6df60b74b..cddcbb6c0 100644 --- a/source/core/list.h +++ b/source/core/list.h @@ -199,6 +199,15 @@ namespace Slang return buffer[_count-1]; } + void RemoveLast() + { +#ifdef _DEBUG + if (_count == 0) + throw "Index out of range."; +#endif + _count--; + } + inline void SwapWith(List<T, TAllocator> & other) { T* tmpBuffer = this->buffer; diff --git a/source/core/slang-memory-arena.cpp b/source/core/slang-memory-arena.cpp new file mode 100644 index 000000000..bdfd87602 --- /dev/null +++ b/source/core/slang-memory-arena.cpp @@ -0,0 +1,244 @@ + +#include "slang-memory-arena.h" + +namespace Slang { + +MemoryArena::MemoryArena() +{ + // Mark as invalid so any alloc call will fail + m_blockAlignment = 0; + m_blockSize = 0; + + // Set up as empty + m_blocks = nullptr; + _setCurrentBlock(nullptr); + m_blockFreeList.init(sizeof(Block), sizeof(void*), 16); +} + +MemoryArena::~MemoryArena() +{ + _deallocateBlocks(); +} + +MemoryArena::MemoryArena(size_t blockSize, size_t blockAlignment) +{ + _initialize(blockSize, blockAlignment); +} + +void MemoryArena::init(size_t blockSize, size_t blockAlignment) +{ + _deallocateBlocks(); + m_blockFreeList.reset(); + _initialize(blockSize, blockAlignment); +} + +void MemoryArena::_initialize(size_t blockSize, size_t alignment) +{ + // Alignment must be a power of 2 + assert(((alignment - 1) & alignment) == 0); + + // Must be at least sizeof(void*) in size, as that is the minimum the backing allocator will be + alignment = (alignment < kMinAlignment) ? kMinAlignment : alignment; + + // If alignment required is larger then the backing allocators then + // make larger to ensure when alignment correction takes place it will be aligned + if (alignment > kMinAlignment) + { + blockSize += alignment; + } + + m_blockSize = blockSize; + m_blockAlignment = alignment; + m_blocks = nullptr; + _setCurrentBlock(nullptr); + m_blockFreeList.init(sizeof(Block), sizeof(void*), 16); +} + +void* MemoryArena::_allocateAligned(size_t size, size_t alignment) +{ + assert(size); + // Can't be space in the current block -> so we can either place in next, or in a new block + _newCurrentBlock(size, alignment); + uint8_t* const current = m_current; + // If everything has gone to plan, must be space here... + assert(current + size <= m_end); + m_current = current + size; + return current; +} + +void MemoryArena::_deallocateBlocks() +{ + Block* currentBlock = m_blocks; + while (currentBlock) + { + // Deallocate the block + ::free(currentBlock->m_alloc); + // next block + currentBlock = currentBlock->m_next; + } + // Can deallocate all blocks to + m_blockFreeList.deallocateAll(); +} + +void MemoryArena::_setCurrentBlock(Block* block) +{ + if (block) + { + m_end = block->m_end; + m_start = block->m_start; + m_current = m_start; + } + else + { + m_start = nullptr; + m_end = nullptr; + m_current = nullptr; + } + m_currentBlock = block; +} + +MemoryArena::Block* MemoryArena::_newCurrentBlock(size_t size, size_t alignment) +{ + // Make sure init has been called (or has been set up in parameterized constructor) + assert(m_blockSize > 0); + // Alignment must be a power of 2 + assert(((alignment - 1) & alignment) == 0); + + // Alignment must at a minimum be block alignment (such if reused the constraints hold) + alignment = (alignment < m_blockAlignment) ? m_blockAlignment : alignment; + + const size_t alignMask = alignment - 1; + + // First try the next block (if there is one) + { + Block* next = m_currentBlock ? m_currentBlock->m_next : m_blocks; + if (next) + { + // Align could be done from the actual allocation start, but doing so would mean a pointer which + // didn't hit the constraint of being between start/end + // So have to align conservatively using start + uint8_t* memory = (uint8_t*)((size_t(next->m_start) + alignMask) & ~alignMask); + + // Check if can fit block in + if (memory + size <= next->m_end) + { + _setCurrentBlock(next); + return next; + } + } + } + + // The size of the block must be at least large enough to take into account alignment + size_t allocSize = (alignment <= kMinAlignment) ? size : (size + alignment); + + // The minimum block size should be at least m_blockSize + allocSize = (allocSize < m_blockSize) ? m_blockSize : allocSize; + + // Allocate block + Block* block = (Block*)m_blockFreeList.allocate(); + if (!block) + { + return nullptr; + } + // Allocate the memory + uint8_t* alloc = (uint8_t*)::malloc(allocSize); + if (!alloc) + { + m_blockFreeList.deallocate(block); + return nullptr; + } + // Do the alignment on the allocation + uint8_t* const start = (uint8_t*)((size_t(alloc) + alignMask) & ~alignMask); + + // Setup the block + block->m_alloc = alloc; + block->m_start = start; + block->m_end = alloc + allocSize; + block->m_next = nullptr; + + // Insert block into list + if (m_currentBlock) + { + // Insert after current block + block->m_next = m_currentBlock->m_next; + m_currentBlock->m_next = block; + } + else + { + // Add to start of the list of the blocks + block->m_next = m_blocks; + m_blocks = block; + } + _setCurrentBlock(block); + return block; +} + +MemoryArena::Block* MemoryArena::_findBlock(const void* alloc, Block* endBlock) const +{ + const uint8_t* ptr = (const uint8_t*)alloc; + + Block* block = m_blocks; + while (block != endBlock) + { + if (ptr >= block->m_start && ptr < block->m_end) + { + return block; + } + block = block->m_next; + } + return nullptr; +} + +MemoryArena::Block* MemoryArena::_findPreviousBlock(Block* block) +{ + Block* currentBlock = m_blocks; + while (currentBlock) + { + if (currentBlock->m_next == block) + { + return currentBlock; + } + currentBlock = currentBlock->m_next; + } + return nullptr; +} + +void MemoryArena::deallocateAll() +{ + Block** prev = &m_blocks; + Block* block = m_blocks; + + while (block) + { + if (size_t(block->m_end - block->m_alloc) > m_blockSize) + { + // Oversized block so we need to free it and remove from the list + Block* nextBlock = block->m_next; + *prev = nextBlock; + // Free the backing memory + ::free(block->m_alloc); + // Free the block + m_blockFreeList.deallocate(block); + // prev stays the same, now working on next tho + block = nextBlock; + } + else + { + // Onto next + prev = &block->m_next; + block = block->m_next; + } + } + + // Make the first current (if any) + _setCurrentBlock(m_blocks); +} + +void MemoryArena::reset() +{ + _deallocateBlocks(); + m_blocks = nullptr; + _setCurrentBlock(nullptr); +} + +} // namespace Slang diff --git a/source/core/slang-memory-arena.h b/source/core/slang-memory-arena.h new file mode 100644 index 000000000..acbac51dd --- /dev/null +++ b/source/core/slang-memory-arena.h @@ -0,0 +1,393 @@ +#ifndef SLANG_MEMORY_ARENA_H +#define SLANG_MEMORY_ARENA_H + +#include "../../slang.h" + +#include <assert.h> + +#include <stdlib.h> +#include <string.h> + +#include "slang-free-list.h" + +namespace Slang { + +/** Defines arena allocator where allocations are made very quickly, but that deallocations +can only be performed in reverse order, or with the client code knowing a previous deallocation (say with + deallocateAllFrom), automatically deallocates everything after it. + +It works by allocating large blocks and then cutting out smaller pieces as requested. If a piece of memory is +deallocated, it either MUST be in reverse allocation order OR the subsequent allocations are implicitly +deallocated too, and therefore accessing their memory is now undefined behavior. Allocations are made +contiguously from the current block. If there is no space in the current block, the +next block (which is unused) if available is checked. If that works, an allocation is made from the next block. +If not a new block is allocated that can hold at least the allocation with required alignment. + +All memory allocated can be deallocated very quickly and without a client having to track any memory. +All memory allocated will be freed on destruction - or with reset. + +A memory arena can have requests larger than the block size. When that happens they will just be allocated +from the heap. As such 'oversized blocks' are seen as unusual and potentially wasteful they are deallocated +when deallocateAll is called, whereas regular size blocks will remain allocated for fast subsequent allocation. + +It is intentional that blocks information is stored separately from the allocations that store the +user data. This is so that alignment permitting, block allocations sizes can be passed directly to underlying allocator. +For large power of 2 backing allocations this might mean a page/pages directly allocated by the OS for example. +Also means better cache coherency when traversing blocks -> as generally they will be contiguous in memory. + +Also note that allocateUnaligned can be used for slightly faster aligned allocations. All blocks allocated internally +are aligned to the blockAlignment passed to the constructor. If subsequent allocations (of any type) sizes are of that +alignment or larger then no alignment fixing is required (because allocations are contiguous) and so 'allocateUnaligned' +will return allocations of blockAlignment alignment. +*/ +class MemoryArena +{ +public: + typedef MemoryArena ThisType; + + static const size_t kMinAlignment = sizeof(void*); ///< The minimum alignment of the backing memory allocator. + + /** Determines if an allocation is consistent with an allocation from this arena. + + The test cannot say definitively if this was such an allocation, because the exact details + of each allocation is not kept. + @param alloc The start of the allocation + @param sizeInBytes The size of the allocation + @return true if allocation could have been from this Arena */ + bool isValid(const void* alloc, size_t sizeInBytes) const; + + /** Initialize the arena with specified block size and alignment + If the arena has been previously initialized will free and deallocate all memory */ + void init(size_t blockSize, size_t blockAlignment = kMinAlignment); + + /** Allocate some memory of at least size bytes without having any specific alignment. + + Can be used for slightly faster *aligned* allocations if caveats in class description are met. The + Unaligned, means the method will not enforce alignment - but a client call to allocateUnaligned can control + subsequent allocations alignments via it's size. + + @param size The size of the allocation requested (in bytes and must be > 0). + @return The allocation. Can be nullptr if backing allocator was not able to request required memory */ + void* allocate(size_t sizeInBytes); + + /** Allocate some aligned memory of at least size bytes + @param size Size of allocation wanted (must be > 0). + @param alignment Alignment of allocation - must be a power of 2. + @return The allocation (or nullptr if unable to allocate). Will be at least 'alignment' alignment or better. */ + void* allocateAligned(size_t sizeInBytes, size_t alignment); + + /** Allocate some aligned memory of at least size bytes + @param size Size of allocation wanted (must be > 0). + @param alignment Alignment of allocation - must be a power of 2. + @return The allocation (or nullptr if unable to allocate). Will be at least 'alignment' alignment or better. */ + void* allocateUnaligned(size_t sizeInBytes); + + /** Allocates a null terminated string. + @param str A null-terminated string + @return A copy of the string held on the arena */ + const char* allocateString(const char* str); + + /** Allocates a null terminated string. + @param chars Pointer to first character + @param charCount The amount of characters NOT including terminating 0. + @return A copy of the string held on the arena. */ + const char* allocateString(const char* chars, size_t numChars); + + /// Allocate an element of the specified type. Note: Constructor for type is not executed. + template <typename T> + T* allocate(); + + /// Allocate an array of a specified type. NOTE Constructor of T is NOT executed. + template <typename T> + T* allocateArray(size_t size); + + /// Allocate an array of a specified type, and copy array passed into it. + template <typename T> + T* allocateAndCopyArray(const T* src, size_t size); + + /// Allocate an array of a specified type, and zero it. + template <typename T> + T* allocateAndZeroArray(size_t size); + + /// Deallocate the last allocation. If data is not from the last allocation then the behavior is undefined. + void deallocateLast(void* data); + + /// Deallocate this allocation and all remaining after it. + void deallocateAllFrom(void* dataStart); + + /** Deallocates all allocated memory. That backing memory will generally not be released so + subsequent allocation will be fast, and from the same memory. Note though that 'oversize' blocks + will be deallocated. */ + void deallocateAll(); + + /// Resets to the initial state when constructed (and all backing memory will be deallocated) + void reset(); + /// Adjusts such that the next allocate will be at least to the block alignment. + void adjustToBlockAlignment(); + + /// Gets the block alignment that is passed at initialization otherwise 0 an invalid block alignment. + size_t getBlockAlignment() const { return m_blockAlignment; } + + /// Default Ctor + MemoryArena(); + /// Construct with block size and alignment. Block alignment must be a power of 2. + MemoryArena(size_t blockSize, size_t blockAlignment = kMinAlignment); + + /// Dtor + ~MemoryArena(); + +protected: + struct Block + { + Block* m_next; + uint8_t* m_alloc; + uint8_t* m_start; + uint8_t* m_end; + }; + + void _initialize(size_t blockSize, size_t blockAlignment); + + void* _allocateAligned(size_t size, size_t alignment); + void _deallocateBlocks(); + + void _setCurrentBlock(Block* block); + + Block* _newCurrentBlock(size_t size, size_t alignment); + Block* _findBlock(const void* alloc, Block* endBlock = nullptr) const; + Block* _findPreviousBlock(Block* block); + + uint8_t* m_start; + uint8_t* m_end; + uint8_t* m_current; + size_t m_blockSize; + size_t m_blockAlignment; + Block* m_blocks; + Block* m_currentBlock; + + FreeList m_blockFreeList; + + private: + // Disable + MemoryArena(const ThisType& rhs) = delete; + void operator=(const ThisType& rhs) = delete; +}; + +// -------------------------------------------------------------------------- +inline bool MemoryArena::isValid(const void* data, size_t size) const +{ + assert(size); + + uint8_t* ptr = (uint8_t*)data; + // Is it in current + if (ptr >= m_start && ptr + size <= m_current) + { + return true; + } + // Is it in a previous block? + Block* block = _findBlock(data, m_currentBlock); + return block && (ptr >= block->m_start && (ptr + size) <= block->m_end); +} + +// -------------------------------------------------------------------------- +SLANG_FORCE_INLINE void* MemoryArena::allocateUnaligned(size_t size) +{ + // Align with the minimum alignment + uint8_t* mem = m_current; + uint8_t* end = mem + size; + if (end <= m_end) + { + m_current = end; + return mem; + } + else + { + return _allocateAligned(size, m_blockAlignment); + } +} + +// -------------------------------------------------------------------------- +SLANG_FORCE_INLINE void* MemoryArena::allocate(size_t size) +{ + // Align with the minimum alignment + const size_t alignMask = kMinAlignment - 1; + uint8_t* mem = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask); + + if (mem + size <= m_end) + { + m_current = mem + size; + return mem; + } + else + { + return _allocateAligned(size, kMinAlignment); + } +} + +// -------------------------------------------------------------------------- +inline void* MemoryArena::allocateAligned(size_t size, size_t alignment) +{ + // Alignment must be a power of 2 + assert(((alignment - 1) & alignment) == 0); + + // Align the pointer + const size_t alignMask = alignment - 1; + uint8_t* memory = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask); + + if (memory + size <= m_end) + { + m_current = memory + size; + return memory; + } + else + { + return _allocateAligned(size, alignment); + } +} + +// -------------------------------------------------------------------------- +inline const char* MemoryArena::allocateString(const char* str) +{ + size_t size = ::strlen(str); + if (size == 0) + { + return ""; + } + char* dst = (char*)allocateUnaligned(size + 1); + ::memcpy(dst, str, size + 1); + return dst; +} + +// -------------------------------------------------------------------------- +inline const char* MemoryArena::allocateString(const char* chars, size_t charsCount) +{ + if (charsCount == 0) + { + return ""; + } + char* dst = (char*)allocateUnaligned(charsCount + 1); + ::memcpy(dst, chars, charsCount); + + // Add null-terminating zero + dst[charsCount] = 0; + return dst; +} + +// -------------------------------------------------------------------------- +template <typename T> +inline T* MemoryArena::allocate() +{ + return reinterpret_cast<T*>(allocateAligned(sizeof(T), SLANG_ALIGN_OF(T))); +} + +// -------------------------------------------------------------------------- +template <typename T> +inline T* MemoryArena::allocateArray(size_t count) +{ + return (count > 0) ? reinterpret_cast<T*>(allocateAligned(sizeof(T) * count, SLANG_ALIGN_OF(T))) : nullptr; +} + +// -------------------------------------------------------------------------- +template <typename T> +inline T* MemoryArena::allocateAndCopyArray(const T* arr, size_t size) +{ + if (size > 0) + { + const size_t totalSize = sizeof(T) * size; + void* ptr = allocateAligned(totalSize, SLANG_ALIGN_OF(T)); + ::memcpy(ptr, arr, totalSize); + return reinterpret_cast<T*>(ptr); + } + return nullptr; +} + +// --------------------------------------------------------------------------- +template <typename T> +inline T* MemoryArena::allocateAndZeroArray(size_t size) +{ + if (size > 0) + { + const size_t totalSize = sizeof(T) * size; + void* ptr = allocateAligned(totalSize, SLANG_ALIGN_OF(T)); + ::memset(ptr, 0, totalSize); + return reinterpret_cast<T*>(ptr); + } + return nullptr; +} + +// -------------------------------------------------------------------------- +inline void MemoryArena::deallocateLast(void* data) +{ + // See if it's in current block + uint8_t* ptr = (uint8_t*)data; + if (ptr >= m_start && ptr < m_current) + { + // Then just go back + m_current = ptr; + } + else + { + // Only called if not in the current block. Therefore can only be in previous + Block* prevBlock = _findPreviousBlock(m_currentBlock); + if (prevBlock == nullptr || (!(ptr >= prevBlock->m_start && ptr < prevBlock->m_end))) + { + assert(!"Allocation not found"); + return; + } + + // Make the previous block the current + _setCurrentBlock(prevBlock); + // Make the current the alloc freed + m_current = ptr; + } +} + +// -------------------------------------------------------------------------- +inline void MemoryArena::deallocateAllFrom(void* data) +{ + // See if it's in current block, and is allocated (ie < m_current) + uint8_t* ptr = (uint8_t*)data; + if (ptr >= m_start && ptr < m_current) + { + // If it's in current block, then just go back + m_current = ptr; + return; + } + + // Search all blocks prior to current block + Block* block = _findBlock(data, m_currentBlock); + assert(block); + if (!block) + { + return; + } + // Make this current block + _setCurrentBlock(block); + + // Move the pointer to the allocations position + m_current = ptr; +} + +// -------------------------------------------------------------------------- +inline void MemoryArena::adjustToBlockAlignment() +{ + const size_t alignMask = m_blockAlignment - 1; + uint8_t* ptr = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask); + + // Alignment might push beyond end of block... if so allocate a new block + // This test could be avoided if we aligned m_end, but depending on block alignment that might waste some space + if (ptr > m_end) + { + // We'll need a new block to make this alignment + _newCurrentBlock(0, m_blockAlignment); + } + else + { + // Set the position + m_current = ptr; + } + assert(size_t(m_current) & alignMask); +} + +} // namespace Slang + +#endif // SLANG_MEMORY_ARENA_H
\ No newline at end of file diff --git a/source/core/slang-random-generator.cpp b/source/core/slang-random-generator.cpp new file mode 100644 index 000000000..7e8476c30 --- /dev/null +++ b/source/core/slang-random-generator.cpp @@ -0,0 +1,143 @@ + +#include "slang-random-generator.h" + +namespace Slang { + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! RandomGenerator !!!!!!!!!!!!!!!!!!!!!!!! */ + +float RandomGenerator::nextUnitFloat32() +{ + int32_t intValue = nextInt32(); + return (intValue & 0x7fffffff) * (1.0f / 0x7fffffff); +} + +bool RandomGenerator::nextBool() +{ + uint32_t bits = uint32_t(nextInt32()); + + // Xor together all bits in each byte + bits = ((bits & 0xaaaaaaaa) >> 1) ^ (bits & 0x55555555); + bits = ((bits & 0x44444444) >> 2) ^ (bits & 0x11111111); + bits = ((bits & 0x10101010) >> 4) ^ (bits & 0x01010101); + + // In effect is the xor of all the bits of the original last byte + return ( bits & 1) != 0; +} + +int64_t RandomGenerator::nextInt64() +{ + const int32_t high = nextInt32(); + const int32_t low = nextInt32(); + + return (int64_t(high) << 32) | low; +} + +int32_t RandomGenerator::nextInt32InRange(int32_t min, int32_t max) +{ + int32_t diff = max - min; + if (diff <= 1) + { + return min; + } + + return (nextPositiveInt32() % diff) + min; +} + +int64_t RandomGenerator::nextInt64InRange(int64_t min, int64_t max) +{ + int64_t diff = max - min; + if (diff <= 1) + { + return min; + } + return (nextPositiveInt64() % diff) + min; +} + +/* static */RandomGenerator* RandomGenerator::create(int32_t seed) +{ + return new DefaultRandomGenerator(seed); +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! Mt19937RandomGenerator !!!!!!!!!!!!!!!!!!!!!!!! */ + +Mt19937RandomGenerator::Mt19937RandomGenerator() +{ + reset(21452); +} + +Mt19937RandomGenerator::Mt19937RandomGenerator(const ThisType& rhs) +{ + *this = rhs; +} + +Mt19937RandomGenerator::Mt19937RandomGenerator(int32_t seed) +{ + reset(seed); +} + +void Mt19937RandomGenerator::_generate() +{ + const uint32_t xorValue = 2567483615u; + for (int i = 0; i < kNumEntries - 1; ++i) + { + const uint32_t y = (m_mt[i] & 0x80000000) + (m_mt[i + 1] & 0x7fffffff); + + // o = (i + 397) % kNumEntries + int32_t o = i + 397; + o = (o >= kNumEntries) ? (o - kNumEntries) : o; + + m_mt[i] = m_mt[o] ^ (y >> 1); + // If y is odd + if (y & 1) + { + m_mt[i] = m_mt[i] ^ xorValue; + } + } + + // Last + { + const int i = kNumEntries - 1; + const uint32_t y = (m_mt[i] & 0x80000000) + (m_mt[0] & 0x7fffffff); + const int32_t o = ((i + 397) - kNumEntries); + + m_mt[i] = m_mt[o] ^ (y >> 1); + // If y is odd + if (y & 1) + { + m_mt[i] = m_mt[i] ^ xorValue; + } + } + + m_index = 0; +} + +void Mt19937RandomGenerator::reset(int32_t seedIn) +{ + m_index = 0; + m_mt[0] = uint32_t(seedIn); + for (int i = 1; i < kNumEntries; ++i) + { + m_mt[i] = (1812433253 * (m_mt[i - 1] ^ (m_mt[i - 1] >> 30)) + i); + } +} + +int32_t Mt19937RandomGenerator::nextInt32() +{ + if (m_index >= kNumEntries) + { + _generate(); + } + + uint32_t y = m_mt[m_index++]; + y = y ^ (y >> 11); + y = y ^ ((y << 7) & uint32_t(0x9d2c5680u)); + y = y ^ ((y << 15) & uint32_t(0xefc6000u)); + y = y ^ (y >> 18); + + return int32_t(y); +} + + + + +} // namespace Slang diff --git a/source/core/slang-random-generator.h b/source/core/slang-random-generator.h new file mode 100644 index 000000000..7b3e42288 --- /dev/null +++ b/source/core/slang-random-generator.h @@ -0,0 +1,97 @@ +#ifndef SLANG_RANDOM_GENERATOR_H +#define SLANG_RANDOM_GENERATOR_H + +#include "../../slang.h" + +#include <assert.h> + +#include <stdlib.h> +#include <string.h> + +#include "smart-pointer.h" + +namespace Slang { + +class RandomGenerator: public RefObject +{ + public: + + /// Make a copy of the generator in the same state + virtual RandomGenerator* clone() = 0; + + /// Reset with a seed + virtual void reset(int32_t seed) = 0; + /// Next int32_t random number + virtual int32_t nextInt32() = 0; + /// Next int64_t random number + virtual int64_t nextInt64(); + + /// Get a 0-1 range floating point + virtual float nextUnitFloat32(); + + /// Get the next bool + virtual bool nextBool(); + + /// Next Int32 which can only be positive + int32_t nextPositiveInt32() { return nextInt32() & 0x7fffffff; } + /// Next Int64 which can only be positive + int64_t nextPositiveInt64() { return nextInt64() & SLANG_INT64(0x7fffffffffffffff); } + + /// Returns value up to BUT NOT INCLUDING maxValue. + int32_t nextInt32UpTo(int32_t maxValue) { assert(maxValue > 0); return (maxValue <= 1) ? 0 : (nextPositiveInt32() % maxValue); } + + /// Returns value from min up to BUT NOT INCLUDING max + int32_t nextInt32InRange(int32_t min, int32_t max); + + /// Returns value up to BUT NOT INCLUDING maxValue + int64_t nextInt64UpTo(int64_t maxValue) { assert(maxValue > 0); return (maxValue <= 1) ? 0 : (nextPositiveInt64() % maxValue); } + + /// Returns value from min up to BUT NOT INCLUDING max + int64_t nextInt64InRange(int64_t min, int64_t max); + + /// Create a RandomGenerator with specified seed using default generator type + static RandomGenerator* create(int32_t seed); +}; + +/* Mersenne Twister random number generator +https://en.wikipedia.org/wiki/Mersenne_Twister +*/ +class Mt19937RandomGenerator: public RandomGenerator +{ + public: + typedef Mt19937RandomGenerator ThisType; + + enum + { + kNumEntries = 624 + }; + + Mt19937RandomGenerator* clone() SLANG_OVERRIDE { return new ThisType(*this); } + void reset(int32_t seed) SLANG_OVERRIDE; + int32_t nextInt32() SLANG_OVERRIDE; + + /// Ctor + Mt19937RandomGenerator(); + Mt19937RandomGenerator(const ThisType& rhs); + explicit Mt19937RandomGenerator(int32_t seed); + + /// Assignment + void operator=(const ThisType& rhs) + { + m_index = rhs.m_index; + ::memcpy(m_mt, rhs.m_mt, sizeof(m_mt)); + } + + protected: + void _generate(); + + uint32_t m_mt[kNumEntries]; ///< The random state vector + int m_index; ///< If set to >= kMaxEntries it means reset + +}; + +typedef Mt19937RandomGenerator DefaultRandomGenerator; + +} // namespace Slang + +#endif // SLANG_RANDOM_GENERATOR_H
\ No newline at end of file diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index c60ad9683..29f8dc0ca 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -26,4 +26,52 @@ namespace Slang { } } + +/* static */void StringUtil::append(const char* format, va_list args, StringBuilder& buf) +{ + int numChars = 0; + +#if SLANG_WINDOWS_FAMILY + numChars = _vscprintf(format, args); +#else + { + va_list argsCopy; + va_copy(argsCopy, args); + numChars = vsnprintf(nullptr, 0, format, argsCopy); + va_end(argsCopy); + } +#endif + + List<char> chars; + chars.SetSize(numChars + 1); + +#if SLANG_WINDOWS_FAMILY + vsnprintf_s(chars.Buffer(), numChars + 1, _TRUNCATE, format, args); +#else + vsnprintf(chars.Buffer(), numChars + 1, format, args); +#endif + + buf.Append(chars.Buffer(), numChars); +} + +/* static */void StringUtil::appendFormat(StringBuilder& buf, const char* format, ...) +{ + va_list args; + va_start(args, format); + append(format, args, buf); + va_end(args); +} + +/* static */String StringUtil::makeStringWithFormat(const char* format, ...) +{ + StringBuilder builder; + + va_list args; + va_start(args, format); + append(format, args, builder); + va_end(args); + + return builder; +} + } // namespace Slang diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index bae576370..fc3258490 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -4,14 +4,26 @@ #include "slang-string.h" #include "list.h" +#include <stdarg.h> + namespace Slang { struct StringUtil { + /// Split in, by specified splitChar into slices out + /// Slices contents will directly address into in, so contents will only stay valid as long as in does. static void split(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& slicesOut); + + /// Appends formatted string with args into buf + static void append(const char* format, va_list args, StringBuilder& buf); + + /// Appends the formatted string with specified trailing args + static void appendFormat(StringBuilder& buf, const char* format, ...); + + /// Create a string from the format string applying args (like sprintf) + static String makeStringWithFormat(const char* format, ...); }; } // namespace Slang - #endif // SLANG_STRING_UTIL_H |
