mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 05:08:12 +08:00
Drop arena entries in reverse allocation order and require Send + Sync payloads
This commit is contained in:
@@ -30,7 +30,9 @@ struct DropEntry {
|
|||||||
|
|
||||||
// SAFETY: disjoint regions are handed out by an atomic bump; a region is written
|
// SAFETY: disjoint regions are handed out by an atomic bump; a region is written
|
||||||
// only by its allocating caller before publication, and cross-thread hand-off is
|
// only by its allocating caller before publication, and cross-thread hand-off is
|
||||||
// ordered by the Release/Acquire pair on the published handle word.
|
// ordered by the Release/Acquire pair on the published handle word. The stored
|
||||||
|
// payloads are `Send + Sync` by the bound on every allocating method, so drop
|
||||||
|
// glue running on the resetting thread and lent `&T` crossing threads are sound.
|
||||||
unsafe impl Sync for Arena {}
|
unsafe impl Sync for Arena {}
|
||||||
unsafe impl Send for Arena {}
|
unsafe impl Send for Arena {}
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ impl Arena {
|
|||||||
Some(start)
|
Some(start)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc<T>(&self, value: T) -> Option<(&T, ArenaWeak<T>)> {
|
pub fn alloc<T: Send + Sync>(&self, value: T) -> Option<(&T, ArenaWeak<T>)> {
|
||||||
let offset = self.reserve(size_of::<T>(), align_of::<T>())?;
|
let offset = self.reserve(size_of::<T>(), align_of::<T>())?;
|
||||||
// Built before the write so an unencodable offset drops `value` here
|
// Built before the write so an unencodable offset drops `value` here
|
||||||
// rather than stranding it in the arena without drop glue.
|
// rather than stranding it in the arena without drop glue.
|
||||||
@@ -92,7 +94,7 @@ impl Arena {
|
|||||||
Some((unsafe { &*ptr }, weak))
|
Some((unsafe { &*ptr }, weak))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_slice_copy<T: Copy>(&self, src: &[T]) -> Option<&[T]> {
|
pub fn alloc_slice_copy<T: Copy + Send + Sync>(&self, src: &[T]) -> Option<&[T]> {
|
||||||
let buf = self.alloc_scratch::<T>(src.len())?;
|
let buf = self.alloc_scratch::<T>(src.len())?;
|
||||||
for (slot, &value) in buf.iter_mut().zip(src) {
|
for (slot, &value) in buf.iter_mut().zip(src) {
|
||||||
slot.write(value);
|
slot.write(value);
|
||||||
@@ -104,7 +106,7 @@ impl Arena {
|
|||||||
// Not drop-tracked: callers must either consume every written lane or
|
// Not drop-tracked: callers must either consume every written lane or
|
||||||
// restrict themselves to `Copy` payloads (leak, not UB, otherwise).
|
// restrict themselves to `Copy` payloads (leak, not UB, otherwise).
|
||||||
#[allow(clippy::mut_from_ref)]
|
#[allow(clippy::mut_from_ref)]
|
||||||
pub fn alloc_scratch<T>(&self, len: usize) -> Option<&mut [MaybeUninit<T>]> {
|
pub fn alloc_scratch<T: Send + Sync>(&self, len: usize) -> Option<&mut [MaybeUninit<T>]> {
|
||||||
let size = size_of::<T>().checked_mul(len)?;
|
let size = size_of::<T>().checked_mul(len)?;
|
||||||
let offset = self.reserve(size, align_of::<T>())?;
|
let offset = self.reserve(size, align_of::<T>())?;
|
||||||
let ptr = unsafe { self.base().add(offset) }.cast::<MaybeUninit<T>>();
|
let ptr = unsafe { self.base().add(offset) }.cast::<MaybeUninit<T>>();
|
||||||
@@ -115,7 +117,7 @@ impl Arena {
|
|||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
let base = self.base();
|
let base = self.base();
|
||||||
for entry in self.drops.get_mut().unwrap().drain(..) {
|
for entry in self.drops.get_mut().unwrap().drain(..).rev() {
|
||||||
// SAFETY: registered at alloc time; insert-only means the region was
|
// SAFETY: registered at alloc time; insert-only means the region was
|
||||||
// never overwritten within this generation.
|
// never overwritten within this generation.
|
||||||
unsafe { (entry.drop_fn)(base.add(entry.offset)) }
|
unsafe { (entry.drop_fn)(base.add(entry.offset)) }
|
||||||
@@ -273,6 +275,23 @@ mod tests {
|
|||||||
assert!(arena.alloc(0u32).is_some(), "the arena stays usable");
|
assert!(arena.alloc(0u32).is_some(), "the arena stays usable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_drops_dependents_before_their_dependencies() {
|
||||||
|
static ORDER: Mutex<Vec<u32>> = Mutex::new(Vec::new());
|
||||||
|
struct Probe(u32);
|
||||||
|
impl Drop for Probe {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
ORDER.lock().unwrap().push(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut arena = Arena::new(1024);
|
||||||
|
for id in 0..3 {
|
||||||
|
arena.alloc(Probe(id)).unwrap();
|
||||||
|
}
|
||||||
|
arena.reset();
|
||||||
|
assert_eq!(*ORDER.lock().unwrap(), vec![2, 1, 0], "later allocations may borrow earlier ones, so they drop first");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn drop_glue_runs_on_reset() {
|
fn drop_glue_runs_on_reset() {
|
||||||
static DROPS: AtomicU32 = AtomicU32::new(0);
|
static DROPS: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user