Struct RegionAllocator

This struct provides an interface to the RegionAllocator functionality and enforces scoped deletion. A new instance using the thread-local RegionAllocatorStack instance is created using the global newRegionAllocator function. A new instance using an explicitly created RegionAllocatorStack is created using RegionAllocatorStack.newRegionAllocator.

struct RegionAllocator ;

Each instance has reference semantics in that any copy will allocate from the same memory. When the last copy of an instance goes out of scope, all memory allocated via that instance is freed. Only the most recently created still-existing RegionAllocator using a given RegionAllocatorStack may be used to allocate and free memory at any given time. Deviations from this model result in a RegionAllocatorException being thrown.

An uninitialized RegionAllocator (for example RegionAllocator.init) has semantics similar to a null pointer. It may be assigned to or passed to a function. However, any attempt to call a method will result in a RegionAllocatorException being thrown.

Properties

NameTypeDescription
gcScanned[get] boolReturns whether the RegionAllocatorStack used by this RegionAllocator instance is scanned by the garbage collector.
segmentSize[get] ulongReturns the segment size of the RegionAllocatorStack used by this RegionAllocator.
segmentSlack[get] ulongReturns the maximum number of bytes that may be allocated in the current segment.

Methods

NameDescription
alignBytes Returns the number of bytes to which an allocation of size nBytes is guaranteed to be aligned.
allocate Allocates nBytes bytes on the RegionAllocatorStack used by this RegionAllocator instance. The last block allocated from this RegionAllocator instance can be freed by calling RegionAllocator.free or RegionAllocator.freeLast or will be automatically freed when the last copy of this RegionAllocator instance goes out of scope.
allocSize Returns the number of bytes used to satisfy an allocation request of nBytes. Will return a value greater than or equal to nBytes to account for alignment overhead.
array Copies range to an array. The array will be located on the RegionAllocator stack if any of the following conditions apply:
free Checks that ptr is a pointer to the block that would be freed by freeLast then calls freeLast. Throws a RegionAllocatorException if the pointer does not point to the block that would be freed by freeLast.
freeLast Frees the last block of memory allocated by the current RegionAllocator. Throws a RegionAllocatorException if this RegionAllocator is not the most recently created still-existing RegionAllocator using its RegionAllocatorStack instance.
newArray Allocates an array of type T. T may be a multidimensional array. In this case sizes may be specified for any number of dimensions from 1 to the number in T.
opAssign
resize Attempts to resize a previously allocated block of memory in place. This is possible only if ptr points to the beginning of the last block allocated by this RegionAllocator instance and, in the case where newSize is greater than the old size, there is additional space in the segment that ptr was allocated from. Additionally, blocks larger than this RegionAllocator's segment size cannot be grown or shrunk.
uninitializedArray Same as newArray, except skips initialization of elements for performance reasons.

Examples

void foo() {
    auto alloc = newRegionAllocator();
    auto ptr1 = bar(alloc);
    auto ptr2 = alloc.allocate(42);

    // The last copy of the RegionAllocator object used to allocate ptr1
    // and ptr2 is going out of scope here.  The memory pointed to by
    // both ptr1 and ptr2 will be freed.
}

void* bar(RegionAllocator alloc) {
    auto ret = alloc.allocate(42);

    auto alloc2 = newRegionAllocator();
    auto ptr3 = alloc2.allocate(42);

    // ptr3 was allocated using alloc2, which is going out of scope.
    // Its memory will therefore be freed.  ret was allocated using alloc.
    // A copy of this RegionAllocator is still alive in foo() after
    // bar() executes.  Therefore, ret will not be freed on returning and
    // is still valid after bar() returns.

    return ret;
}

void* thisIsSafe() {
    // This is safe because the two RegionAllocator objects being used
    // are using two different RegionAllocatorStack objects.
    auto alloc = newRegionAllocator();
    auto ptr1 = alloc.allocate(42);

    auto stack = RegionAllocatorStack(1_048_576, GCScan.no);
    auto alloc2 = stack.newRegionAllocator();

    auto ptr2 = alloc2.allocate(42);
    auto ptr3 = alloc.allocate(42);
}

void* dontDoThis() {
    auto alloc = newRegionAllocator();
    auto ptr1 = alloc.allocate(42);
    auto alloc2 = newRegionAllocator();

    // Error:  Allocating from a RegionAllocator instance other than the
    // most recently created one that's still alive from a given stack.
    auto ptr = alloc.allocate(42);
}

void uninitialized() {
    RegionAllocator alloc;
    auto ptr = alloc.allocate(42);  // Error:  alloc is not initialized.
    auto alloc2 = alloc;  // Ok.  Both alloc, alloc2 are uninitialized.

    alloc2 = newRegionAllocator();
    auto ptr2 = alloc2.allocate(42);  // Ok.
    auto ptr3 = alloc.allocate(42);  // Error:  alloc is still uninitialized.

    alloc = alloc2;
    auto ptr4 = alloc.allocate(42);  // Ok.
}

Note

Allocations larger than this.segmentSize are handled as a special case and fall back to allocating directly from the C heap. These large allocations are freed as if they were allocated on a RegionAllocatorStack when free or freeLast is called or the last copy of a RegionAllocator instance goes out of scope. However, due to the extra bookkeeping required, destroying a region (as happens when the last copy of a RegionAllocator instance goes out of scope) will require time linear instead of constant in the number of allocations for regions where these large allocations are present.