untyped_box/
impl.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
use core::{alloc::Layout, any::type_name, mem::MaybeUninit, ptr::NonNull};

use crate::alloc_shim::{AllocError, Allocator, Global};

/// An allocation is management representation of some allocated memory.
///
/// For the most part, this behaves like a lower-level (untyped) cousin of a `Box`.
/// The memory backing this allocation is deallocated when the allocation is dropped.
/// In contrast, no validity or initialization state of the memory is implied by
/// existance of an [Allocation].
pub struct Allocation<A: Allocator = Global> {
    // TODO: should be a Unique pointer!
    ptr: NonNull<u8>,
    layout: Layout,
    alloc: A,
}

// TODO: There is a bit of a mismatch here. In essence, we are losing information.
// For example, requesting an allocation for some `Layout::new::<T>()` that results in the allocator
// giving us more memory than we asked for might make later checks when trying to convert to a `Box`
// fail on size mismatch.
// We might have to blow up the allocation struct to reconstruct [Memory fitting] information.
// [Memory fitting]: https://doc.rust-lang.org/nightly/alloc/alloc/trait.Allocator.html#memory-fitting
fn match_allocated_size(ptr: NonNull<[u8]>, layout: Layout) -> (NonNull<u8>, Layout) {
    let actual_layout = unsafe { Layout::from_size_align_unchecked(ptr.len(), layout.align()) };
    debug_assert!(actual_layout.size() >= layout.size());
    (ptr.cast(), actual_layout)
}
fn allocate(alloc: &impl Allocator, layout: Layout) -> Result<(NonNull<u8>, Layout), AllocError> {
    let ptr = alloc.allocate(layout)?;
    Ok(match_allocated_size(ptr, layout))
}
fn allocate_zeroed(
    alloc: &impl Allocator,
    layout: Layout,
) -> Result<(NonNull<u8>, Layout), AllocError> {
    let ptr = alloc.allocate_zeroed(layout)?;
    Ok(match_allocated_size(ptr, layout))
}
unsafe fn grow(
    alloc: &impl Allocator,
    ptr: NonNull<u8>,
    old_layout: Layout,
    new_layout: Layout,
) -> Result<(NonNull<u8>, Layout), AllocError> {
    let ptr = alloc.grow(ptr, old_layout, new_layout)?;
    Ok(match_allocated_size(ptr, new_layout))
}
unsafe fn grow_zeroed(
    alloc: &impl Allocator,
    ptr: NonNull<u8>,
    old_layout: Layout,
    new_layout: Layout,
) -> Result<(NonNull<u8>, Layout), AllocError> {
    let ptr = alloc.grow_zeroed(ptr, old_layout, new_layout)?;
    Ok(match_allocated_size(ptr, new_layout))
}
unsafe fn shrink(
    alloc: &impl Allocator,
    ptr: NonNull<u8>,
    old_layout: Layout,
    new_layout: Layout,
) -> Result<(NonNull<u8>, Layout), AllocError> {
    let ptr = alloc.shrink(ptr, old_layout, new_layout)?;
    Ok(match_allocated_size(ptr, new_layout))
}

