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

Pinned Box #228

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ harness = false
# trait on stable Rust. Enabling this feature means that `bumpalo` will
# implement its `Allocator` trait.
allocator-api2 = { version = "0.2.8", default-features = false, optional = true }
memoffset = { version = "0.9.0", optional = true }

[dev-dependencies]
quickcheck = "1.0.3"
Expand All @@ -45,6 +46,7 @@ rand = "0.8.5"
default = []
collections = []
boxed = []
pin = ["dep:memoffset"]
allocator_api = []
std = []

Expand Down
19 changes: 0 additions & 19 deletions src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,6 @@ impl<'a, T> Box<'a, T> {
Box(a.alloc(x))
}

/// Constructs a new `Pin<Box<T>>`. If `T` does not implement `Unpin`, then
/// `x` will be pinned in memory and unable to be moved.
#[inline(always)]
pub fn pin_in(x: T, a: &'a Bump) -> Pin<Box<'a, T>> {
Box(a.alloc(x)).into()
}

Comment on lines -168 to -174
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of being removed entirely, it would also be sound to restrict it to 'static, i.e. add a separate impl block for Box<'static, T> with

impl<T: ?Sized> Box<'static, T> {
    /// Constructs a new `Pin<Box<'static, T>>`. If `T` does not implement `Unpin`, then
    /// `x` will be pinned in memory and unable to be moved. This requires borrowing
    /// the `Bump` for `'static` to ensure that it can never be reset or dropped.
    #[inline(always)]
    pub fn pin_in(x: T, a: &'static Bump) -> Pin<Box<'static, T>> {
        Box(a.alloc(x)).into()
    }
}

/// Consumes the `Box`, returning the wrapped value.
///
/// # Examples
Expand Down Expand Up @@ -452,18 +445,6 @@ impl<'a, T: ?Sized + Hasher> Hasher for Box<'a, T> {
}
}

impl<'a, T: ?Sized> From<Box<'a, T>> for Pin<Box<'a, T>> {
/// Converts a `Box<T>` into a `Pin<Box<T>>`.
///
/// This conversion does not allocate on the heap and happens in place.
fn from(boxed: Box<'a, T>) -> Self {
// It's not possible to move or replace the insides of a `Pin<Box<T>>`
// when `T: !Unpin`, so it's safe to pin it directly without any
// additional requirements.
unsafe { Pin::new_unchecked(boxed) }
}
}

Comment on lines -455 to -466
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, this impl could just be restricted to 'static.

impl<T: ?Sized> From<Box<'static, T>> for Pin<Box<'static, T>> {
    /// Converts a `Box<'static, T>` into a `Pin<Box<'static, T>>`.
    ///
    /// This conversion does not allocate and happens in place. This requires borrowing
    /// the `Bump` for `'static` to ensure that it can never be reset or dropped.
    fn from(boxed: Box<'static, T>) -> Self {
        // It's not possible to move or replace the insides of a `Pin<Box<T>>`
        // when `T: !Unpin`, and because the lifetime the `Bump` is borrowed
        // for is `'static`, the memory can never be re-used, so it's safe to pin
        // it directly without any additional requirements.
        unsafe { Pin::new_unchecked(boxed) }
    }
}

impl<'a> Box<'a, dyn Any> {
#[inline]
/// Attempt to downcast the box to a concrete type.
Expand Down
110 changes: 110 additions & 0 deletions src/drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use core::{
cell::{Cell, UnsafeCell},
marker::PhantomPinned,
mem::MaybeUninit,
ptr::NonNull,
};

/// A circular doubly linked list.
#[derive(Debug, Default)]
pub struct DropList {
pub link: Link,
}

impl DropList {
/// Safety: `self` must be pinned.
#[inline]
pub unsafe fn init(&self) {
let link_ptr = Some(NonNull::from(&self.link));
self.link.prev.set(link_ptr);
self.link.next.set(link_ptr);
}

pub unsafe fn insert(&self, node: NonNull<Link>) {
insert_after(NonNull::from(&self.link), node)
}

pub unsafe fn run_drop(&self) {
let mut curr = self.link.next.get().unwrap();
let end = NonNull::from(&self.link);
while curr != end {
let entry = unsafe { curr.cast::<DropEntry<()>>().as_ref() };
unsafe {
(entry.drop_fn)(entry.data.assume_init_ref().get());
}
curr = entry.link.next.get().unwrap();
}
}
}

#[inline]
unsafe fn insert_after(tail: NonNull<Link>, node_ptr: NonNull<Link>) {
let tail = tail.as_ref();

let node = node_ptr.as_ref();
node.prev.set(Some(NonNull::from(tail)));
node.next.set(tail.next.get());

tail.next.get().unwrap().as_ref().prev.set(Some(node_ptr));
tail.next.set(Some(node_ptr));
}

#[derive(Debug, Default)]
pub struct Link {
prev: Cell<Option<NonNull<Link>>>,
next: Cell<Option<NonNull<Link>>>,
_marker: PhantomPinned,
}

impl Link {
pub unsafe fn unlink(&self) {
let Some(prev) = self.prev.take() else {
return;
};
let next = self.next.take().unwrap();
prev.as_ref().next.set(Some(next));
next.as_ref().prev.set(Some(prev));
}
}

#[derive(Debug)]
#[repr(C)]
pub struct DropEntry<T> {
link: Link,
drop_fn: unsafe fn(*mut ()),
data: MaybeUninit<UnsafeCell<T>>,
}

impl<T> DropEntry<T> {
#[inline]
pub fn new(val: T) -> Self {
Self {
link: Link::default(),
drop_fn: unsafe {
core::mem::transmute::<_, unsafe fn(*mut ())>(
core::ptr::drop_in_place::<T> as unsafe fn(*mut T),
)
},
data: MaybeUninit::new(UnsafeCell::new(val)),
}
}

#[inline]
pub unsafe fn link_and_data(&self) -> (NonNull<Link>, *mut T) {
(NonNull::from(&self.link), self.data.assume_init_ref().get())
}

#[inline]
pub unsafe fn ptr_from_data(data: *mut T) -> NonNull<DropEntry<T>> {
NonNull::new_unchecked(
data.byte_sub(memoffset::offset_of!(Self, data))
.cast::<DropEntry<T>>(),
)
}

#[inline]
pub unsafe fn link_from_data(data: *mut T) -> NonNull<Link> {
let entry = Self::ptr_from_data(data).as_ptr();
NonNull::new_unchecked(core::ptr::addr_of_mut!((*entry).link))
}
}
43 changes: 43 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ pub extern crate alloc as core_alloc;
pub mod boxed;
#[cfg(feature = "collections")]
pub mod collections;
#[cfg(feature = "pin")]
pub mod pin;

mod alloc;
#[cfg(feature = "pin")]
mod drop;

use core::cell::Cell;
use core::fmt::Display;
Expand Down Expand Up @@ -293,6 +297,8 @@ pub struct Bump {
// The current chunk we are bump allocating within.
current_chunk_footer: Cell<NonNull<ChunkFooter>>,
allocation_limit: Cell<Option<usize>>,
#[cfg(feature = "pin")]
drop_list: Cell<*const drop::DropList>,
}

#[repr(C)]
Expand Down Expand Up @@ -386,6 +392,9 @@ impl Default for Bump {
impl Drop for Bump {
fn drop(&mut self) {
unsafe {
#[cfg(feature = "pin")]
self.reset_drop_list();

dealloc_chunk_list(self.current_chunk_footer.get());
}
}
Expand Down Expand Up @@ -523,6 +532,8 @@ impl Bump {
return Ok(Bump {
current_chunk_footer: Cell::new(EMPTY_CHUNK.get()),
allocation_limit: Cell::new(None),
#[cfg(feature = "pin")]
drop_list: Cell::new(core::ptr::null()),
});
}

Expand All @@ -540,6 +551,8 @@ impl Bump {
Ok(Bump {
current_chunk_footer: Cell::new(chunk_footer),
allocation_limit: Cell::new(None),
#[cfg(feature = "pin")]
drop_list: Cell::new(core::ptr::null()),
})
}

Expand Down Expand Up @@ -747,6 +760,9 @@ impl Bump {
return;
}

#[cfg(feature = "pin")]
self.reset_drop_list();

let mut cur_chunk = self.current_chunk_footer.get();

// Deallocate all chunks except the current one
Expand Down Expand Up @@ -1767,6 +1783,33 @@ impl Bump {
}
}

#[cfg(feature = "pin")]
impl Bump {
fn drop_list(&self) -> &drop::DropList {
if self.drop_list.get().is_null() {
let drop_list = &*self.alloc(drop::DropList::default());
unsafe { drop_list.init() };
self.drop_list.set(drop_list);
}
unsafe { &*self.drop_list.get() }
}

fn take_drop_list(&mut self) -> Option<&drop::DropList> {
let drop_list_ptr = self.drop_list.replace(core::ptr::null());
if drop_list_ptr.is_null() {
return None;
}
unsafe { Some(&*drop_list_ptr) }
}

unsafe fn reset_drop_list(&mut self) {
let Some(drop_list) = self.take_drop_list() else {
return;
};
drop_list.run_drop();
}
}

/// An iterator over each chunk of allocated memory that
/// an arena has bump allocated into.
///
Expand Down