Skip to content

Allocator API taking context #138

Open
@pitaj

Description

@pitaj

The rust-for-linux project have their own allocator API including a custom allocator trait. I'm working on some modifications to the Allocator trait that would enable their use-cases. Essentially, what they need is a way to pass some flags at each (possible) allocation. For instance:

v.push(1, GFP_KERNEL)?;

Context as associated type on Allocator

Add an associated type for additional context passed into (re)allocating functions.

pub unsafe trait Allocator {
    type Ctx: Copy;

    fn allocate(&self, context: Self::Ctx, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;

    fn allocate_zeroed(&self, context: Self::Ctx, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { ... }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);

    unsafe fn grow(
        &self,
        context: Self::Ctx,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Result<NonNull<[u8]>, AllocError> { ... }
    unsafe fn grow_zeroed(
        &self,
        context: Self::Ctx,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Result<NonNull<[u8]>, AllocError> { ... }
    unsafe fn shrink(
        &self,
        context: Self::Ctx,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Result<NonNull<[u8]>, AllocError> { ... }

    fn by_ref(&self) -> &Self where Self: Sized { self }
}

// Support storing the context alongside the allocator
unsafe impl<A: Allocator> Allocator for (A, A::Ctx) {
    type Ctx = ();

    fn allocate(&self, _context: (), layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
        self.0.allocate(self.1, layout)
    }

    ...
}

Then for each heap container, we would have a copy of each (re)allocating function that takes a generic context:

pub struct Vec<T, A = Global> { ... }

// Normal `push` without context
impl<T, A: Allocator<Ctx = ()>> Vec<T, A> {
    pub fn push(&mut self, elem: T) { ... }
}
// `cpush` with context
impl<T, A: Allocator> Vec<T, A> {
    pub fn cpush(&mut self, elem: T, context: A::Ctx) { ... }
}

rust-for-linux folks would have to use cpush

Could even use #![feature(default_associated_types)] to make Ctx default to (). Though since the allocator API is unstable, this isn't necessary.

Variant: Ctx as a trait generic

You could make Ctx a type parameter of the trait instead:

pub unsafe trait Allocator<Ctx> { ... }

But I fail to see a case where an allocator would want to support multiple types of context.

This would also require that every heap type wanting to generically support allocators taking context would need to add a PhantomData<Ctx> to their struct.

Questions

  • Should Ctx be passed by reference?
    Then we could remove the Copy bound, but people could also just set Ctx = &Something.

Problems

  1. Duplicated API surface: push, reserve, etc would all need to be duplicated with a context-taking variant

The only way to avoid this is some new language feature, such as making final () arguments optional or calculating disjointness based on associated types.

  1. Restricted API surface: Extend, etc can only be implemented for containers where Ctx = ()

This could possibly be mitigated by providing ways to bundle or split up the allocator and context, for instance:

impl<T, A, Ctx> Vec<T, A>
where
    A: Allocator<Ctx = Ctx>,
    (A, Ctx): Allocator<Ctx = ()>,
{
    pub fn to_bundled_context(self, context: Ctx) -> Vec<T, (A, Ctx)>;
}
impl<T, A, Ctx> Vec<T, (A, Ctx)>
where
    A: Allocator<Ctx = Ctx>,
    (A, Ctx): Allocator<Ctx = ()>,
{
    pub fn to_split_context(self) -> (Vec<T, A>, Ctx);
}

// Usage
fn takes_extend<E: Extend<u32>>(e: &mut E);

let mut v = v.to_bundled_context(context);
takes_extend(&mut v);
let (mut v, context) = v.to_split_context();

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions