Skip to content

Commit e080a05

Browse files
author
Julian Orth
committed
Add StructAlloc
StructAlloc<T> is an internal allocator that, when asked to allocate memory for a type `Data`, actually allocates space for ```rust struct Struct { t: T, data: Data, } ``` This is useful for allocations that will later be stored in smart pointers. Instead of having to copy data, we simply offset the pointer, knowing that we can store the smart metadata (e.g. ref count) in the space before the data allocation.
1 parent d416093 commit e080a05

File tree

4 files changed

+350
-0
lines changed

4 files changed

+350
-0
lines changed

library/alloc/src/alloc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub use core::alloc::*;
1616

1717
#[cfg(test)]
1818
mod tests;
19+
#[macro_use]
20+
pub(crate) mod struct_alloc;
1921

2022
extern "Rust" {
2123
// These are the magic symbols to call the global allocator. rustc generates
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
use crate::alloc::Global;
2+
use crate::fmt;
3+
use core::alloc::{AllocError, Allocator, Layout};
4+
use core::fmt::{Debug, Formatter};
5+
use core::marker::PhantomData;
6+
use core::mem;
7+
use core::ptr::NonNull;
8+
9+
#[cfg(test)]
10+
mod tests;
11+
12+
/// Allocator that adds appropriate padding for a repr(C) struct.
13+
///
14+
/// This allocator takes as type arguments the type of a field `T` and an allocator `A`.
15+
///
16+
/// Consider
17+
///
18+
/// ```rust,ignore (not real code)
19+
/// #[repr(C)]
20+
/// struct Struct {
21+
/// t: T,
22+
/// data: Data,
23+
/// }
24+
/// ```
25+
///
26+
/// where `Data` is a type with layout `layout`.
27+
///
28+
/// When this allocator creates an allocation for layout `layout`, the pointer can be
29+
/// offset by `-offsetof(Struct, data)` and the resulting pointer points is an allocation
30+
/// of `A` for `Layout::new::<Struct>()`.
31+
pub(crate) struct StructAlloc<T, A = Global>(A, PhantomData<*const T>);
32+
33+
impl<T, A> StructAlloc<T, A> {
34+
#[allow(dead_code)]
35+
/// Creates a new allocator.
36+
pub(crate) fn new(allocator: A) -> Self {
37+
Self(allocator, PhantomData)
38+
}
39+
40+
/// Computes the layout of `Struct`.
41+
fn struct_layout(data_layout: Layout) -> Result<Layout, AllocError> {
42+
let t_align = mem::align_of::<T>();
43+
let t_size = mem::size_of::<T>();
44+
if t_size == 0 && t_align == 1 {
45+
// Skip the checks below
46+
return Ok(data_layout);
47+
}
48+
let data_align = data_layout.align();
49+
// The contract of `Layout` guarantees that `data_align > 0`.
50+
let data_align_minus_1 = data_align.wrapping_sub(1);
51+
let data_size = data_layout.size();
52+
let align = data_align.max(t_align);
53+
let align_minus_1 = align.wrapping_sub(1);
54+
// `size` is
55+
// t_size rounded up to `data_align`
56+
// plus
57+
// `data_size` rounded up to `align`
58+
// Note that the result is a multiple of `align`.
59+
let (t_size_aligned, t_overflow) =
60+
t_size.overflowing_add(t_size.wrapping_neg() & data_align_minus_1);
61+
let (data_size_aligned, data_overflow) = match data_size.overflowing_add(align_minus_1) {
62+
(sum, req_overflow) => (sum & !align_minus_1, req_overflow),
63+
};
64+
let (size, sum_overflow) = t_size_aligned.overflowing_add(data_size_aligned);
65+
if t_overflow || data_overflow || sum_overflow {
66+
return Err(AllocError);
67+
}
68+
unsafe { Ok(Layout::from_size_align_unchecked(size, align)) }
69+
}
70+
71+
/// Returns the offset of `data` in `Struct`.
72+
#[inline]
73+
pub(crate) fn offset_of_data(data_layout: Layout) -> usize {
74+
let t_size = mem::size_of::<T>();
75+
// The contract of `Layout` guarantees `.align() > 0`
76+
let data_align_minus_1 = data_layout.align().wrapping_sub(1);
77+
t_size.wrapping_add(t_size.wrapping_neg() & data_align_minus_1)
78+
}
79+
80+
/// Given a pointer to `data`, returns a pointer to `Struct`.
81+
///
82+
/// # Safety
83+
///
84+
/// The data pointer must have been allocated by `Self` with the same `data_layout`.
85+
#[inline]
86+
unsafe fn data_ptr_to_struct_ptr(data: NonNull<u8>, data_layout: Layout) -> NonNull<u8> {
87+
unsafe {
88+
let offset_of_data = Self::offset_of_data(data_layout);
89+
NonNull::new_unchecked(data.as_ptr().sub(offset_of_data))
90+
}
91+
}
92+
93+
/// Given a pointer to `Struct`, returns a pointer to `data`.
94+
///
95+
/// # Safety
96+
///
97+
/// The struct pointer must have been allocated by `A` with the layout
98+
/// `Self::struct_layout(data_layout)`.
99+
#[inline]
100+
unsafe fn struct_ptr_to_data_ptr(
101+
struct_ptr: NonNull<[u8]>,
102+
data_layout: Layout,
103+
) -> NonNull<[u8]> {
104+
let offset_of_data = Self::offset_of_data(data_layout);
105+
let data_ptr =
106+
unsafe { NonNull::new_unchecked(struct_ptr.as_mut_ptr().add(offset_of_data)) };
107+
// Note that the size is the exact size requested in the layout. Let me explain
108+
// why this is necessary:
109+
//
110+
// Assume the original requested layout was `size=1, align=1`. Assume `T=u16`
111+
// Then the struct layout is `size=4, align=2`. Assume that the allocator returns
112+
// a slice with `size=5`. Then the space available for `data` is `3`.
113+
// However, if we returned a slice with `len=3`, then the user would be allowed
114+
// to call `dealloc` with `size=3, align=1`. In this case the struct layout
115+
// would be computed as `size=6, align=2`. This size would be larger than what
116+
// the allocator returned.
117+
NonNull::slice_from_raw_parts(data_ptr, data_layout.size())
118+
}
119+
}
120+
121+
impl<T, A: Debug> Debug for StructAlloc<T, A> {
122+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123+
f.debug_struct("StructAlloc").field("0", &self.0).finish()
124+
}
125+
}
126+
127+
/// Delegates `Self::allocate{,_zereod}` to the allocator after computing the struct
128+
/// layout. Then transforms the new struct pointer to the new data pointer and returns it.
129+
macro delegate_alloc($id:ident) {
130+
fn $id(&self, data_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
131+
let struct_layout = Self::struct_layout(data_layout)?;
132+
let struct_ptr = self.0.$id(struct_layout)?;
133+
unsafe { Ok(Self::struct_ptr_to_data_ptr(struct_ptr, data_layout)) }
134+
}
135+
}
136+
137+
/// Delegates `Self::{{grow{,_zeroed},shrink}` to the allocator after computing the struct
138+
/// layout and transforming the data pointer to the struct pointer. Then transforms
139+
/// the new struct pointer to the new data pointer and returns it.
140+
macro delegate_transform($id:ident) {
141+
unsafe fn $id(
142+
&self,
143+
old_data_ptr: NonNull<u8>,
144+
old_data_layout: Layout,
145+
new_data_layout: Layout,
146+
) -> Result<NonNull<[u8]>, AllocError> {
147+
let old_struct_layout = Self::struct_layout(old_data_layout)?;
148+
let new_struct_layout = Self::struct_layout(new_data_layout)?;
149+
unsafe {
150+
let old_struct_ptr = Self::data_ptr_to_struct_ptr(old_data_ptr, old_data_layout);
151+
let new_struct_ptr =
152+
self.0.$id(old_struct_ptr, old_struct_layout, new_struct_layout)?;
153+
Ok(Self::struct_ptr_to_data_ptr(new_struct_ptr, new_data_layout))
154+
}
155+
}
156+
}
157+
158+
unsafe impl<T, A: Allocator> Allocator for StructAlloc<T, A> {
159+
delegate_alloc!(allocate);
160+
delegate_alloc!(allocate_zeroed);
161+
162+
unsafe fn deallocate(&self, data_ptr: NonNull<u8>, data_layout: Layout) {
163+
unsafe {
164+
let struct_ptr = Self::data_ptr_to_struct_ptr(data_ptr, data_layout);
165+
let struct_layout =
166+
Self::struct_layout(data_layout).expect("deallocate called with invalid layout");
167+
self.0.deallocate(struct_ptr, struct_layout);
168+
}
169+
}
170+
171+
delegate_transform!(grow);
172+
delegate_transform!(grow_zeroed);
173+
delegate_transform!(shrink);
174+
}
175+
176+
#[allow(unused_macros)]
177+
macro_rules! implement_struct_allocator {
178+
($id:ident) => {
179+
#[unstable(feature = "struct_alloc", issue = "none")]
180+
unsafe impl<A: Allocator> Allocator for $id<A> {
181+
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
182+
self.0.allocate(layout)
183+
}
184+
185+
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
186+
self.0.allocate_zeroed(layout)
187+
}
188+
189+
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
190+
unsafe { self.0.deallocate(ptr, layout) }
191+
}
192+
193+
unsafe fn grow(
194+
&self,
195+
ptr: NonNull<u8>,
196+
old_layout: Layout,
197+
new_layout: Layout,
198+
) -> Result<NonNull<[u8]>, AllocError> {
199+
unsafe { self.0.grow(ptr, old_layout, new_layout) }
200+
}
201+
202+
unsafe fn grow_zeroed(
203+
&self,
204+
ptr: NonNull<u8>,
205+
old_layout: Layout,
206+
new_layout: Layout,
207+
) -> Result<NonNull<[u8]>, AllocError> {
208+
unsafe { self.0.grow_zeroed(ptr, old_layout, new_layout) }
209+
}
210+
211+
unsafe fn shrink(
212+
&self,
213+
ptr: NonNull<u8>,
214+
old_layout: Layout,
215+
new_layout: Layout,
216+
) -> Result<NonNull<[u8]>, AllocError> {
217+
unsafe { self.0.shrink(ptr, old_layout, new_layout) }
218+
}
219+
}
220+
};
221+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use super::StructAlloc;
2+
use crate::alloc::Global;
3+
use crate::collections::VecDeque;
4+
use core::alloc::{AllocError, Allocator};
5+
use std::alloc::Layout;
6+
use std::cell::RefCell;
7+
use std::ptr::NonNull;
8+
use std::{any, ptr};
9+
10+
fn test_pair<T, Data>() {
11+
if let Err(_) = std::panic::catch_unwind(|| test_pair_::<T, Data>()) {
12+
panic!("test of {} followed by {} failed", any::type_name::<T>(), any::type_name::<Data>());
13+
}
14+
}
15+
16+
fn test_pair_<T, Data>() {
17+
#[repr(C)]
18+
struct S<T, Data> {
19+
t: T,
20+
data: Data,
21+
}
22+
23+
let offset = {
24+
let s: *const S<T, Data> = ptr::null();
25+
unsafe { std::ptr::addr_of!((*s).data) as usize }
26+
};
27+
28+
let expected_layout = RefCell::new(VecDeque::new());
29+
let expected_ptr = RefCell::new(VecDeque::new());
30+
31+
let check_layout = |actual| {
32+
let mut e = expected_layout.borrow_mut();
33+
match e.pop_front() {
34+
Some(expected) if expected == actual => {}
35+
Some(expected) => panic!("expected layout {:?}, actual layout {:?}", expected, actual),
36+
_ => panic!("unexpected allocator invocation with layout {:?}", actual),
37+
}
38+
};
39+
40+
let check_ptr = |actual: NonNull<u8>| {
41+
let mut e = expected_ptr.borrow_mut();
42+
match e.pop_front() {
43+
Some(expected) if expected == actual.as_ptr() => {}
44+
Some(expected) => {
45+
panic!("expected pointer {:p}, actual pointer {:p}", expected, actual)
46+
}
47+
_ => panic!("unexpected allocator invocation with pointer {:p}", actual),
48+
}
49+
};
50+
51+
struct TestAlloc<F, G>(F, G);
52+
53+
unsafe impl<F, G> Allocator for TestAlloc<F, G>
54+
where
55+
F: Fn(Layout),
56+
G: Fn(NonNull<u8>),
57+
{
58+
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
59+
self.0(layout);
60+
Global.allocate(layout)
61+
}
62+
63+
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
64+
self.1(ptr);
65+
self.0(layout);
66+
unsafe { Global.deallocate(ptr, layout) }
67+
}
68+
}
69+
70+
let struct_alloc = StructAlloc::<T, _>::new(TestAlloc(check_layout, check_ptr));
71+
72+
fn s_layout<T, Data, const N: usize>() -> Layout {
73+
Layout::new::<S<T, [Data; N]>>()
74+
}
75+
76+
fn d_layout<Data, const N: usize>() -> Layout {
77+
Layout::new::<[Data; N]>()
78+
}
79+
80+
fn check_slice<Data, const N: usize>(ptr: NonNull<[u8]>) {
81+
let expected = d_layout::<Data, N>().size();
82+
if ptr.len() != expected {
83+
panic!(
84+
"expected allocation size: {:?}, actual allocation size: {:?}",
85+
expected,
86+
ptr.len()
87+
)
88+
}
89+
}
90+
91+
expected_layout.borrow_mut().push_back(s_layout::<T, Data, 1>());
92+
let ptr = struct_alloc.allocate(d_layout::<Data, 1>()).unwrap();
93+
check_slice::<Data, 1>(ptr);
94+
unsafe {
95+
expected_ptr.borrow_mut().push_back(ptr.as_mut_ptr().sub(offset));
96+
}
97+
expected_layout.borrow_mut().push_back(s_layout::<T, Data, 3>());
98+
expected_layout.borrow_mut().push_back(s_layout::<T, Data, 1>());
99+
let ptr = unsafe {
100+
struct_alloc
101+
.grow(ptr.as_non_null_ptr(), d_layout::<Data, 1>(), d_layout::<Data, 3>())
102+
.unwrap()
103+
};
104+
check_slice::<Data, 3>(ptr);
105+
unsafe {
106+
expected_ptr.borrow_mut().push_back(ptr.as_mut_ptr().sub(offset));
107+
}
108+
expected_layout.borrow_mut().push_back(s_layout::<T, Data, 3>());
109+
unsafe {
110+
struct_alloc.deallocate(ptr.as_non_null_ptr(), d_layout::<Data, 3>());
111+
}
112+
if !expected_ptr.borrow().is_empty() || !expected_layout.borrow().is_empty() {
113+
panic!("missing allocator calls");
114+
}
115+
}
116+
117+
#[test]
118+
fn test() {
119+
macro_rules! test_ty {
120+
($($ty:ty),*) => { test_ty!(@2 $($ty),*; ($($ty),*)) };
121+
(@2 $($tyl:ty),*; $tyr:tt) => { $(test_ty!(@3 $tyl; $tyr);)* };
122+
(@3 $tyl:ty; ($($tyr:ty),*)) => { $(test_pair::<$tyl, $tyr>();)* };
123+
}
124+
// call test_pair::<A, B>() for every combination of these types
125+
test_ty!((), u8, u16, u32, u64, u128);
126+
}

library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ mod macros;
155155

156156
// Heaps provided for low-level allocation strategies
157157

158+
#[macro_use]
158159
pub mod alloc;
159160

160161
// Primitive types using the heaps above

0 commit comments

Comments
 (0)