/// Methods for the global allocator
impl Allocation {
    /// Allocate new memory for the given layout.
    ///
    /// The pointer backing the allocation is valid for reads and writes of `layout.size()` bytes and this
    /// memory region does not alias any other existing allocation.
    ///
    /// The pointer is guaranteed to be aligned to `layout.align()` but several systems align memory more
    /// lax when a small alignment is requested.
    ///
    /// Memory is not initialized or zeroed, try [`Self::zeroed`] instead.
    ///
    /// # Panics
    ///
    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
    /// See [`Self::try_new_in`] for a version that returns an error instead.
    // Forwards to alloc, handles layout.size() == 0 with a dangling ptr
    pub fn new(layout: Layout) -> Self {
        Self::new_in(layout, Global)
    }
    /// Allocate new zeroed-out memory for the given layout.
    ///
    /// # Panics
    ///
    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
    /// See [`Self::try_zeroed_in`] for a version that returns an error instead.
    pub fn zeroed(layout: Layout) -> Self {
        Self::zeroed_in(layout, Global)
    }
    /// Split the allocation into its raw parts.
    ///
    /// Deallocating the allocation is the responsibility of the caller. The returned
    /// pointer can be passed to [`alloc::alloc::dealloc`] if the returned layout indicates `size() > 0`.
    /// If the allocated memory is 0 sized, the pointer does not need to be deallocated.
    ///
    /// See also [`Self::into_parts_with_alloc`] for an allocator-aware version.
    pub fn into_parts(self) -> (NonNull<u8>, Layout) {
        let (ptr, layout, _) = Self::into_parts_with_alloc(self);
        (ptr, layout)
    }
    /// Constructs an [`Allocation`] from a pointer and layout information.
    ///
    /// # Safety
    ///
    /// The pointer must point to [*currently-allocated*] memory from the global allocator, and `layout`
    /// was used to allocate that memory.
    ///
    /// [*currently-allocated*]: Allocator#currently-allocated-memory
    pub unsafe fn from_parts(ptr: NonNull<u8>, layout: Layout) -> Self {
        Self::from_parts_in(ptr, layout, Global)
    }
}
/// Common methods
impl<A: Allocator> Allocation<A> {
    /// Gets a pointer to the allocation.
    ///
    /// The pointer is always aligned to the alignment of the layout indicated by [Self::layout] or the requested layout
    /// indicated on allocation, whichever is more strict.
    ///
    /// The pointer can be used to read and write memory in this allocation until it is [reallocated](Self::realloc),
    /// dropped or the memory is reclaimed manually (e.g. after converting [`into_parts`](Self::into_parts)).
    ///
    /// In particular, the pointer does not in itself materialize a reference to the underlying storage for the purpose of the aliasing model.
    pub fn as_ptr<T>(&self) -> NonNull<T> {
        self.ptr.cast()
    }
    /// View the underlying storage as a possibly uninitialized `T`.
    ///
    /// # Panics
    ///
    /// If the allocation is too small, or not aligned enough to contain a `T`.
    pub fn as_uninit_ref<T>(&self) -> &MaybeUninit<T> {
        assert!(
            self.layout.size() >= size_of::<T>(),
            "allocation too small to represent a {}",
            type_name::<T>()
        );
        assert!(
            self.layout.align() >= align_of::<T>(),
            "allocation not aligned for a {}",
            type_name::<T>()
        );
        unsafe { &*self.ptr.as_ptr().cast() }
    }
    /// View the underlying storage as a possibly uninitialized `T`.
    ///
    /// # Panics
    ///
    /// If the allocation is too small, or not aligned enough to contain a `T`.
    pub fn as_uninit_mut<T>(&mut self) -> &mut MaybeUninit<T> {
        assert!(
            self.layout.size() >= size_of::<T>(),
            "allocation too small to represent a {}",
            type_name::<T>()
        );
        assert!(
            self.layout.align() >= align_of::<T>(),
            "allocation not aligned for a {}",
            type_name::<T>()
        );
        unsafe { &mut *self.ptr.as_ptr().cast() }
    }
    /// View the allocation as a pointer to a slice of possibly uninitialized bytes.
    ///
    /// The caller is responsible for checking lifetimes when convert to a reference.
    ///
    /// The pointer can be used to read and write memory in this allocation until it is [reallocated](Self::realloc),
    /// dropped or the memory is reclaimed manually (e.g. after converting [`into_parts`](Self::into_parts)).
    ///
    /// Like [`as_ptr`](Self::as_ptr), this does not materialize a reference to the underlying storage for the purpose of the aliasing model.
    /// Hence, these two methods can be intermixed.
    pub fn as_slice(&self) -> NonNull<[MaybeUninit<u8>]> {
        let ptr = core::ptr::slice_from_raw_parts_mut(
            self.ptr.as_ptr().cast::<MaybeUninit<u8>>(),
            self.layout.size(),
        );
        unsafe { NonNull::new_unchecked(ptr) }
    }
    /// Reallocates memory to a new layout.
    ///
    /// If the newly requested layout is larger than the currently allocated layout, existing (possibly uninitialized) bytes are preserved.
    /// Newly allocated bytes are uninitialized.
    ///
    /// Any pointers to the managed memory are invalidated on return.
    ///
    /// # Panics
    ///
    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic. In this case, pointers are still valid.
    /// See [`Self::try_realloc`] for a version that returns an error instead.
    // Calls either grow or shrink, compares against stored layout
    pub fn realloc(&mut self, new_layout: Layout) {
        let () = self
            .try_realloc(new_layout)
            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(new_layout));
    }
    /// Reallocates memory to a new layout.
    ///
    /// If the newly requested layout is larger than the currently allocated layout, existing (possibly uninitialized) bytes are preserved.
    /// Newly allocated bytes are zeroed.
    ///
    /// Any pointers to the managed memory are invalidated on return.
    ///
    /// # Panics
    ///
    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic. In this case, pointers are still valid.
    /// See [`Self::try_realloc_zeroed`] for a version that returns an error instead.
    pub fn realloc_zeroed(&mut self, new_layout: Layout) {
        let () = self
            .try_realloc_zeroed(new_layout)
            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(new_layout));
    }
    /// Get the layout of the underlying allocation.
    ///
    /// This layout is guaranteed to be at least as large as previously requested from [`new`](Self::new) or [`realloc`](Self::realloc) and
    /// at least as strictly aligned, but might indicate more available memory.
    pub fn layout(&self) -> Layout {
        self.layout
    }
}
/// Methods using the allocator-api or shim
impl<A: Allocator> Allocation<A> {
    /// Allocate new memory for the given layout in a given allocator.
    ///
    /// The pointer backing the allocation is valid for reads and writes of `layout.size()` bytes and this
    /// memory region does not alias any other existing allocation.
    ///
    /// The pointer is guaranteed to be aligned to `layout.align()` but several systems align memory more
    /// lax when a small alignment is requested.
    ///
    /// Memory is not initialized or zeroed, try [`Self::zeroed_in`] instead.
    ///
    /// # Panics
    ///
    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
    /// See [`Self::try_new_in`] for a version that returns an error instead.
    pub fn new_in(layout: Layout, alloc: A) -> Self {
        Self::try_new_in(layout, alloc)
            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(layout))
    }
    /// Allocate new memory for the given layout in a given allocator.
    ///
    /// Returns an error when no memory could be allocated.
    pub fn try_new_in(layout: Layout, alloc: A) -> Result<Self, AllocError> {
        let (ptr, layout) = allocate(&alloc, layout)?;
        Ok(Self { ptr, layout, alloc })
    }
    /// Allocate new zeroed-out memory for the given layout in a given allocator.
    ///
    /// # Panics
    ///
    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
    /// See [`Self::try_zeroed_in`] for a version that returns an error instead.
    pub fn zeroed_in(layout: Layout, alloc: A) -> Self {
        Self::try_zeroed_in(layout, alloc)
            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(layout))
    }
    /// Allocate new zeroed-out memory for the given layout in a given allocator.
    ///
    /// Returns an error when no memory could be allocated.
    pub fn try_zeroed_in(layout: Layout, alloc: A) -> Result<Self, AllocError> {
        let (ptr, layout) = allocate_zeroed(&alloc, layout)?;
        Ok(Self { ptr, layout, alloc })
    }
    /// Split the allocation into its raw parts including the allocator.
    ///
    /// Deallocating the allocation is the responsibility of the caller. The returned
    /// pointer can be passed to `alloc.deallocate()`.
    pub fn into_parts_with_alloc(self) -> (NonNull<u8>, Layout, A) {
        let me = core::mem::ManuallyDrop::new(self);
        let alloc = unsafe { core::ptr::read(&me.alloc) };
        (me.ptr, me.layout, alloc)
    }
    /// Constructs an [`Allocation`] from a pointer and layout information in the given allocator.
    ///
    /// # Safety
    ///
    /// The pointer must point to [*currently-allocated*] memory from the given allocator, and `layout`
    /// [*fits*] that memory.
    ///
    /// [*currently-allocated*]: Allocator#currently-allocated-memory
    /// [*fits*]: Allocator#memory-fitting
    pub unsafe fn from_parts_in(ptr: NonNull<u8>, layout: Layout, alloc: A) -> Self {
        Self { ptr, layout, alloc }
    }
    /// Reallocates memory to a new layout.
    ///
    /// Returns an error when the memory could not be reallocated. In this case, any previously derived
    /// pointers remain valid and no memory is deallocated.
    ///
    /// # See also
    ///
    /// [`Self::realloc`] for more disuccion about the memory contents after reallocation.
    pub fn try_realloc(&mut self, new_layout: Layout) -> Result<(), AllocError> {
        if new_layout == self.layout {
            return Ok(());
        }
        // Prefer grow to shrink when all we do is change alignment
        if new_layout.size() >= self.layout.size() {
            (self.ptr, self.layout) =
                unsafe { grow(&self.alloc, self.ptr, self.layout, new_layout)? };
            Ok(())
        } else {
            (self.ptr, self.layout) =
                unsafe { shrink(&self.alloc, self.ptr, self.layout, new_layout)? };
            Ok(())
        }
    }
    /// Reallocates memory to a new layout.
    ///
    /// Returns an error when the memory could not be reallocated. In this case, any previously derived
    /// pointers remain valid and no memory is deallocated.
    ///
    /// # See also
    ///
    /// [`Self::realloc_zeroed`] for more disuccion about the memory contents after reallocation.
    pub fn try_realloc_zeroed(&mut self, new_layout: Layout) -> Result<(), AllocError> {
        if new_layout == self.layout {
            return Ok(());
        }
        // Prefer grow to shrink when all we do is change alignment
        if new_layout.size() >= self.layout.size() {
            (self.ptr, self.layout) =
                unsafe { grow_zeroed(&self.alloc, self.ptr, self.layout, new_layout)? };
            Ok(())
        } else {
            (self.ptr, self.layout) =
                unsafe { shrink(&self.alloc, self.ptr, self.layout, new_layout)? };
            Ok(())
        }
    }
}

impl<A: Allocator> Drop for Allocation<A> {
    fn drop(&mut self) {
        unsafe {
            self.alloc.deallocate(self.ptr, self.layout);
        }
    }
}

unsafe impl<A: Allocator + Sync> Sync for Allocation<A> {}
unsafe impl<A: Allocator + Send> Send for Allocation<A> {}