Zone Memory

Zone memory allocator and containers.

Overview

AsmJit uses zone memory allocation (also known as Arena allocation) to allocate most of the data it uses. It's a fast allocator that allows AsmJit to allocate a lot of small data structures fast and without malloc() overhead. Since code generators and all related classes are usually short-lived this approach decreases memory usage and fragmentation as arena-based allocators always allocate larger blocks of memory, which are then split into smaller chunks.

Another advantage of zone memory allocation is that since the whole library uses this strategy it's very easy to deallocate everything that a particular instance is holding by simply releasing the memory the allocator holds. This improves destruction time of such objects as there is no destruction at all. Long-lived objects just reset its data in destructor or in their reset() member function for a future reuse. For this purpose all containers in AsmJit are also zone allocated.

Zone Allocation

  • Zone - Incremental zone memory allocator with minimum features. It can only allocate memory without the possibility to return it back to the allocator.
  • ZoneTmp - A temporary Zone with some initial static storage. If the allocation requests fit the static storage allocated then there will be no dynamic memory allocation during the lifetime of ZoneTmp, otherwise it would act as Zone with one preallocated block on the stack.
  • ZoneAllocator - A wrapper of Zone that provides the capability of returning memory to the allocator. Such memory is stored in a pool for later reuse.

Zone Allocated Containers

Using Zone Allocated Containers

The most common data structure exposed by AsmJit is ZoneVector. It's very similar to std::vector, but the implementation doesn't use exceptions and uses the mentioned ZoneAllocator for performance reasons. You don't have to worry about allocations as you should not need to add items to AsmJit's data structures directly as there should be API for all required operations.

The following APIs in CodeHolder returns ZoneVector reference:

using namespace asmjit;
void example(CodeHolder& code) {
// Contains all emitters attached to CodeHolder.
const ZoneVector<BaseEmitter*>& emitters = code.emitters();
// Contains all section entries managed by CodeHolder.
const ZoneVector<Section*>& sections = code.sections();
// Contains all label entries managed by CodeHolder.
const ZoneVector<LabelEntry*>& labelEntries = code.labelEntries();
// Contains all relocation entries managed by CodeHolder.
const ZoneVector<RelocEntry*>& relocEntries = code.relocEntries();
}

ZoneVector has overloaded array access operator to make it possible to access its elements through operator[]. Some standard functions like ZoneVector::empty(), ZoneVector::size(), and ZoneVector::data() are provided as well. Vectors are also iterable through a range-based for loop:

using namespace asmjit;
void example(CodeHolder& code) {
for (LabelEntry* le : code.labelEntries()) {
printf("Label #%u {Bound=%s Offset=%llu}",
le->id(),
le->isBound() ? "true" : "false",
(unsigned long long)le->offset());
}
}

Design Considerations

Zone-allocated containers do not store the allocator within the container. This decision was made to reduce the footprint of such containers as AsmJit tooling, especially Compiler's register allocation, may use many instances of such containers to perform code analysis and register allocation.

For example to append an item into a ZoneVector it's required to pass the allocator as the first argument, so it can be used in case that the vector needs a reallocation. Such function also returns an error, which must be propagated to the caller.

using namespace asmjit
Error example(ZoneAllocator* allocator) {
// Unfortunately, allocator must be provided to all functions that mutate
// the vector. However, AsmJit users should never need to do this as all
// manipulation should be done through public API, which takes care of
// that.
for (int i = 0; i < 100; i++) {
ASMJIT_PROPAGATE(vector.append(allocator, i));
}
// By default vector's destructor doesn't release anything as it knows
// that its content is zone allocated. However, \ref ZoneVector::release
// can be used to explicitly release the vector data to the allocator if
// necessary
vector.release(allocator);
}

Containers like ZoneVector also provide a functionality to reserve a certain number of items before any items are added to it. This approach is used internally in most places as it allows to prepare space for data that will be added to some container before the data itself was created.

using namespace asmjit
Error example(ZoneAllocator* allocator) {
ASMJIT_PROPAGATE(vector.willGrow(100));
for (int i = 0; i < 100; i++) {
// Cannot fail.
vector.appendUnsafe(allocator, i);
}
vector.release(allocator);
}

Classes