Skip to content

Commit 2dcb786

Browse files
committed
Initial tests for the 'epsilon' collector
TODO: Consider moving the 'epsilon' collector to an independent crate? As usual, debugging w/ malloc is much easier than with arena allocation.
1 parent ecb75bb commit 2dcb786

File tree

7 files changed

+244
-32
lines changed

7 files changed

+244
-32
lines changed

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ bumpalo = { version = "3", optional = true }
2626
members = ["libs/simple", "libs/derive", "libs/context"]
2727

2828
[features]
29-
default = ["std", "epsilon"]
29+
default = ["std", "epsilon", "epsilon-arena-alloc"]
3030
# Depend on the standard library (optional)
3131
#
3232
# This implements tracing for most standard library types.
@@ -39,4 +39,7 @@ alloc = []
3939
# Serde support
4040
serde1 = ["serde", "zerogc-derive/__serde-internal", "indexmap/serde-1"]
4141
# Support the "epsilon" no-op collector
42-
epsilon = ["bumpalo"]
42+
epsilon = []
43+
# Configure the "epsilon" collector use arena allocation
44+
# (on by default)
45+
epsilon-arena-alloc = ["epsilon", "bumpalo"]

src/epsilon.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@
99
#![cfg(feature = "epsilon")]
1010

1111
mod layout;
12+
mod alloc;
1213

1314
use crate::{CollectorId, GcContext, GcSafe, GcSimpleAlloc, GcSystem, GcVisitor, NullTrace, Trace, TraceImmutable, TrustedDrop};
1415
use std::ptr::NonNull;
1516
use std::alloc::Layout;
1617
use std::rc::Rc;
1718
use std::cell::Cell;
1819
use std::ffi::c_void;
19-
2020
use std::lazy::OnceCell;
2121

