Skip to content

Box<T, A: BuildAlloc> #12

Open
Open
@scottjmaddox

Description

@scottjmaddox

The current push to associate an allocator to boxes (among other types) is targeting something like Box<T, A: Alloc>. This limits the design space of custom allocators, though, because it forces one of two approaches for accessing the Alloc:

  1. Use a single static global state (or pointer or reference to the state).
  2. Embed a pointer or reference to the state inside every box.

There are many kinds of allocators that these limitations rule out. For example, perhaps you want to have multiple independent "Arena" or "Region" allocators that each allocate from a dedicated slab of memory. Since you want to have multiple independent allocators, you cannot (efficiently) use approach 1. So you're forced to use approach 2, which is extremely inefficient, requiring a whole extra pointer to be stored in each Box.

One approach for avoiding this limitation would be something like the following:

pub struct Box<T: ?Sized, A: BuildAlloc>(Unique<T>, A);

pub trait BuildAlloc {
    type Alloc: Alloc;

    /// Build an `Alloc` given a `ptr` pointer to memory with the given
    /// `layout` that was allocated by the associated allocator.
    ///
    /// # Safety
    ///
    /// This function is unsafe because undefined behavior can result
    /// if the caller does not ensure all of the following:
    ///
    /// * `ptr` must denote a block of memory currently allocated via
    ///   this allocator,
    ///
    /// * `layout` must *fit* that block of memory,
    ///
    /// * In addition to fitting the block of memory `layout`, the
    ///   alignment of the `layout` must match the alignment used
    ///   to allocate that block of memory.
    unsafe fn build_alloc(&mut self, ptr: NonNull<u8>, layout: Layout) -> Self::Alloc;
}

Note that BuildAlloc provides the method build_alloc, which accepts &mut self and a pointer and layout for memory that was allocated by the associated allocator, and returns an Alloc implementation. This gives many options for locating the allocator's state and generating an Alloc. Approach 1 (global state) is of course still possible, and so is approach 2 (embed a pointer in every box). But this also makes approach 3 possible:

  1. Calculate a pointer to the allocator state from a pointer to heap memory allocated by that allocator.

This makes many more clever and efficient kinds of allocators possible, such as an allocator that allocates all 8-byte allocations from aligned 1 MiB blocks, and stashes a pointer to its own state at the beginning of each of those 1 MiB blocks. Such an allocator can implement BuildAlloc to check the size of the allocated memory and if it is 8-bytes then mask off the lowest 10 bits from ptr to get a (1 MiB aligned) pointer to the allocator's state, from which it can construct an Alloc.

To clarify, BuildAlloc makes it possible to calculate a pointer to the allocator's state from the Box itself:

impl<T: ?Sized, A: BuildAlloc>  Box<T, A> {
    fn get_alloc(&self) -> A::Alloc {
        self.1.build_alloc(
            NonNull::from(self.0).cast::<u8>(),
            Layout::for_value(self.as_ref())
        )
    }
}

Since the Box provides a pointer to memory allocated by the allocator, the allocator can use its control of the pointers it returns and the memory around those pointers to efficiently locate its own state and generate an Alloc. This makes many more kinds of clever and efficient allocators possible.

Relevant comments from PR #58457: Associate an allocator to boxes:

Edited 2019-05-04: Rename AllocFrom to BuildAlloc for consistency with BuildHasher.
Edited 2019-05-07: Adjust build_alloc signature to match dealloc.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions