Skip to main content

vortex_array/
memory.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Session-scoped buffer allocation.
5
6use std::any::Any;
7
8pub use vortex_buffer::BufferAllocator;
9pub use vortex_buffer::BufferAllocatorRef;
10pub use vortex_buffer::StaticBufferAllocator;
11use vortex_session::SessionExt;
12use vortex_session::SessionGuard;
13use vortex_session::SessionVar;
14use vortex_session::VortexSession;
15
16/// Session-scoped memory configuration for Vortex arrays.
17#[derive(Clone, Debug)]
18pub struct MemorySession {
19    allocator: BufferAllocatorRef,
20}
21
22impl MemorySession {
23    /// Creates a new memory configuration using the provided allocator.
24    pub fn new(allocator: BufferAllocatorRef) -> Self {
25        Self { allocator }
26    }
27
28    /// Returns the configured allocator.
29    pub fn allocator(&self) -> BufferAllocatorRef {
30        self.allocator.clone()
31    }
32
33    /// Updates the configured allocator.
34    pub fn set_allocator(&mut self, allocator: BufferAllocatorRef) {
35        self.allocator = allocator;
36    }
37}
38
39impl Default for MemorySession {
40    fn default() -> Self {
41        Self::new(BufferAllocatorRef::statically_allocated())
42    }
43}
44
45impl SessionVar for MemorySession {
46    fn as_any(&self) -> &dyn Any {
47        self
48    }
49
50    fn as_any_mut(&mut self) -> &mut dyn Any {
51        self
52    }
53}
54
55/// Extension methods for session-scoped buffer allocation.
56pub trait MemorySessionExt: SessionExt {
57    /// Returns the memory configuration.
58    fn memory(&self) -> SessionGuard<'_, MemorySession> {
59        self.get::<MemorySession>()
60    }
61
62    /// Returns the configured buffer allocator.
63    fn allocator(&self) -> BufferAllocatorRef {
64        self.memory().allocator()
65    }
66
67    /// Configures the session allocator and returns the session.
68    fn with_allocator(self, allocator: BufferAllocatorRef) -> VortexSession {
69        let session = self.session();
70        session.get_mut::<MemorySession>().set_allocator(allocator);
71        session
72    }
73}
74
75impl<S: SessionExt> MemorySessionExt for S {}
76
77#[cfg(test)]
78pub(crate) mod test_allocator {
79    use std::alloc::Layout;
80    use std::ptr::NonNull;
81    use std::sync::Arc;
82    use std::sync::atomic::AtomicUsize;
83    use std::sync::atomic::Ordering;
84
85    use allocator_api2::alloc::AllocError;
86    use allocator_api2::alloc::Allocator;
87    use allocator_api2::alloc::Global;
88    use parking_lot::Mutex;
89
90    use super::BufferAllocatorRef;
91
92    #[derive(Debug)]
93    struct CountingAllocator(Arc<AtomicUsize>);
94
95    unsafe impl Allocator for CountingAllocator {
96        fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
97            self.0.fetch_add(1, Ordering::Relaxed);
98            Global.allocate(layout)
99        }
100
101        unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
102            // SAFETY: the allocation came from Global with this layout.
103            unsafe { Global.deallocate(ptr, layout) }
104        }
105    }
106
107    /// Tracks live allocation ranges so tests can identify the returned payload's allocator.
108    #[derive(Clone, Debug, Default)]
109    pub(crate) struct AllocationTracker {
110        allocations: Arc<Mutex<Vec<(usize, usize)>>>,
111    }
112
113    impl AllocationTracker {
114        #[track_caller]
115        pub(crate) fn assert_owns<T>(&self, values: &[T]) {
116            assert!(
117                !values.is_empty(),
118                "allocation ownership needs a nonempty payload",
119            );
120            let start = values.as_ptr() as usize;
121            let end = start + size_of_val(values);
122            assert!(
123                self.allocations
124                    .lock()
125                    .iter()
126                    .any(|&(base, size)| base <= start && end <= base + size),
127                "the returned payload must be backed by the configured allocator",
128            );
129        }
130
131        pub(crate) fn live_allocations(&self) -> usize {
132            self.allocations.lock().len()
133        }
134    }
135
136    #[derive(Debug)]
137    struct TrackingAllocator(AllocationTracker);
138
139    // SAFETY: allocation and deallocation use Global with the original layout. The tracker only
140    // records address ranges and never accesses their contents.
141    unsafe impl Allocator for TrackingAllocator {
142        fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
143            let allocation = Global.allocate(layout)?;
144            self.0
145                .allocations
146                .lock()
147                .push((allocation.cast::<u8>().as_ptr() as usize, allocation.len()));
148            Ok(allocation)
149        }
150
151        unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
152            self.0
153                .allocations
154                .lock()
155                .retain(|&(base, _)| base != ptr.as_ptr() as usize);
156            // SAFETY: this allocation came from Global with the supplied layout.
157            unsafe { Global.deallocate(ptr, layout) }
158        }
159    }
160
161    pub(crate) fn tracking_allocator() -> (BufferAllocatorRef, AllocationTracker) {
162        let tracker = AllocationTracker::default();
163        (
164            BufferAllocatorRef::new(TrackingAllocator(tracker.clone())),
165            tracker,
166        )
167    }
168
169    pub(crate) fn counting_allocator() -> (BufferAllocatorRef, Arc<AtomicUsize>) {
170        let allocations = Arc::new(AtomicUsize::new(0));
171        (
172            BufferAllocatorRef::new(CountingAllocator(Arc::clone(&allocations))),
173            allocations,
174        )
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use vortex_buffer::BufferAllocatorRef;
181
182    use super::MemorySession;
183
184    #[test]
185    fn memory_session_replaces_allocator() {
186        let allocator = BufferAllocatorRef::statically_allocated();
187        let mut session = MemorySession::default();
188        session.set_allocator(allocator);
189        let buffer = session.allocator().copy_from([1u32, 2, 3]);
190        assert_eq!(buffer.as_slice(), [1, 2, 3]);
191    }
192}