22+
use self::{alloc::{EpsilonAlloc}, layout::TypeInfo};
23+
2224
/// Fake a [Gc] that points to the specified value
2325
///
2426
/// This will never actually be collected
@@ -43,7 +45,7 @@ macro_rules! epsilon_static_array {
4345
array: [T; LEN]
4446
}
4547
static HEADERED: &'static ArrayWithHeader<$target, { $len }> = &ArrayWithHeader {
46-
header: self::epsilon::ArrayHeaderHack::for_len($len),
48+
header: self::epsilon::ArrayHeaderHack::for_len::<$target>($len),
4749
array: $values
4850
};
4951
std::mem::transmute::<
@@ -61,10 +63,10 @@ pub struct ArrayHeaderHack(layout::EpsilonArrayHeader);
6163
impl ArrayHeaderHack {
6264
#[inline]
6365
#[doc(hidden)]
64-
pub const fn for_len(len: usize) -> ArrayHeaderHack {
66+
pub const fn for_len<T>(len: usize) -> ArrayHeaderHack {
6567
ArrayHeaderHack(layout::EpsilonArrayHeader {
6668
len, common_header: layout::EpsilonHeader {
67-
type_info: None,
69+
type_info: TypeInfo::of_array::<T>(),
6870
next: None
6971
}
7072
})
@@ -130,7 +132,9 @@ unsafe impl GcContext for EpsilonContext {
130132

131133
#[inline]
132134
fn system(&self) -> &'_ Self::System {
133-
unsafe { self.state.cast::<EpsilonSystem>().as_ref() }
135+
// Pointer to a pointer
136+
unsafe { NonNull::<NonNull<State>>::from(&self.state)
137+
.cast::<EpsilonSystem>().as_ref() }
134138
}
135139

136140

@@ -151,7 +155,7 @@ impl Drop for EpsilonContext {
151155
}
152156

153157
struct State {
154-
alloc: bumpalo::Bump,
158+
alloc: alloc::Default,
155159
/// The head of the linked-list of allocated objects.
156160
head: Cell<Option<NonNull<layout::EpsilonHeader>>>,
157161
empty_vec: OnceCell<NonNull<layout::EpsilonVecRepr>>
@@ -163,6 +167,39 @@ impl State {
163167
self.head.set(Some(header));
164168
}
165169
}
170+
impl Drop for State {
171+
fn drop(&mut self) {
172+
let mut ptr = self.head.get();
173+
unsafe {
174+
while let Some(header) = ptr {
175+
let header_layout = layout::EpsilonHeader::LAYOUT;
176+
let desired_align = header.as_ref().type_info.layout.align();
177+
let padding = header_layout.padding_needed_for(desired_align);
178+
let value_ptr = (header.as_ptr() as *const u8)
179+
.add(header_layout.size())
180+
.add(padding);
181+
if let Some(drop_func) = header.as_ref().type_info.drop_func {
182+
(drop_func)(value_ptr as *const _ as *mut _);
183+
}
184+
let next = header.as_ref().next;
185+
if self::alloc::Default::NEEDS_EXPLICIT_FREE {
186+
let value_layout = header.as_ref().determine_layout();
187+
let original_header = NonNull::new_unchecked(header.cast::<u8>()
188+
.as_ptr()
189+
.sub(header.as_ref().type_info.layout.common_header_offset()));
190+
let header_size = value_ptr.cast::<u8>()
191+
.offset_from(original_header.as_ptr()) as usize;
192+
let combined_layout = Layout::from_size_align_unchecked(
193+
value_layout.size() + header_size,
194+
value_layout.align().max(layout::EpsilonHeader::LAYOUT.align())
195+
);
196+
self.alloc.free_alloc(original_header, combined_layout);
197+
}
198+
ptr = next;
199+
}
200+
}
201+
}
202+
}
166203

167204
/// A dummy implementation of [GcSystem]
168205
/// which is useful for testing
@@ -191,7 +228,7 @@ impl EpsilonSystem {
191228
#[inline]
192229
pub fn leak() -> Self {
193230
EpsilonSystem::from_state(Rc::new(State {
194-
alloc: bumpalo::Bump::new(),
231+
alloc: self::alloc::Default::new(),
195232
head: Cell::new(None),
196233
empty_vec: OnceCell::new()
197234
}))
@@ -228,13 +265,16 @@ unsafe impl GcSimpleAlloc for EpsilonContext {
228265
#[inline]
229266
unsafe fn alloc_uninit<'gc, T>(&'gc self) -> (Self::Id, *mut T) where T: GcSafe<'gc, EpsilonCollectorId> + 'gc {
230267
let id = self.id();
231-
let ptr = if let Some(tp) = self::layout::TypeInfo::of::<T>() {
268+
let tp = self::layout::TypeInfo::of::<T>();
269+
let needs_header = self::alloc::Default::NEEDS_EXPLICIT_FREE
270+
|| !tp.may_ignore();
271+
let ptr = if needs_header {
232272
let (overall_layout, offset) = self::layout::EpsilonHeader::LAYOUT
233273
.extend(Layout::new::<T>()).unwrap();
234274
let mem = self.system().state().alloc.alloc_layout(overall_layout);
235275
let header = mem.cast::<self::layout::EpsilonHeader>();
236276
header.as_ptr().write(self::layout::EpsilonHeader {
237-
type_info: Some(tp),
277+
type_info: tp,
238278
next: None
239279
});
240280
self.system().state().push_state(header);
@@ -392,7 +432,7 @@ unsafe impl CollectorId for EpsilonCollectorId {
392432
unsafe fn assume_valid_system(&self) -> &Self::System {
393433
/*
394434
* NOTE: Supporting this would lose our ability to go from `&'static T` -> `Gc<'gc, T, EpsilonCollectorId>
395-
* It would also nessesitate a header for `Copy` objects.
435+
* It would also necessitate a header for `Copy` objects.
396436
*/
397437
unimplemented!("Unable to convert EpsilonCollectorId -> EpsilonSystem")
398438
}

src/epsilon/alloc.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use std::ptr::NonNull;
2+
use std::alloc::Layout;
3+
4+
#[cfg(feature = "epsilon-arena-alloc")]
5+
mod arena;
6+
7+
pub trait EpsilonAlloc {
8+
fn new() -> Self;
9+
fn alloc_layout(&self, layout: Layout) -> NonNull<u8>;
10+
unsafe fn free_alloc(&self, target: NonNull<u8>, layout: Layout);
11+
const NEEDS_EXPLICIT_FREE: bool;
12+
}
13+
14+
#[cfg(feature = "epsilon-arena-alloc")]
15+
pub type Default = arena::BumpEpsilonAlloc;
16+
#[cfg(not(feature = "epsilon-arena-alloc"))]
17+
pub type Default = StdEpsilonAlloc;
18+
19+
pub struct StdEpsilonAlloc;
20+
impl EpsilonAlloc for StdEpsilonAlloc {
21+
#[inline]
22+
fn new() -> Self {
23+
StdEpsilonAlloc
24+
}
25+
26+
#[inline]
27+
fn alloc_layout(&self, layout: Layout) -> NonNull<u8> {
28+
const EMPTY: &[u8] = b"";
29+
if layout.size() == 0 {
30+
return NonNull::from(EMPTY).cast();
31+
}
32+
// SAFETY: We checked for layout.size() == 0
33+
NonNull::new(unsafe { std::alloc::alloc(layout) })
34+
.unwrap_or_else(|| std::alloc::handle_alloc_error(layout))
35+
}
36+
37+
#[inline]
38+
unsafe fn free_alloc(&self, target: NonNull<u8>, layout: Layout) {
39+
if layout.size() == 0 {
40+
return; // We returned our dummy empty alloc
41+
}
42+
std::alloc::dealloc(target.as_ptr(), layout)
43+
}
44+
45+
const NEEDS_EXPLICIT_FREE: bool = true;
46+
}
47+

src/epsilon/alloc/arena.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::ptr::NonNull;
2+
use std::alloc::Layout;
3+
4+
use bumpalo::Bump;
5+
6+
use super::EpsilonAlloc;
7+
8+
pub struct BumpEpsilonAlloc(Bump);
9+
impl EpsilonAlloc for BumpEpsilonAlloc {
10+
#[inline]
11+
fn new() -> Self {
12+
BumpEpsilonAlloc(Bump::new())
13+
}
14+
#[inline]
15+
fn alloc_layout(&self, layout: Layout) -> NonNull<u8> {
16+
self.0.alloc_layout(layout)
17+
}
18+
#[inline]
19+
unsafe fn free_alloc(&self, _target: NonNull<u8>, _layout: Layout) {}
20+
const NEEDS_EXPLICIT_FREE: bool = false;
21+
}

src/epsilon/layout.rs

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::vec::repr::GcVecRepr;
1212
/// They are also unnecessary for statically allocated objects.
1313
pub struct EpsilonHeader {
1414
/// This object's `TypeInfo`, or `None` if it doesn't need any.
15-
pub type_info: Option<&'static TypeInfo>,
15+
pub type_info: &'static TypeInfo,
1616
/// The next allocated object, or `None` if this is the final object.
1717
pub next: Option<NonNull<EpsilonHeader>>
1818
}
@@ -29,6 +29,20 @@ impl EpsilonHeader {
2929
let (_, offset) = Self::LAYOUT.extend(Layout::for_value(&*header)).unwrap_unchecked();
3030
(header as *const c_void).sub(offset).cast()
3131
}
32+
#[inline]
33+
#[track_caller]
34+
pub unsafe fn determine_layout(&self) -> Layout {
35+
let tp = self.type_info;
36+
match tp.layout {
37+
LayoutInfo::Fixed(fixed) => fixed,
38+
LayoutInfo::Array { element_layout } |
39+
LayoutInfo::Vec { element_layout } => {
40+
let array_header = EpsilonArrayHeader::from_common_header(self);
41+
let len = (*array_header).len;
42+
element_layout.repeat(len).unwrap_unchecked().0
43+
}
44+
}
45+
}
3246
}
3347
#[repr(C)]
3448
pub struct EpsilonArrayHeader {
@@ -49,37 +63,67 @@ pub struct EpsilonVecHeader {
4963
pub len: Cell<usize>,
5064
pub common_header: EpsilonHeader,
5165
}
66+
impl EpsilonVecHeader {
67+
const COMMON_OFFSET: usize = std::mem::size_of::<Self>() - std::mem::size_of::<EpsilonHeader>();
68+
}
69+
pub enum LayoutInfo {
70+
Fixed(Layout),
71+
/// A variable sized array
72+
Array {
73+
element_layout: Layout
74+
},
75+
/// A variable sized vector
76+
Vec {
77+
element_layout: Layout
78+
}
79+
}
80+
impl LayoutInfo {
81+
#[inline]
82+
pub const fn align(&self) -> usize {
83+
match *self {
84+
LayoutInfo::Fixed(layout) |
85+
LayoutInfo::Array { element_layout: layout } |
86+
LayoutInfo::Vec { element_layout: layout } => layout.align()
87+
}
88+
}
89+
#[inline]
90+
pub fn common_header_offset(&self) -> usize {
91+
match *self {
92+
LayoutInfo::Fixed(_) => 0,
93+
LayoutInfo::Array { .. } => EpsilonArrayHeader::COMMON_OFFSET,
94+
LayoutInfo::Vec { .. } => EpsilonVecHeader::COMMON_OFFSET
95+
}
96+
}
97+
}
5298
pub struct TypeInfo {
5399
/// The function to drop this object, or `None` if the object doesn't need to be dropped
54100
pub drop_func: Option<unsafe fn(*mut c_void)>,
55-
/// The size of the object, or `None` if this is an array or vector
56-
pub size: Option<usize>
101+
pub layout: LayoutInfo
57102
}
58103
impl TypeInfo {
59104
#[inline]
60-
pub const fn of<T>() -> Option<&'static TypeInfo> {
61-
if std::mem::needs_drop::<T>() {
62-
Some(<T as StaticTypeInfo>::TYPE_INFO)
63-
} else {
64-
None
65-
}
105+
pub const fn may_ignore(&self) -> bool {
106+
// NOTE: We don't care about `size`
107+
self.drop_func.is_none() &&
108+
self.layout.align() <= std::mem::align_of::<usize>()
66109
}
67110
#[inline]
68-
pub const fn of_array<T>() -> Option<&'static TypeInfo> {
69-
if std::mem::needs_drop::<T>() {
70-
Some(<[T] as StaticTypeInfo>::TYPE_INFO)
71-
} else {
72-
None
73-
}
111+
pub const fn of<T>() -> &'static TypeInfo {
112+
<T as StaticTypeInfo>::TYPE_INFO
113+
}
114+
#[inline]
115+
pub const fn of_array<T>() -> &'static TypeInfo {
116+
<[T] as StaticTypeInfo>::TYPE_INFO
74117
}
75118
#[inline]
76-
pub const fn of_vec<T>() -> Option<&'static TypeInfo> {
119+
pub const fn of_vec<T>() -> &'static TypeInfo {
77120
// For now, vectors and arrays share type info
78-
Self::of_array::<T>()
121+
<T as StaticTypeInfo>::VEC_INFO.as_ref().unwrap()
79122
}
80123
}
81124
trait StaticTypeInfo {
82125
const TYPE_INFO: &'static TypeInfo;
126+
const VEC_INFO: &'static Option<TypeInfo>;
83127
}
84128
impl<T> StaticTypeInfo for T {
85129
const TYPE_INFO: &'static TypeInfo = &TypeInfo {
@@ -88,16 +132,29 @@ impl<T> StaticTypeInfo for T {
88132
} else {
89133
None
90134
},
91-
size: Some(std::mem::size_of::<T>())
135+
layout: LayoutInfo::Fixed(Layout::new::<T>()),
92136
};
137+
const VEC_INFO: &'static Option<TypeInfo> = &Some(TypeInfo {
138+
drop_func: if std::mem::needs_drop::<T>() {
139+
Some(drop_array::<T>)
140+
} else {
141+
None
142+
},
143+
layout: LayoutInfo::Vec {
144+
element_layout: Layout::new::<T>()
145+
}
146+
});
93147
}
94148
impl<T> StaticTypeInfo for [T] {
95149
const TYPE_INFO: &'static TypeInfo = &TypeInfo {
96150
drop_func: if std::mem::needs_drop::<T>() {
97151
Some(drop_array::<T>)
98152
} else { None },
99-
size: None
153+
layout: LayoutInfo::Array {
154+
element_layout: Layout::new::<T>()
155+
}
100156
};
157+
const VEC_INFO: &'static Option<TypeInfo> = &None;
101158
}
102159
/// Drop an array or vector of the specified type
103160
unsafe fn drop_array<T>(ptr: *mut c_void) {
@@ -167,7 +224,6 @@ unsafe impl<'gc> GcVecRepr<'gc> for EpsilonVecRepr {
167224
fn capacity(&self) -> usize {
168225
unsafe { (*self.header()).capacity }
169226
}
170-
171227
#[inline]
172228
unsafe fn ptr(&self) -> *const c_void {
173229
self as *const Self as *const c_void // We are actually just a GC pointer to the value ptr

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
// Needed for epsilon collector:
1010
once_cell, // RFC 2788 (Probably will be accepted)
1111
alloc_layout_extra,
12+
const_fn_fn_ptr_basics,
13+
const_option,
1214
const_fn_trait_bound, // NOTE: Needed for the `epsilon_static_array` macro
1315
)]
1416
#![feature(maybe_uninit_slice)]

0 commit comments

Comments
 (0)