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
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//! An array of fixed length, allocated from lgalloc if possible.
use std::mem::{ManuallyDrop, MaybeUninit};
use std::ops::Deref;
/// A fixed-length region in memory, which is either allocated from heap or lgalloc.
pub struct Array<T> {
/// A handle to lgalloc. None for heap allocations, Some if the memory comes from lgalloc.
handle: Option<lgalloc::Handle>,
/// Slice representation of the memory. Elements 0..self.length are valid.
elements: ManuallyDrop<Box<[MaybeUninit<T>]>>,
/// The number of valid elements in `elements`
length: usize,
}
impl<T> Array<T> {
/// Create a new [`Array`] with the specified capacity. The actual capacity of the returned
/// array is at least as big as the requested capacity.
pub fn with_capacity(capacity: usize) -> Self {
// Allocate memory, fall-back to regular heap allocations if we cannot acquire memory through
// lgalloc.
let (handle, boxed) = if let Ok((ptr, actual_capacity, handle)) =
lgalloc::allocate::<MaybeUninit<T>>(capacity)
{
// We allocated sucessfully through lgalloc.
let handle = Some(handle);
// SAFETY: `ptr` is valid for constructing a slice:
// 1. Valid for reading and writing, and enough capacity.
// 2. Properly initialized (left for writing).
// 3. Not aliased.
// 4. Total size not longer than isize::MAX because lgalloc has a capacity limit.
let slice = unsafe { std::slice::from_raw_parts_mut(ptr.as_ptr(), actual_capacity) };
// SAFETY: slice is valid, and we deallocate it usinge lgalloc.
(handle, unsafe { Box::from_raw(slice) })
} else {
// We failed to allocate through lgalloc, fall back to heap.
let mut vec = Vec::with_capacity(capacity);
// SAFETY: We treat all elements as uninitialized and track initialized elements
// through `self.length`.
unsafe {
vec.set_len(vec.capacity());
}
(None, vec.into_boxed_slice())
};
let elements = ManuallyDrop::new(boxed);
Self {
handle,
elements,
length: 0,
}
}
/// Visit contained allocations to determine their size and capacity.
pub fn heap_size(&self, mut callback: impl FnMut(usize, usize)) {
let size_of_t = std::mem::size_of::<T>();
callback(self.len() * size_of_t, self.capacity() * size_of_t)
}
/// Move an element on the array. Panics if there is no more capacity.
pub fn push(&mut self, item: T) {
debug_assert!(
self.length < self.elements.len(),
"Failed to push: length {} not less than {} capacity",
self.length,
self.elements.len()
);
self.elements[self.length].write(item);
self.length += 1;
}
/// Update the length. Highly unsafe because it doesn't drop elements when reducing the length,
/// and doesn't initialize elements when increasing the length.
pub unsafe fn set_len(&mut self, length: usize) {
debug_assert!(length <= self.capacity());
self.length = length;
}
/// The number of elements this array can absorb.
pub fn capacity(&self) -> usize {
self.elements.len()
}
/// Remove all elements. Drops the contents, but leaves the allocation untouched.
pub fn clear(&mut self) {
let elems = &mut self.elements[..self.length];
// We are about to run the type's destructor, which may panic. Therefore we set the length
// of the array to zero so that if we have to unwind the stack we don't end up re-dropping
// in valid memory through the Drop impl of Array itself.
self.length = 0;
for e in elems {
// SAFETY: We know elements up to `length` are initialized.
unsafe {
e.assume_init_drop();
}
}
}
}
impl<T> Deref for Array<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
// TODO: Use `slice_assume_init_ref` once stable.
// Context: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.slice_assume_init_ref
// The following safety argument is adapted from the source.
// SAFETY: casting `elements` to a `*const [T]` is safe since the caller guarantees that
// `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`.
// The pointer obtained is valid since it refers to memory owned by `elements` which is a
// reference and thus guaranteed to be valid for reads.
#[allow(clippy::as_conversions)]
unsafe {
&*(&self.elements[..self.length] as *const [MaybeUninit<T>] as *const [T])
}
}
}
impl<T> Drop for Array<T> {
fn drop(&mut self) {
self.clear();
if let Some(handle) = self.handle.take() {
// Memory allocated through lgalloc
lgalloc::deallocate(handle);
} else {
// Regular allocation
// SAFETY: `elements` is a sliced box allocated from the global allocator, drop it.
unsafe {
ManuallyDrop::drop(&mut self.elements);
}
}
}
}
#[cfg(test)]
mod test {
use std::sync::atomic::{AtomicUsize, Ordering};
use super::*;
#[mz_ore::test]
fn double_drop() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct DropGuard;
impl Drop for DropGuard {
fn drop(&mut self) {
let drops = DROP_COUNT.fetch_add(1, Ordering::Relaxed);
// If this is the first time we're being dropped, panic.
if drops == 0 {
panic!();
}
}
}
let mut array = Array::with_capacity(1);
array.push(DropGuard);
let _ = mz_ore::panic::catch_unwind(move || array.clear());
assert_eq!(DROP_COUNT.load(Ordering::Relaxed), 1);
}
}