mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-18 13:04:36 +03:00
Reworked Zone memory allocator a bit.
This commit is contained in:
@@ -29,7 +29,7 @@ CodeGen::CodeGen(Runtime* runtime) :
|
||||
_features(static_cast<uint8_t>(IntUtil::mask(kCodeGenOptimizedAlign))),
|
||||
_error(kErrorOk),
|
||||
_options(0),
|
||||
_baseZone(16384 - sizeof(Zone::Chunk) - kMemAllocOverhead) {}
|
||||
_baseZone(16384 - kZoneOverhead) {}
|
||||
|
||||
CodeGen::~CodeGen() {
|
||||
if (_errorHandler != NULL)
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace asmjit {
|
||||
|
||||
BaseContext::BaseContext(BaseCompiler* compiler) :
|
||||
_compiler(compiler),
|
||||
_baseZone(8192 - sizeof(Zone::Chunk) - kMemAllocOverhead) {
|
||||
_baseZone(8192 - kZoneOverhead) {
|
||||
|
||||
BaseContext::reset();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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::Chunk*>(&Zone_zeroChunk);
|
||||
_chunkSize = chunkSize;
|
||||
Zone::Zone(size_t blockSize) {
|
||||
_blocks = const_cast<Zone::Block*>(&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;
|
||||
|
||||
while (cur->prev != NULL)
|
||||
cur = cur->prev;
|
||||
while (cur != NULL) {
|
||||
Chunk* prev = cur->prev;
|
||||
::free(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::Chunk*>(&Zone_zeroChunk);
|
||||
_blocks = const_cast<Zone::Block*>(&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<size_t>(_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<Chunk*>(::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<void*>(next->data);
|
||||
}
|
||||
|
||||
// Prevent arithmetic overflow.
|
||||
if (blockSize > ~static_cast<size_t>(0) - sizeof(Block))
|
||||
return NULL;
|
||||
|
||||
cur->prev = NULL;
|
||||
cur->pos = 0;
|
||||
cur->size = chunkSize;
|
||||
Block* newBlock = static_cast<Block*>(::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<void*>(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<size_t>(len, 255);
|
||||
len = vsnprintf(buf, ASMJIT_ARRAY_SIZE(buf) - 1, fmt, ap);
|
||||
buf[len++] = 0;
|
||||
|
||||
va_end(ap);
|
||||
return static_cast<char*>(dup(buf, len));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
//! // 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();
|
||||
//! Object* obj = static_cast<Object*>( 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 ...
|
||||
//!
|
||||
//! // Destroy your objects:
|
||||
//! // To destroy the instance (if required).
|
||||
//! obj->~Object();
|
||||
//!
|
||||
//! // Zone destructor will free all memory allocated through it, you can
|
||||
//! // call `zone.reset()` if you wan't to reuse current `Zone`.
|
||||
//! }
|
||||
//! // 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<typename T>
|
||||
ASMJIT_INLINE T* allocT(size_t size = sizeof(T)) {
|
||||
return static_cast<T*>(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<typename T>
|
||||
ASMJIT_INLINE T* allocZeroedT(size_t size = sizeof(T)) {
|
||||
return static_cast<T*>(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<int>(sizeof(Zone::Block) - sizeof(void*)) + kMemAllocOverhead
|
||||
};
|
||||
|
||||
//! \}
|
||||
|
||||
Reference in New Issue
Block a user