diff --git a/src/asmjit/base/codegen.cpp b/src/asmjit/base/codegen.cpp index 548e16e..1445a7a 100644 --- a/src/asmjit/base/codegen.cpp +++ b/src/asmjit/base/codegen.cpp @@ -29,7 +29,7 @@ CodeGen::CodeGen(Runtime* runtime) : _features(static_cast(IntUtil::mask(kCodeGenOptimizedAlign))), _error(kErrorOk), _options(0), - _baseZone(16384 - sizeof(Zone::Chunk) - kMemAllocOverhead) {} + _baseZone(16384 - kZoneOverhead) {} CodeGen::~CodeGen() { if (_errorHandler != NULL) diff --git a/src/asmjit/base/compiler.cpp b/src/asmjit/base/compiler.cpp index 0ea674c..68c57b6 100644 --- a/src/asmjit/base/compiler.cpp +++ b/src/asmjit/base/compiler.cpp @@ -44,9 +44,9 @@ BaseCompiler::BaseCompiler(Runtime* runtime) : _lastNode(NULL), _cursor(NULL), _func(NULL), - _varZone(4096 - kMemAllocOverhead), - _stringZone(4096 - kMemAllocOverhead), - _localConstZone(4096 - kMemAllocOverhead), + _varZone(4096 - kZoneOverhead), + _stringZone(4096 - kZoneOverhead), + _localConstZone(4096 - kZoneOverhead), _localConstPool(&_localConstZone), _globalConstPool(&_baseZone) {} diff --git a/src/asmjit/base/context.cpp b/src/asmjit/base/context.cpp index 5828d65..7cf48d4 100644 --- a/src/asmjit/base/context.cpp +++ b/src/asmjit/base/context.cpp @@ -22,7 +22,7 @@ namespace asmjit { BaseContext::BaseContext(BaseCompiler* compiler) : _compiler(compiler), - _baseZone(8192 - sizeof(Zone::Chunk) - kMemAllocOverhead) { + _baseZone(8192 - kZoneOverhead) { BaseContext::reset(); } diff --git a/src/asmjit/base/vmem.cpp b/src/asmjit/base/vmem.cpp index b3d62a6..bbfd03c 100644 --- a/src/asmjit/base/vmem.cpp +++ b/src/asmjit/base/vmem.cpp @@ -29,32 +29,39 @@ // efficient. There are several goals I decided to write implementation myself. // // Goals: -// - We need usually to allocate blocks of 64 bytes long and more. -// - Alignment of allocated blocks is large - 32 bytes or 64 bytes. -// - Keep memory manager information outside allocated virtual memory pages -// (these pages allows execution of code). -// - Keep implementation small. // -// I think that implementation is not small and probably not too much readable, -// so there is small know how. +// - Granularity of allocated blocks is different than granularity for a typical +// C malloc. It is at least 64-bytes so Assembler/Compiler can guarantee the +// alignment required. Alignment requirements can grow in the future, but at +// the moment 64 bytes is safe (we may jump to 128 bytes if necessary or make +// it configurable). // -// - Implementation is based on bit arrays and binary trees. Bit arrays -// contains information about allocated and unused blocks of memory. Each -// block size describes MemNode::density member. Count of blocks are -// stored in MemNode::blocks member. For example if density is 64 and -// count of blocks is 20, memory node contains 64*20 bytes of memory and -// smallest possible allocation (and also alignment) is 64 bytes. So density -// describes also memory alignment. Binary trees are used to enable fast -// lookup into all addresses allocated by memory manager instance. This is -// used mainly in VMemPrivate::release(). +// - Keep memory manager information outside of the allocated virtual memory +// pages, because these pages allow executing of machine code and there should +// not be data required to keep track of these blocks. Another reason is that +// some environments (i.e. iOS) allow to generate and run JIT code, but this +// code has to be set to [Executable, but not Writable]. +// +// - Keep implementation simple and easy to follow. +// +// Implementation is based on bit arrays and binary trees. Bit arrays contain +// information related to allocated and unused blocks of memory. The size of +// a block is described by `MemNode::density`. Count of blocks is stored in +// `MemNode::blocks`. For example if density is 64 and count of blocks is 20, +// memory node contains 64*20 bytes of memory and smallest possible allocation +// (and also alignment) is 64 bytes. So density is also related to memory +// alignment. Binary trees (RB) are used to enable fast lookup into all addresses +// allocated by memory manager instance. This is used mainly by `VMemPrivate::release()`. +// +// Bit array looks like this (empty = unused, X = used) - Size of block 64: // -// Bit array looks like this (empty = unused, X = used) - Size of block 64 // ------------------------------------------------------------------------- // | |X|X| | | | | |X|X|X|X|X|X| | | | | | | | | | | | |X| | | | |X|X|X| | | // ------------------------------------------------------------------------- -// Bits array shows that there are 12 allocated blocks of 64 bytes, so total -// allocated size is 768 bytes. Maximum count of continuous blocks is 12 -// (see largest gap). +// (Maximum continuous block) +// +// These bits show that there are 12 allocated blocks (X) of 64 bytes, so total +// size allocated is 768 bytes. Maximum count of continuous memory is 12 * 64. namespace asmjit { diff --git a/src/asmjit/base/zone.cpp b/src/asmjit/base/zone.cpp index 76bbded..8b18175 100644 --- a/src/asmjit/base/zone.cpp +++ b/src/asmjit/base/zone.cpp @@ -19,18 +19,18 @@ namespace asmjit { -//! Zero width chunk used when Zone doesn't have any memory allocated. -static const Zone::Chunk Zone_zeroChunk = { - NULL, 0, 0, { 0 } +//! Zero size block used by `Zone` that doesn't have any memory allocated. +static const Zone::Block Zone_zeroBlock = { + NULL, NULL, NULL, NULL, { 0 } }; // ============================================================================ // [asmjit::Zone - Construction / Destruction] // ============================================================================ -Zone::Zone(size_t chunkSize) { - _chunks = const_cast(&Zone_zeroChunk); - _chunkSize = chunkSize; +Zone::Zone(size_t blockSize) { + _blocks = const_cast(&Zone_zeroBlock); + _blockSize = blockSize; } Zone::~Zone() { @@ -42,35 +42,33 @@ Zone::~Zone() { // ============================================================================ void Zone::clear() { - Chunk* cur = _chunks; + Block* cur = _blocks; - if (cur == &Zone_zeroChunk) + // Can't be altered. + if (cur == &Zone_zeroBlock) return; - cur = cur->prev; - while (cur != NULL) { - Chunk* prev = cur->prev; - ::free(cur); - cur = prev; - } + while (cur->prev != NULL) + cur = cur->prev; - _chunks->pos = 0; - _chunks->prev = NULL; + cur->pos = cur->data; + _blocks = cur; } void Zone::reset() { - Chunk* cur = _chunks; + Block* cur = _blocks; - if (cur == &Zone_zeroChunk) + // Can't be altered. + if (cur == &Zone_zeroBlock) return; - while (cur != NULL) { - Chunk* prev = cur->prev; + do { + Block* prev = cur->prev; ::free(cur); cur = prev; - } + } while (cur != NULL); - _chunks = const_cast(&Zone_zeroChunk); + _blocks = const_cast(&Zone_zeroBlock); } // ============================================================================ @@ -78,38 +76,58 @@ void Zone::reset() { // ============================================================================ void* Zone::_alloc(size_t size) { - Chunk* cur = _chunks; - ASMJIT_ASSERT(cur == &Zone_zeroChunk || cur->getRemainingSize() < size); + Block* curBlock = _blocks; + size_t blockSize = IntUtil::iMax(_blockSize, size); - size_t chunkSize = _chunkSize; - if (chunkSize < size) - chunkSize = size; + // The `_alloc()` method can only be called if there is not enough space + // in the current block, see `alloc()` implementation for more details. + ASMJIT_ASSERT(curBlock == &Zone_zeroBlock || curBlock->getRemainingSize() < size); - cur = static_cast(::malloc(sizeof(Chunk) - sizeof(void*) + chunkSize)); - if (cur == NULL) + // If the `Zone` has been cleared the current block doesn't have to be the + // last one. Check if there is a block that can be used instead of allocating + // a new one. If there is a `next` block it's completely unused, we don't have + // to check for remaining bytes. + Block* next = curBlock->next; + if (next != NULL && next->getBlockSize() >= size) { + next->pos = next->data + size; + _blocks = next; + return static_cast(next->data); + } + + // Prevent arithmetic overflow. + if (blockSize > ~static_cast(0) - sizeof(Block)) return NULL; - cur->prev = NULL; - cur->pos = 0; - cur->size = chunkSize; + Block* newBlock = static_cast(::malloc(sizeof(Block) - sizeof(void*) + blockSize)); + if (newBlock == NULL) + return NULL; - if (_chunks != &Zone_zeroChunk) - cur->prev = _chunks; - _chunks = cur; + newBlock->pos = newBlock->data + size; + newBlock->end = newBlock->data + blockSize; + newBlock->prev = NULL; + newBlock->next = NULL; - uint8_t* p = cur->data + cur->pos; - cur->pos += size; + if (curBlock != &Zone_zeroBlock) { + newBlock->prev = curBlock; + curBlock->next = newBlock; - ASMJIT_ASSERT(cur->pos <= cur->size); - return (void*)p; + // Does only happen if there is a next block, but the requested memory + // can't fit into it. In this case a new buffer is allocated and inserted + // between the current block and the next one. + if (next != NULL) { + newBlock->next = next; + next->prev = newBlock; + } + } + + _blocks = newBlock; + return static_cast(newBlock->data); } -void* Zone::_allocZeroed(size_t size) { - void* p = _alloc(size); - +void* Zone::allocZeroed(size_t size) { + void* p = alloc(size); if (p != NULL) ::memset(p, 0, size); - return p; } @@ -132,7 +150,7 @@ char* Zone::sdup(const char* str) { if (str == NULL) return NULL; - size_t len = strlen(str); + size_t len = ::strlen(str); if (len == 0) return NULL; @@ -153,17 +171,16 @@ char* Zone::sformat(const char* fmt, ...) { if (fmt == NULL) return NULL; - char buf[256]; + char buf[512]; size_t len; va_list ap; va_start(ap, fmt); - len = vsnprintf(buf, 256, fmt, ap); - va_end(ap); - len = IntUtil::iMin(len, 255); + len = vsnprintf(buf, ASMJIT_ARRAY_SIZE(buf) - 1, fmt, ap); buf[len++] = 0; + va_end(ap); return static_cast(dup(buf, len)); } diff --git a/src/asmjit/base/zone.h b/src/asmjit/base/zone.h index 8d9fafd..f78be2d 100644 --- a/src/asmjit/base/zone.h +++ b/src/asmjit/base/zone.h @@ -23,28 +23,53 @@ namespace asmjit { // [asmjit::Zone] // ============================================================================ -//! Fast incremental memory allocator. +//! Zone memory allocator. //! -//! Memory allocator designed to allocate small objects that will be invalidated -//! (freed) all at once. +//! Zone is an incremental memory allocator that allocates memory by simply +//! incrementing a pointer. It allocates blocks of memory by using standard +//! C library `malloc/free`, but divides these blocks into smaller segments +//! requirested by calling `Zone::alloc()` and friends. +//! +//! Zone memory allocators are designed to allocate data of short lifetime. The +//! data used by `Assembler` and `Compiler` has a very short lifetime, thus, is +//! allocated by `Zone`. The advantage is that `Zone` can free all of the data +//! allocated at once by calling `clear()` or `reset()`. struct Zone { // -------------------------------------------------------------------------- - // [Chunk] + // [Block] // -------------------------------------------------------------------------- //! \internal //! - //! One allocated chunk of memory. - struct Chunk { - //! Get count of remaining (unused) bytes in chunk. - ASMJIT_INLINE size_t getRemainingSize() const { return size - pos; } + //! A single block of memory. + struct Block { + // ------------------------------------------------------------------------ + // [Accessors] + // ------------------------------------------------------------------------ - //! Link to previous chunk. - Chunk* prev; - //! Position in this chunk. - size_t pos; - //! Size of this chunk (in bytes). - size_t size; + //! Get the size of the block. + ASMJIT_INLINE size_t getBlockSize() const { + return (size_t)(end - data); + } + + //! Get count of remaining bytes in the block. + ASMJIT_INLINE size_t getRemainingSize() const { + return (size_t)(end - pos); + } + + // ------------------------------------------------------------------------ + // [Members] + // ------------------------------------------------------------------------ + + //! Current data pointer (pointer to the first available byte). + uint8_t* pos; + //! End data pointer (pointer to the first invalid byte). + uint8_t* end; + + //! Link to the previous block. + Block* prev; + //! Link to the next block. + Block* next; //! Data. uint8_t data[sizeof(void*)]; @@ -56,38 +81,45 @@ struct Zone { //! Create a new instance of `Zone` allocator. //! - //! The `chunkSize` parameter describes the size of the chunk. If `alloc()` - //! requires more memory than `chunkSize` then a bigger chunk will be - //! allocated, however `chunkSize` will not be changed. - ASMJIT_API Zone(size_t chunkSize); - - //! Destroy `Zone` instance. + //! The `blockSize` parameter describes the default size of the block. If the + //! `size` parameter passed to `alloc()` is greater than the default size + //! `Zone` will allocate and use a larger block, but it will not change the + //! default `blockSize`. //! - //! Destructor released all chunks allocated by `Zone`. The `reset()` member - //! function does the same without actually destroying `Zone` object itself. + //! It's not required, but it's good practice to set `blockSize` to a + //! reasonable value that depends on the usage of `Zone`. Greater block sizes + //! are generally safer and performs better than unreasonably low values. + ASMJIT_API Zone(size_t blockSize); + + //! Destroy the `Zone` instance. + //! + //! This will destroy the `Zone` instance and release all blocks of memory + //! allocated by it. It performs implicit `reset()`. ASMJIT_API ~Zone(); // -------------------------------------------------------------------------- // [Clear / Reset] // -------------------------------------------------------------------------- - //! Reset the `Zone` releasing all chunks allocated. + //! Clear the `Zone`, but keep all blocks allocated so they can be reused. //! - //! Calling `clear()` will release all chunks allocated by `Zone` except the - //! first one that will be reused if needed. + //! This is the preferred way of invalidating objects allocated by `Zone`. ASMJIT_API void clear(); - //! Reset the `Zone` releasing all chunks allocated. + //! Reset the `Zone` releasing all blocks allocated. //! - //! Calling `reset()` will release all chunks allocated by `Zone`. + //! Calling `reset()` does complete cleanup, it releases all blocks allocated + //! by `Zone`. ASMJIT_API void reset(); // -------------------------------------------------------------------------- // [Accessors] // -------------------------------------------------------------------------- - //! Get chunk size. - ASMJIT_INLINE size_t getChunkSize() const { return _chunkSize; } + //! Get the default block size. + ASMJIT_INLINE size_t getBlockSize() const { + return _blockSize; + } // -------------------------------------------------------------------------- // [Alloc] @@ -95,75 +127,70 @@ struct Zone { //! Allocate `size` bytes of memory. //! - //! Pointer allocated by this way will be valid until `Zone` object is - //! destroyed. To create class by this way use placement `new` and `delete` - //! operators: + //! Pointer returned is valid until the `Zone` instance is destroyed or reset + //! by calling `clear()` or `reset()`. If you plan to make an instance of C++ + //! from the given pointer use placement `new` and `delete` operators: //! //! ~~~ - //! // Example of simple class allocation. - //! using namespace asmjit + //! using namespace asmjit; //! - //! // Your class. - //! class Object { - //! // members... - //! }; + //! class SomeObject { ... }; //! - //! // Your function - //! void f() { - //! // Create zone object with chunk size of 65536 bytes. - //! Zone zone(65536); + //! // Create Zone with default block size of 65536 bytes. + //! Zone zone(65536); //! - //! // Create your objects using zone object allocating, for example: - //! Object* obj = new(zone.alloc(sizeof(YourClass))) Object(); - //! - //! // ... lifetime of your objects ... - //! - //! // Destroy your objects: - //! obj->~Object(); - //! - //! // Zone destructor will free all memory allocated through it, you can - //! // call `zone.reset()` if you wan't to reuse current `Zone`. + //! // Create your objects using zone object allocating, for example: + //! Object* obj = static_cast( zone.alloc(sizeof(SomeClass)) ); + // + //! if (obj == NULL) { + //! // Handle out of memory error. //! } + //! + //! // To instantiate class placement `new` and `delete` operators can be used. + //! new(obj) Object(); + //! + //! // ... lifetime of your objects ... + //! + //! // To destroy the instance (if required). + //! obj->~Object(); + //! + //! // Reset of destroy `Zone`. + //! zone.reset(); //! ~~~ ASMJIT_INLINE void* alloc(size_t size) { - Chunk* cur = _chunks; + Block* cur = _blocks; - if (cur->getRemainingSize() < size) + uint8_t* ptr = cur->pos; + size_t remainingBytes = (size_t)(cur->end - ptr); + + if (remainingBytes < size) return _alloc(size); - uint8_t* p = cur->data + cur->pos; cur->pos += size; - ASMJIT_ASSERT(cur->pos <= cur->size); + ASMJIT_ASSERT(cur->pos <= cur->end); - return (void*)p; + return (void*)ptr; } - //! Like `alloc()`, but the return is casted to `T*`. + //! Allocate `size` bytes of zeroed memory. + //! + //! See \ref alloc() for more details. + ASMJIT_API void* allocZeroed(size_t size); + + //! Like `alloc()`, but the return pointer is casted to `T*`. template ASMJIT_INLINE T* allocT(size_t size = sizeof(T)) { return static_cast(alloc(size)); } - //! \internal - ASMJIT_API void* _alloc(size_t size); - - //! Allocate `size` bytes of zeroed memory. - ASMJIT_INLINE void* allocZeroed(size_t size) { - Chunk* cur = _chunks; - - if (cur->getRemainingSize() < size) - return _allocZeroed(size); - - uint8_t* p = cur->data + cur->pos; - cur->pos += size; - ASMJIT_ASSERT(cur->pos <= cur->size); - - ::memset(p, 0, size); - return (void*)p; + //! Like `allocZeroed()`, but the return pointer is casted to `T*`. + template + ASMJIT_INLINE T* allocZeroedT(size_t size = sizeof(T)) { + return static_cast(allocZeroed(size)); } //! \internal - ASMJIT_API void* _allocZeroed(size_t size); + ASMJIT_API void* _alloc(size_t size); //! Helper to duplicate data. ASMJIT_API void* dup(const void* data, size_t size); @@ -178,10 +205,15 @@ struct Zone { // [Members] // -------------------------------------------------------------------------- - //! Last allocated chunk of memory. - Chunk* _chunks; - //! Default chunk size. - size_t _chunkSize; + //! The current block. + Block* _blocks; + //! Default block size. + size_t _blockSize; +}; + +enum { + //! Zone allocator overhead. + kZoneOverhead = static_cast(sizeof(Zone::Block) - sizeof(void*)) + kMemAllocOverhead }; //! \}