Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sub-arenas that can be reset in a LIFO order, without resetting the whole bump arena #105

Open
fitzgen opened this issue Apr 7, 2021 · 4 comments

Comments

@fitzgen
Copy link
Owner

fitzgen commented Apr 7, 2021

We should make this work out so that safety is guaranteed by the types. So a sub-arena should exclusively borrow its parent (whether that is the top-level arena or another sub-arena). And the sub-arena shouldn't expose its parent's lifetime, so that references allocated through the sub-arena cannot be used after we drop the sub-arena, and therefore it is safe for the sub-arena's Drop impl to reset the bump pointer to the point it was at when the sub-arena was constructed.

Unclear how to add support for backing bumpalo::collections::Vec et al with a sub-arena and even whether this is desirable vs just waiting for custom allocators to stabilize, now that there is movement there.

cc @cfallin

@zakarumych
Copy link
Contributor

You can look at https://github.com/zakarumych/scoped-arena for an implementation of this.

@zakarumych
Copy link
Contributor

zakarumych commented Jan 19, 2022

API looks like this

let mut scope = Scope::new(); // creates root scope backed by `Global` allocator.
let mut proxy = scope.proxy();

{
  let scope = proxy.scope();
  let v: &mut usize = scope.to_scope(42);
  // 42 dropped here
  // memory will be reused.
}

@fitzgen
Copy link
Owner Author

fitzgen commented Jul 7, 2022

Was thinking about this a little more today, came up with the following API:

/// The backing storage for bump allocation.
pub struct Region {
    _storage: (),
}

impl Region {
    /// Create a new region to bump into.
    pub fn new() -> Self {
        Region { _storage: () }
    }

    /// Get the allocation capability for this region.
    pub fn bump(&mut self) -> Bump<'_> {
        Bump(self, None)
    }
}

/// Capability to allocate inside some region. Optionally scoped.
pub struct Bump<'a>(&'a mut Region, Option<usize>);

impl Drop for Bump<'_> {
    fn drop(&mut self) {
        if let Some(scope) = self.1 {
            drop(scope);
            // TODO: reset the bump pointer to the start of this nested Bump's
            // scope.
        }
    }
}

impl<'a> Bump<'a> {
    /// Allocate into the region.
    pub fn alloc<T>(&self, value: T) -> &'a mut T {
        Box::leak(Box::new(value))
    }

    /// Get a nested `Bump` that will reset and free all of the things allocated
    /// within it upon drop.
    pub fn scope<'b>(&'b mut self) -> Bump<'b> {
        // Remember the current bump pointer; this is the scope of the nested
        // Bump.
        let scope = 1234;

        Bump(self.0, Some(scope))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn scopes() {
        let mut region = Region::new();
        let mut bump = region.bump();

        let outer = bump.alloc(1234);
        {
            let bump = bump.scope();
            let inner = bump.alloc(5678);

            assert_eq!(*inner, 5678, "can access scoped allocations");
            assert_eq!(*outer, 1234, "can still access allocations from outer scopes");
        }
    }
}

@That3Percent
Copy link

The requirements for bumpalo are different, but you might be inspired by the use of closures in second-stack as an API to ensure correct scoping: https://docs.rs/second-stack/0.3.4/second_stack/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants