-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Introduce an arena type which may be used to allocate a list of types with destructors #59536
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
Changes from all commits
002c70f
e835d27
4ccb9ae
43e33ea
04762dd
223f1c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
use arena::{TypedArena, DroplessArena}; | ||
use std::mem; | ||
use std::ptr; | ||
use std::slice; | ||
use std::cell::RefCell; | ||
use std::marker::PhantomData; | ||
use smallvec::SmallVec; | ||
|
||
#[macro_export] | ||
macro_rules! arena_types { | ||
($macro:path, $args:tt, $tcx:lifetime) => ( | ||
$macro!($args, [ | ||
[] vtable_method: Option<( | ||
rustc::hir::def_id::DefId, | ||
rustc::ty::subst::SubstsRef<$tcx> | ||
)>, | ||
[few] mir_keys: rustc::util::nodemap::DefIdSet, | ||
[decode] specialization_graph: rustc::traits::specialization_graph::Graph, | ||
], $tcx); | ||
) | ||
} | ||
|
||
macro_rules! arena_for_type { | ||
([][$ty:ty]) => { | ||
TypedArena<$ty> | ||
}; | ||
([few $(, $attrs:ident)*][$ty:ty]) => { | ||
PhantomData<$ty> | ||
}; | ||
([$ignore:ident $(, $attrs:ident)*]$args:tt) => { | ||
arena_for_type!([$($attrs),*]$args) | ||
}; | ||
} | ||
|
||
macro_rules! declare_arena { | ||
([], [$($a:tt $name:ident: $ty:ty,)*], $tcx:lifetime) => { | ||
#[derive(Default)] | ||
pub struct Arena<$tcx> { | ||
dropless: DroplessArena, | ||
drop: DropArena, | ||
$($name: arena_for_type!($a[$ty]),)* | ||
} | ||
} | ||
} | ||
|
||
macro_rules! which_arena_for_type { | ||
([][$arena:expr]) => { | ||
Some($arena) | ||
}; | ||
([few$(, $attrs:ident)*][$arena:expr]) => { | ||
None | ||
}; | ||
([$ignore:ident$(, $attrs:ident)*]$args:tt) => { | ||
which_arena_for_type!([$($attrs),*]$args) | ||
}; | ||
} | ||
|
||
macro_rules! impl_arena_allocatable { | ||
([], [$($a:tt $name:ident: $ty:ty,)*], $tcx:lifetime) => { | ||
$( | ||
impl ArenaAllocatable for $ty {} | ||
unsafe impl<$tcx> ArenaField<$tcx> for $ty { | ||
#[inline] | ||
fn arena<'a>(_arena: &'a Arena<$tcx>) -> Option<&'a TypedArena<Self>> { | ||
which_arena_for_type!($a[&_arena.$name]) | ||
} | ||
} | ||
)* | ||
} | ||
} | ||
|
||
arena_types!(declare_arena, [], 'tcx); | ||
|
||
arena_types!(impl_arena_allocatable, [], 'tcx); | ||
|
||
pub trait ArenaAllocatable {} | ||
|
||
impl<T: Copy> ArenaAllocatable for T {} | ||
|
||
pub unsafe trait ArenaField<'tcx>: Sized { | ||
/// Returns a specific arena to allocate from. | ||
/// If None is returned, the DropArena will be used. | ||
fn arena<'a>(arena: &'a Arena<'tcx>) -> Option<&'a TypedArena<Self>>; | ||
} | ||
|
||
unsafe impl<'tcx, T> ArenaField<'tcx> for T { | ||
#[inline] | ||
default fn arena<'a>(_: &'a Arena<'tcx>) -> Option<&'a TypedArena<Self>> { | ||
panic!() | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This impl can conflict with the types in the list :(
Do we have some way to express an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wow, it would be nice to be able to rely on sets and maps never being There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found a way around this using a marker trait. |
||
} | ||
|
||
impl<'tcx> Arena<'tcx> { | ||
#[inline] | ||
pub fn alloc<T: ArenaAllocatable>(&self, value: T) -> &mut T { | ||
if !mem::needs_drop::<T>() { | ||
return self.dropless.alloc(value); | ||
} | ||
match <T as ArenaField<'tcx>>::arena(self) { | ||
Some(arena) => arena.alloc(value), | ||
None => unsafe { self.drop.alloc(value) }, | ||
} | ||
} | ||
|
||
pub fn alloc_from_iter< | ||
T: ArenaAllocatable, | ||
I: IntoIterator<Item = T> | ||
>( | ||
&'a self, | ||
iter: I | ||
) -> &'a mut [T] { | ||
if !mem::needs_drop::<T>() { | ||
return self.dropless.alloc_from_iter(iter); | ||
} | ||
match <T as ArenaField<'tcx>>::arena(self) { | ||
Some(arena) => arena.alloc_from_iter(iter), | ||
None => unsafe { self.drop.alloc_from_iter(iter) }, | ||
} | ||
} | ||
} | ||
|
||
/// Calls the destructor for an object when dropped. | ||
struct DropType { | ||
drop_fn: unsafe fn(*mut u8), | ||
obj: *mut u8, | ||
} | ||
|
||
unsafe fn drop_for_type<T>(to_drop: *mut u8) { | ||
std::ptr::drop_in_place(to_drop as *mut T) | ||
} | ||
|
||
impl Drop for DropType { | ||
fn drop(&mut self) { | ||
unsafe { | ||
(self.drop_fn)(self.obj) | ||
} | ||
} | ||
} | ||
|
||
/// An arena which can be used to allocate any type. | ||
/// Allocating in this arena is unsafe since the type system | ||
/// doesn't know which types it contains. In order to | ||
/// allocate safely, you must store a PhantomData<T> | ||
/// alongside this arena for each type T you allocate. | ||
#[derive(Default)] | ||
struct DropArena { | ||
/// A list of destructors to run when the arena drops. | ||
/// Ordered so `destructors` gets dropped before the arena | ||
/// since its destructor can reference memory in the arena. | ||
destructors: RefCell<Vec<DropType>>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the impl, I think this will work for values which refer to other values in the same arena, as long as those types don't have interior mutability and can end up with references to values created later. How are we ensuring this invariant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not an invariant that's upheld. We just let dropck ensure that types can't do anything bad in their destructors. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm talking about something like struct A<'tcx> {
x: Mutex<Option<&'tcx B>>,
}
impl<'tcx> Drop for A<'tcx> {
fn drop(&mut self) {
println!("{}", x.lock().unwrap().unwrap().s);
}
}
struct B {
s: String,
} where
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can the same interning with |
||
arena: DroplessArena, | ||
} | ||
|
||
impl DropArena { | ||
#[inline] | ||
unsafe fn alloc<T>(&self, object: T) -> &mut T { | ||
let mem = self.arena.alloc_raw( | ||
mem::size_of::<T>(), | ||
mem::align_of::<T>() | ||
) as *mut _ as *mut T; | ||
// Write into uninitialized memory. | ||
ptr::write(mem, object); | ||
let result = &mut *mem; | ||
// Record the destructor after doing the allocation as that may panic | ||
// and would cause `object`'s destuctor to run twice if it was recorded before | ||
self.destructors.borrow_mut().push(DropType { | ||
drop_fn: drop_for_type::<T>, | ||
obj: result as *mut T as *mut u8, | ||
}); | ||
result | ||
} | ||
|
||
#[inline] | ||
unsafe fn alloc_from_iter<T, I: IntoIterator<Item = T>>(&self, iter: I) -> &mut [T] { | ||
let mut vec: SmallVec<[_; 8]> = iter.into_iter().collect(); | ||
if vec.is_empty() { | ||
return &mut []; | ||
} | ||
let len = vec.len(); | ||
|
||
let start_ptr = self.arena.alloc_raw( | ||
len.checked_mul(mem::size_of::<T>()).unwrap(), | ||
mem::align_of::<T>() | ||
) as *mut _ as *mut T; | ||
|
||
let mut destructors = self.destructors.borrow_mut(); | ||
// Reserve space for the destructors so we can't panic while adding them | ||
destructors.reserve(len); | ||
|
||
// Move the content to the arena by copying it and then forgetting | ||
// the content of the SmallVec | ||
vec.as_ptr().copy_to_nonoverlapping(start_ptr, len); | ||
mem::forget(vec.drain()); | ||
|
||
// Record the destructors after doing the allocation as that may panic | ||
// and would cause `object`'s destuctor to run twice if it was recorded before | ||
for i in 0..len { | ||
destructors.push(DropType { | ||
drop_fn: drop_for_type::<T>, | ||
obj: start_ptr.offset(i as isize) as *mut u8, | ||
}); | ||
} | ||
|
||
slice::from_raw_parts_mut(start_ptr, len) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The list of types are declared here with a macro.