1use std::fmt::Debug;
8use std::ptr::NonNull;
9
10use crate::{AllocationCommitRange, MemoryError, SharedDevicePrefix, SharedPrefixCommitInfo};
11
12pub trait VirtualBacking: Send + Sync + Debug {
18 fn allocate_committed(
20 &self,
21 bytes: usize,
22 align: usize,
23 committed_ranges: &[std::ops::Range<usize>],
24 ) -> Result<NonNull<u8>, MemoryError>;
25
26 fn commit_allocation_range(
27 &self,
28 ptr: NonNull<u8>,
29 allocation_bytes: usize,
30 align: usize,
31 offset: usize,
32 bytes: usize,
33 ) -> Result<(), MemoryError>;
34
35 fn commit_allocation_ranges(
36 &self,
37 ranges: &[AllocationCommitRange],
38 ) -> Result<(), MemoryError> {
39 for range in ranges {
40 self.commit_allocation_range(
41 range.ptr,
42 range.allocation_bytes,
43 range.align,
44 range.offset,
45 range.bytes,
46 )?;
47 }
48 Ok(())
49 }
50
51 fn mapped_bytes_for_allocation_ranges(
56 &self,
57 ranges: &[AllocationCommitRange],
58 ) -> Result<u64, MemoryError>;
59
60 fn mapped_bytes_for_allocation(&self, bytes: usize, align: usize) -> Result<u64, MemoryError>;
61
62 fn decommit_allocation_range(
67 &self,
68 ptr: NonNull<u8>,
69 allocation_bytes: usize,
70 align: usize,
71 offset: usize,
72 bytes: usize,
73 ) -> Result<u64, MemoryError>;
74
75 fn allocation_committed_bytes(
76 &self,
77 ptr: NonNull<u8>,
78 allocation_bytes: usize,
79 align: usize,
80 ) -> usize;
81}
82
83pub trait SharedMapping: Send + Sync + Debug {
89 fn create_shared_prefix(
90 &self,
91 bytes: usize,
92 ) -> Result<Box<dyn SharedDevicePrefix>, MemoryError>;
93
94 fn incremental_owned_bytes_for_shared_prefix(
101 &self,
102 prefix: &dyn SharedDevicePrefix,
103 ) -> Result<u64, MemoryError>;
104
105 fn commit_shared_prefix(
111 &self,
112 prefix: &dyn SharedDevicePrefix,
113 ptr: NonNull<u8>,
114 allocation_bytes: usize,
115 byte_offset: usize,
116 ) -> Result<SharedPrefixCommitInfo, MemoryError>;
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::{DeviceAllocator, DeviceKey, HostAllocator};
123 use std::any::Any;
124 use std::sync::atomic::{AtomicU64, Ordering};
125
126 #[derive(Debug, Default)]
127 struct VirtualOnly {
128 commits: AtomicU64,
129 decommits: AtomicU64,
130 }
131
132 impl DeviceAllocator for VirtualOnly {
133 fn allocate(&self, bytes: usize, align: usize) -> Result<NonNull<u8>, MemoryError> {
134 HostAllocator.allocate(bytes, align)
135 }
136
137 unsafe fn deallocate(&self, ptr: NonNull<u8>, bytes: usize, align: usize) {
138 unsafe { HostAllocator.deallocate(ptr, bytes, align) };
140 }
141
142 fn device(&self) -> DeviceKey {
143 DeviceKey::HOST
144 }
145
146 fn as_virtual_backing(&self) -> Option<&dyn VirtualBacking> {
147 Some(self)
148 }
149 }
150
151 impl VirtualBacking for VirtualOnly {
152 fn allocate_committed(
153 &self,
154 bytes: usize,
155 align: usize,
156 _ranges: &[std::ops::Range<usize>],
157 ) -> Result<NonNull<u8>, MemoryError> {
158 self.commits.fetch_add(1, Ordering::Relaxed);
159 HostAllocator.allocate(bytes, align)
160 }
161
162 fn commit_allocation_range(
163 &self,
164 _ptr: NonNull<u8>,
165 _allocation_bytes: usize,
166 _align: usize,
167 _offset: usize,
168 _bytes: usize,
169 ) -> Result<(), MemoryError> {
170 self.commits.fetch_add(1, Ordering::Relaxed);
171 Ok(())
172 }
173
174 fn mapped_bytes_for_allocation_ranges(
175 &self,
176 ranges: &[AllocationCommitRange],
177 ) -> Result<u64, MemoryError> {
178 Ok(ranges.iter().map(|range| range.bytes as u64).sum())
179 }
180
181 fn mapped_bytes_for_allocation(
182 &self,
183 bytes: usize,
184 _align: usize,
185 ) -> Result<u64, MemoryError> {
186 Ok(bytes as u64)
187 }
188
189 fn decommit_allocation_range(
190 &self,
191 _ptr: NonNull<u8>,
192 _allocation_bytes: usize,
193 _align: usize,
194 _offset: usize,
195 bytes: usize,
196 ) -> Result<u64, MemoryError> {
197 self.decommits.fetch_add(1, Ordering::Relaxed);
198 Ok(bytes as u64)
199 }
200
201 fn allocation_committed_bytes(
202 &self,
203 _ptr: NonNull<u8>,
204 allocation_bytes: usize,
205 _align: usize,
206 ) -> usize {
207 allocation_bytes
208 }
209 }
210
211 #[derive(Debug)]
212 struct SharedOnly;
213
214 #[derive(Debug)]
215 struct ForeignZeroPrefix;
216
217 impl SharedDevicePrefix for ForeignZeroPrefix {
218 fn device_ptr(&self) -> u64 {
219 0
220 }
221
222 fn committed_physical_bytes(&self) -> u64 {
223 0
224 }
225
226 fn mapped_bytes(&self) -> usize {
227 0
228 }
229
230 fn requested_bytes(&self) -> usize {
231 0
232 }
233
234 fn as_any(&self) -> &dyn Any {
235 self
236 }
237 }
238
239 impl DeviceAllocator for SharedOnly {
240 fn allocate(&self, bytes: usize, align: usize) -> Result<NonNull<u8>, MemoryError> {
241 HostAllocator.allocate(bytes, align)
242 }
243
244 unsafe fn deallocate(&self, ptr: NonNull<u8>, bytes: usize, align: usize) {
245 unsafe { HostAllocator.deallocate(ptr, bytes, align) };
247 }
248
249 fn device(&self) -> DeviceKey {
250 DeviceKey::HOST
251 }
252
253 fn as_shared_mapping(&self) -> Option<&dyn SharedMapping> {
254 Some(self)
255 }
256 }
257
258 impl SharedMapping for SharedOnly {
259 fn create_shared_prefix(
260 &self,
261 bytes: usize,
262 ) -> Result<Box<dyn SharedDevicePrefix>, MemoryError> {
263 Err(MemoryError::InvalidRequest {
264 tier: "host",
265 requested: bytes as u64,
266 reason: "test shared prefix construction is intentionally unsupported",
267 })
268 }
269
270 fn incremental_owned_bytes_for_shared_prefix(
271 &self,
272 prefix: &dyn SharedDevicePrefix,
273 ) -> Result<u64, MemoryError> {
274 Err(MemoryError::InvalidRequest {
275 tier: "host",
276 requested: prefix.requested_bytes() as u64,
277 reason: "test shared prefix is foreign to this capability",
278 })
279 }
280
281 fn commit_shared_prefix(
282 &self,
283 _prefix: &dyn SharedDevicePrefix,
284 _ptr: NonNull<u8>,
285 allocation_bytes: usize,
286 _byte_offset: usize,
287 ) -> Result<SharedPrefixCommitInfo, MemoryError> {
288 Err(MemoryError::InvalidRequest {
289 tier: "host",
290 requested: allocation_bytes as u64,
291 reason: "test shared mapping is intentionally unsupported",
292 })
293 }
294 }
295
296 #[test]
297 fn virtual_backing_is_discovered_and_used_before_canonical_release() {
298 let allocator = VirtualOnly::default();
299 let ordinary: &dyn DeviceAllocator = &allocator;
300 let backing = ordinary
301 .as_virtual_backing()
302 .expect("virtual backing capability");
303 let initial = 0..8;
304 let ptr = backing
305 .allocate_committed(64, 16, std::slice::from_ref(&initial))
306 .expect("reserve and commit");
307 backing
308 .commit_allocation_range(ptr, 64, 16, 8, 8)
309 .expect("additional commit");
310 assert_eq!(
311 backing
312 .decommit_allocation_range(ptr, 64, 16, 8, 8)
313 .expect("partial decommit"),
314 8
315 );
316 assert_eq!(backing.allocation_committed_bytes(ptr, 64, 16), 64);
317 assert_eq!(allocator.commits.load(Ordering::Relaxed), 2);
318 assert_eq!(allocator.decommits.load(Ordering::Relaxed), 1);
319 unsafe { ordinary.deallocate(ptr, 64, 16) };
321 }
322
323 #[test]
324 fn shared_mapping_discovery_is_independent_of_virtual_backing() {
325 let allocator: &dyn DeviceAllocator = &SharedOnly;
326 assert!(allocator.as_virtual_backing().is_none());
327 let mapping = allocator
328 .as_shared_mapping()
329 .expect("independent shared mapping capability");
330 assert!(
331 mapping
332 .incremental_owned_bytes_for_shared_prefix(&ForeignZeroPrefix)
333 .is_err(),
334 "foreign input must be rejected before even a zero cost is reported"
335 );
336 }
337}