cubecl_environment/bytes/base.rs
1//! A version of [`bytemuck::BoxBytes`] that is cloneable and allows trailing uninitialized elements.
2
3use crate::bytes::{
4 AccessError, AccessPolicy, Reader, Writer,
5 default_controller::{self, NativeAllocationController},
6 shared_arc::SharedAllocationController,
7};
8use crate::sync::Arc;
9use alloc::{boxed::Box, vec::Vec};
10use core::{
11 alloc::LayoutError,
12 mem::MaybeUninit,
13 ops::{Deref, DerefMut},
14 ptr::NonNull,
15};
16
17/// A buffer similar to `Box<[u8]>` that supports custom memory alignment and allows trailing uninitialized bytes.
18///
19/// `Bytes` is designed for efficient memory management in specialized contexts.
20/// It may use non-standard allocators, such as the CUDA SDK allocator for pinned memory, or leverage memory pooling to reduce allocation overhead.
21///
22/// # Safety
23///
24/// The first `len` bytes of the allocation are guaranteed to be initialized. Accessing bytes beyond `len` is undefined behavior unless explicitly initialized.
25pub struct Bytes {
26 /// The buffer used to store data.
27 controller: Box<dyn AllocationController>,
28 /// The length of data actually used and initialized in the current buffer.
29 len: usize,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33/// The kind of allocation behind the [Bytes] type.
34pub enum AllocationProperty {
35 /// A file is used to store the data.
36 File,
37 /// The native allocator of Rust is used.
38 Native,
39 /// Pinned memory is used.
40 Pinned,
41 /// The data still lives on a compute device and is only copied to host memory
42 /// lazily on first access (see [`ComputeClient::read_lazy`](https://docs.rs/cubecl-runtime)).
43 Device,
44 /// Another kind of memory is used.
45 Other,
46}
47
48/// Error when splitting an allocation.
49#[derive(Debug, Clone, Copy)]
50pub enum SplitError {
51 /// The offset isn't valid.
52 InvalidOffset,
53 /// The operation isn't supported.
54 Unsupported,
55}
56
57/// Error when taking a [view](Bytes::view) into an allocation.
58#[derive(Debug, Clone, Copy)]
59pub enum ViewError {
60 /// The range is out of bounds.
61 InvalidRange,
62 /// The backend can't produce a zero-copy window.
63 Unsupported,
64}
65
66/// Controls how [`Bytes::split`] behaves when the allocation can't be split in place.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum SplitPolicy {
69 /// Prefer zero-copy splits, allowing the allocation to be shared behind an
70 /// [`Arc`] when it can't be split natively.
71 ///
72 /// Mutating a resulting half then triggers a copy-on-write. This is the best
73 /// choice for read-only or clone-heavy use cases, where the extra copy is
74 /// never paid.
75 Shared,
76 /// Ensure both halves are independently owned, copying eagerly when a native
77 /// split isn't possible.
78 ///
79 /// This avoids a later copy-on-write surprise when the halves will be
80 /// mutated. Prefer it when you split *in order to* mutate, so the copy is
81 /// paid once, up front, instead of lazily on first write.
82 Owned,
83}
84
85/// Defines how an ``Allocation`` can be controlled.
86///
87/// This trait enables type erasure of the allocator after an ``Allocation`` is created, while still
88/// providing methods to modify or manage an existing ``Allocation``.
89pub trait AllocationController {
90 /// The alignment this allocation was created with.
91 fn alloc_align(&self) -> usize;
92
93 /// Returns memory property for the current allocation.
94 fn property(&self) -> AllocationProperty;
95
96 /// Returns a mutable view of the memory of the whole allocation, subject to `policy`.
97 ///
98 /// Returns [`AccessError::WouldCopy`] if satisfying the access would require a copy
99 /// (e.g. copy-on-write of shared storage) that `policy` forbids, or [`AccessError::Read`]
100 /// if materializing lazy storage fails.
101 ///
102 /// # Safety
103 ///
104 /// Must only write initialized data to the buffer.
105 unsafe fn memory_mut(
106 &mut self,
107 policy: AccessPolicy,
108 ) -> Result<&mut [MaybeUninit<u8>], AccessError>;
109
110 /// Returns a view of the memory of the whole allocation, subject to `policy`.
111 ///
112 /// Returns [`AccessError::WouldCopy`] if satisfying the access would require a copy
113 /// (e.g. materializing lazy storage) that `policy` forbids, or [`AccessError::Read`] if
114 /// materializing fails.
115 fn memory(&self, policy: AccessPolicy) -> Result<&[MaybeUninit<u8>], AccessError>;
116
117 /// The total byte capacity of the allocation, WITHOUT materializing lazy storage.
118 ///
119 /// The default is correct for already-resident controllers (where `memory()` is free);
120 /// lazy controllers must override it to report their size cheaply so that querying the
121 /// capacity never triggers a copy.
122 fn capacity(&self) -> usize {
123 match self.memory(AccessPolicy::allow_copy()) {
124 Ok(memory) => memory.len(),
125 // A resident controller using this default must never error here; a lazy
126 // controller is expected to override `capacity` and not reach this path.
127 Err(err) => {
128 debug_assert!(false, "capacity(): resident host access failed: {err:?}");
129 0
130 }
131 }
132 }
133
134 /// Splits the current allocation in multiple separate allocations.
135 #[allow(clippy::type_complexity)]
136 fn split(
137 &mut self,
138 _offset: usize,
139 ) -> Result<(Box<dyn AllocationController>, Box<dyn AllocationController>), SplitError> {
140 Err(SplitError::Unsupported)
141 }
142
143 /// Returns a zero-copy controller over the sub-range `[start, end)` of this
144 /// allocation when the backend supports cheap sub-views (files and shared
145 /// buffers), borrowing `&self`. Returns `None` otherwise.
146 fn view(&self, _start: usize, _end: usize) -> Option<Box<dyn AllocationController>> {
147 None
148 }
149
150 /// Duplicates the current allocation with a clone on write strategy if the allocation
151 /// controller supports it.
152 fn duplicate(&self) -> Option<Box<dyn AllocationController>> {
153 None
154 }
155
156 /// Reads the data from the current allocation controller and copy its content into the provided
157 /// buffer.
158 ///
159 /// # Safety
160 ///
161 /// Ensures the length provided reflect initialized values in the current allocation controller.
162 unsafe fn copy_into(&self, buf: &mut [u8]) {
163 let len = buf.len();
164 let memory = self
165 .memory(AccessPolicy::default())
166 .expect("copy_into: host access failed");
167 let memory_slice = &memory[0..len];
168
169 // SAFETY: By construction, bytes up to len are initialized.
170 let data = unsafe {
171 core::slice::from_raw_parts(memory_slice.as_ptr().cast(), memory_slice.len())
172 };
173 buf.copy_from_slice(data);
174 }
175
176 /// Extends the provided ``Allocation`` to a new size with specified alignment.
177 ///
178 /// # Errors
179 ///
180 /// Returns an [`AllocationError`] if the extension fails (e.g., due to insufficient memory or
181 /// unsupported operation by the allocator).
182 #[allow(unused_variables)]
183 fn grow(&mut self, size: usize, align: usize) -> Result<(), AllocationError> {
184 Err(AllocationError::UnsupportedOperation)
185 }
186
187 /// Indicates whether the allocation uses the Rust [alloc](alloc) crate and can be safely
188 /// managed by another data structure.
189 ///
190 /// If `true`, the allocation is not managed by a memory pool and can be safely deallocated
191 /// using the [alloc](alloc) crate.
192 ///
193 /// # Notes
194 ///
195 /// This allows the allocation's pointer to be converted into a native Rust `Vec` without
196 /// requiring a new allocation.
197 ///
198 /// Implementing this incorrectly is unsafe and may lead to undefined behavior.
199 fn try_detach(&mut self) -> Option<NonNull<u8>> {
200 None
201 }
202}
203
204/// Errors that may occur during memory allocation operations.
205///
206/// This enum represents possible failure cases when manipulating an ``Allocation`` using an
207/// [`AllocationController`].
208#[derive(Debug, Clone, PartialEq)]
209pub enum AllocationError {
210 /// The requested allocation operation is not supported by the allocator.
211 ///
212 /// This may occur, for example, when attempting to grow an allocation with an allocator that
213 /// does not support resizing.
214 UnsupportedOperation,
215
216 /// The allocation failed due to insufficient memory.
217 ///
218 /// This typically indicates that the system or allocator could not provide the requested
219 /// amount of memory.
220 OutOfMemory,
221}
222
223impl Bytes {
224 /// Splits the current allocation at the given offset.
225 ///
226 /// Controllers that support a native split (such as files and shared
227 /// buffers) always split in place. When they can't, the behaviour of the
228 /// fallback is selected by `policy`:
229 ///
230 /// - [`SplitPolicy::Shared`] shares the allocation behind an [`Arc`] (via
231 /// [`Self::shared`]) so both halves reference the same backing memory
232 /// without copying. This is what makes [`Self::split`] work for native
233 /// allocations whose element alignment is larger than one byte. Mutating a
234 /// half then triggers a copy-on-write.
235 /// - [`SplitPolicy::Owned`] copies eagerly so each half is independently
236 /// owned and can be mutated without a later copy-on-write.
237 ///
238 /// In both policies, when the allocation can be detached into a `Vec`
239 /// without copying, that zero-copy path is taken first.
240 pub fn split(
241 self,
242 offset: usize,
243 policy: SplitPolicy,
244 ) -> Result<(Bytes, Bytes), (Bytes, SplitError)> {
245 if offset > self.len {
246 return Err((self, SplitError::InvalidOffset));
247 }
248 match policy {
249 SplitPolicy::Shared => self.split_shared(offset),
250 SplitPolicy::Owned => self.split_owned(offset),
251 }
252 }
253
254 /// Split preferring zero-copy: native split, else detach into a `Vec`, else
255 /// share behind an [`Arc`]. See [`SplitPolicy::Shared`].
256 fn split_shared(mut self, offset: usize) -> Result<(Bytes, Bytes), (Bytes, SplitError)> {
257 let right_len = self.len - offset;
258 match self.controller.split(offset) {
259 Ok((left, right)) => unsafe {
260 Ok((
261 Bytes::from_controller(left, offset),
262 Bytes::from_controller(right, right_len),
263 ))
264 },
265 Err(_) => match self.try_into_vec::<u8>() {
266 Ok(mut left) => {
267 let right = left.split_off(offset);
268
269 Ok((Bytes::from_bytes_vec(left), Bytes::from_bytes_vec(right)))
270 }
271 // The allocation can't be detached into a `Vec` (e.g. a native
272 // allocation with element alignment > 1, or already shared
273 // data). Share it behind an `Arc` so both halves reference the
274 // same backing memory without copying.
275 Err(this) => Ok(this.shared_split(offset)),
276 },
277 }
278 }
279
280 /// Split ensuring both halves are independently owned, copying eagerly when
281 /// a zero-copy detach isn't possible. See [`SplitPolicy::Owned`].
282 fn split_owned(self, offset: usize) -> Result<(Bytes, Bytes), (Bytes, SplitError)> {
283 match self.try_into_vec::<u8>() {
284 Ok(mut left) => {
285 let right = left.split_off(offset);
286
287 Ok((Bytes::from_bytes_vec(left), Bytes::from_bytes_vec(right)))
288 }
289 // The allocation can't be detached into a `Vec`. Copy each half into
290 // its own native allocation so they can be mutated without a later
291 // copy-on-write.
292 Err(this) => {
293 let align = this.align();
294 match (
295 Self::try_from_data(align, &this[..offset]),
296 Self::try_from_data(align, &this[offset..]),
297 ) {
298 (Ok(left), Ok(right)) => Ok((left, right)),
299 // A layout error here is practically unreachable: the data
300 // already lives in a valid allocation of this alignment.
301 _ => Err((this, SplitError::Unsupported)),
302 }
303 }
304 }
305 }
306
307 /// Returns the sub-range `[start, end)` as a zero-copy [`Bytes`] window.
308 ///
309 /// The returned [`Bytes`] is independently owned (it shares the backing
310 /// storage by reference count / re-opened file handle rather than borrowing
311 /// `self`), so it can outlive this reference.
312 ///
313 /// Returns [`ViewError::Unsupported`] when the backend can't produce a
314 /// zero-copy window (e.g. a plain heap allocation); [`Self::shared`] it first
315 /// to make views available.
316 pub fn view(&self, start: usize, end: usize) -> Result<Bytes, ViewError> {
317 if start > end || end > self.len {
318 return Err(ViewError::InvalidRange);
319 }
320 let len = end - start;
321
322 match self.controller.view(start, end) {
323 // SAFETY: the sub-view controller reports exactly `len` bytes.
324 Some(controller) => Ok(unsafe { Bytes::from_controller(controller, len) }),
325 None => Err(ViewError::Unsupported),
326 }
327 }
328
329 /// Shares the current allocation behind an [`Arc`], returning a new [`Bytes`]
330 /// that references the same data without copying.
331 ///
332 /// Cloning and [splitting](Self::split) the returned [`Bytes`] is cheap
333 /// (reference counted, zero-copy). Because the data is shared,
334 /// [`Self::try_into_vec`] will never succeed on the result, and mutating it
335 /// triggers a copy-on-write into a private buffer.
336 pub fn shared(self) -> Self {
337 let len = self.len;
338 let controller = SharedAllocationController::new(Arc::new(self), 0, len);
339
340 Self {
341 controller: Box::new(controller),
342 len,
343 }
344 }
345
346 /// Shares the allocation behind an [`Arc`] and returns two zero-copy views
347 /// split at `offset`. The caller must ensure `offset <= self.len`.
348 fn shared_split(self, offset: usize) -> (Bytes, Bytes) {
349 let len = self.len;
350 let inner = Arc::new(self);
351 let left = SharedAllocationController::new(inner.clone(), 0, offset);
352 let right = SharedAllocationController::new(inner, offset, len - offset);
353
354 // SAFETY: each view reports exactly its own length as initialized memory.
355 unsafe {
356 (
357 Bytes::from_controller(Box::new(left), offset),
358 Bytes::from_controller(Box::new(right), len - offset),
359 )
360 }
361 }
362
363 #[cfg(feature = "std")]
364 /// Creates bytes from a file at the given offset of the given size.
365 pub fn from_file<P: Into<std::path::PathBuf>>(file: P, size: u64, offset: u64) -> Self {
366 let controller = crate::bytes::file::FileAllocationController::new(file, size, offset);
367
368 Self {
369 controller: Box::new(controller),
370 len: size as usize,
371 }
372 }
373
374 /// Creates bytes from a shared [`bytes::Bytes`] buffer (zero-copy).
375 ///
376 /// This is useful for zero-copy tensor loading from:
377 /// - Static embedded data via [`bytes::Bytes::from_static`]
378 /// - Memory-mapped files
379 /// - Any other [`bytes::Bytes`] source
380 ///
381 /// The allocation property is used by GPU backends to optimize data transfers:
382 /// - [`AllocationProperty::File`]: Uses pinned memory staging buffers for faster
383 /// DMA transfers (useful for memory-mapped files)
384 /// - [`AllocationProperty::Native`]: Data is in heap memory
385 /// - [`AllocationProperty::Other`]: Unknown backing storage
386 ///
387 /// # Example
388 ///
389 /// ```
390 /// use cubecl_environment::bytes::{Bytes, AllocationProperty};
391 ///
392 /// // Memory-mapped file data - use File property for optimized GPU transfers
393 /// let mmap_bytes = bytes::Bytes::from_static(&[1, 2, 3, 4]); // pretend this is mmap
394 /// let bytes = Bytes::from_shared(mmap_bytes, AllocationProperty::File);
395 /// assert!(matches!(bytes.property(), AllocationProperty::File));
396 /// ```
397 #[cfg(feature = "shared-bytes")]
398 pub fn from_shared(bytes: bytes::Bytes, property: AllocationProperty) -> Self {
399 let len = bytes.len();
400 let controller =
401 crate::bytes::shared::SharedBytesAllocationController::new(bytes, property);
402
403 Self {
404 controller: Box::new(controller),
405 len,
406 }
407 }
408
409 /// The size of the allocation.
410 ///
411 /// # Notes
412 ///
413 /// This is used so that calling `bytes.len()` doesn't trigger [Deref], which may be expensive.
414 #[allow(clippy::len_without_is_empty)]
415 pub fn len(&self) -> usize {
416 self.len
417 }
418
419 /// Read the bytes, configured by `reader`. Returns the initialized `[0, len)` slice.
420 ///
421 /// Unlike [`Deref`], this is fallible: it surfaces an [`AccessError`] instead of
422 /// panicking, and a [`Reader::no_copy`] reader returns [`AccessError::WouldCopy`] rather
423 /// than silently materializing lazy storage.
424 pub fn read(&self, reader: Reader) -> Result<&[u8], AccessError> {
425 let memory = &self.controller.memory(reader.policy)?[0..self.len];
426 // SAFETY: bytes up to `len` are initialized by construction.
427 Ok(unsafe { core::slice::from_raw_parts(memory.as_ptr().cast(), memory.len()) })
428 }
429
430 /// Mutably access the bytes, configured by `writer`. Returns the initialized `[0, len)`
431 /// slice. A [`Writer::no_copy`] writer fails with [`AccessError::WouldCopy`] on
432 /// still-shared buffers instead of triggering copy-on-write.
433 pub fn write(&mut self, writer: Writer) -> Result<&mut [u8], AccessError> {
434 let len = self.len;
435 // SAFETY: we only ever expose initialized memory in `[0, len)`.
436 let memory = unsafe { self.controller.memory_mut(writer.policy) }?;
437 let memory = &mut memory[0..len];
438 // SAFETY: bytes up to `len` are initialized by construction.
439 Ok(unsafe { core::slice::from_raw_parts_mut(memory.as_mut_ptr().cast(), memory.len()) })
440 }
441
442 /// Default host access (allow copy), panicking on failure — backs [`Deref`].
443 fn memory_default(&self) -> &[MaybeUninit<u8>] {
444 self.controller
445 .memory(AccessPolicy::default())
446 .expect("bytes: host access failed")
447 }
448
449 /// Default mutable host access (allow copy-on-write), panicking on failure — backs
450 /// [`DerefMut`].
451 ///
452 /// # Safety
453 ///
454 /// Caller must only write initialized data into the returned slice.
455 unsafe fn memory_mut_default(&mut self) -> &mut [MaybeUninit<u8>] {
456 unsafe { self.controller.memory_mut(AccessPolicy::default()) }
457 .expect("bytes: host access failed")
458 }
459
460 /// Copy the data from the current allocation to the provided [Bytes].
461 pub fn copy_into(&self, other: &mut Self) {
462 unsafe {
463 self.controller.copy_into(other);
464 }
465 }
466
467 /// Retrieves the allocation property of the given allocation.
468 pub fn property(&self) -> AllocationProperty {
469 self.controller.property()
470 }
471 /// Creates the type from its raw parts.
472 ///
473 /// # Safety
474 ///
475 /// This function is highly unsafe, the provided length must be the actual number of bytes
476 /// initialized in the `AllocationController`.
477 ///
478 /// Note: we intentionally do not assert `len <= controller.memory().len()` here, as
479 /// `memory()` may force a lazy controller to materialize its data (e.g. a device-backed
480 /// allocation), defeating the laziness this constructor is meant to preserve.
481 pub unsafe fn from_controller(controller: Box<dyn AllocationController>, len: usize) -> Self {
482 Self { controller, len }
483 }
484
485 /// Create a sequence of [Bytes] from the memory representation of an unknown type of elements.
486 /// Prefer this over [`Self::from_elems`] when the datatype is not statically known and erased at runtime.
487 pub fn from_bytes_vec(bytes: Vec<u8>) -> Self {
488 let mut bytes = Self::from_elems(bytes);
489 // TODO: this method could be datatype aware and enforce a less strict alignment.
490 // On most platforms, this alignment check is fulfilled either way though, so
491 // the benefits of potentially saving a memcopy are negligible.
492 bytes
493 .try_enforce_runtime_align(default_controller::MAX_ALIGN)
494 .unwrap();
495 bytes
496 }
497
498 /// Erase the element type of a vector by converting into a sequence of [Bytes].
499 ///
500 /// In case the element type is not statically known at runtime, prefer to use [`Self::from_bytes_vec`].
501 pub fn from_elems<E>(elems: Vec<E>) -> Self
502 where
503 // NoUninit implies Copy
504 E: bytemuck::NoUninit + Send + Sync,
505 {
506 let _: () = const {
507 assert!(
508 core::mem::align_of::<E>() <= default_controller::MAX_ALIGN,
509 "element type not supported due to too large alignment"
510 );
511 };
512
513 // Note: going through a Box as in Vec::into_boxed_slice would re-allocate on excess capacity. Avoid that.
514 let byte_len = elems.len() * core::mem::size_of::<E>();
515 let controller = NativeAllocationController::from_elems(elems);
516
517 Self {
518 controller: Box::new(controller),
519 len: byte_len,
520 }
521 }
522
523 /// Extend the byte buffer from a slice of bytes
524 pub fn extend_from_byte_slice(&mut self, bytes: &[u8]) {
525 self.extend_from_byte_slice_aligned(bytes, default_controller::MAX_ALIGN)
526 }
527
528 /// Get the total capacity, in bytes, of the wrapped allocation.
529 ///
530 /// This never materializes lazy storage (see [`AllocationController::capacity`]).
531 pub fn capacity(&self) -> usize {
532 self.controller.capacity()
533 }
534
535 /// Convert the bytes back into a vector. This requires that the type has the same alignment as the element
536 /// type this [Bytes] was initialized with.
537 /// This only returns with Ok(_) if the conversion can be done without a memcopy
538 pub fn try_into_vec<E: bytemuck::CheckedBitPattern + bytemuck::NoUninit>(
539 mut self,
540 ) -> Result<Vec<E>, Self> {
541 // See if the length is compatible.
542 // Use immutable validation to avoid triggering copy-on-write for SharedBytesAllocationController.
543 // Note: This still calls memory() via Deref, which may trigger file I/O for FileAllocationController.
544 let Ok(data) = bytemuck::checked::try_cast_slice::<_, E>(&self) else {
545 return Err(self);
546 };
547 let length = data.len();
548 // If so, try to convert the allocation to a vec. The data is already host-resident
549 // here (the cast above went through `Deref`), so `capacity()` is free.
550 let byte_capacity = self.controller.capacity();
551
552 let Some(capacity) = byte_capacity.checked_div(size_of::<E>()) else {
553 return Err(self);
554 };
555 if capacity * size_of::<E>() != byte_capacity {
556 return Err(self);
557 };
558 // Vec::from_raw_parts requires that the pointer was allocated with
559 // Layout::array::<E>(capacity). On drop, Vec deallocates with that
560 // layout. If our allocation used a different alignment, the dealloc
561 // layout won't match and that's UB per the GlobalAlloc contract:
562 // https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html#safety-1
563 if self.controller.alloc_align() != align_of::<E>() {
564 return Err(self);
565 }
566
567 let Some(ptr) = self.controller.try_detach() else {
568 return Err(self);
569 };
570
571 // SAFETY:
572 // - ptr was allocated by the global allocator as per type-invariant
573 // - alloc_align == align_of::<E> (checked above), so Vec will dealloc
574 // with the same layout as the original allocation.
575 // - capacity * size_of::<E> == layout.size()
576 // - 0 <= capacity
577 // - length was computed from the bytemuck-ed slice into this allocation
578 // - the layout represents a valid allocation, hence has allocation size less than isize::MAX
579 let vec = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast(), length, capacity) };
580 Ok(vec)
581 }
582
583 /// Get the alignment of the wrapped allocation.
584 pub fn align(&self) -> usize {
585 self.controller.alloc_align()
586 }
587
588 /// Extend the byte buffer from a slice of bytes.
589 ///
590 /// This is used internally to preserve the alignment of the memory layout when matching elements
591 /// are extended. Prefer [`Self::extend_from_byte_slice`] otherwise.
592 pub fn extend_from_byte_slice_aligned(&mut self, bytes: &[u8], align: usize) {
593 debug_assert!(align.is_power_of_two(), "alignment must be a power of two");
594 debug_assert!(
595 align <= default_controller::MAX_ALIGN,
596 "alignment exceeds maximum supported alignment"
597 );
598
599 let additional = bytes.len();
600 self.reserve(additional, align);
601
602 let len = self.len();
603 let new_cap = len.wrapping_add(additional); // Can not overflow, as we've just successfully reserved sufficient space for it
604 debug_assert!(
605 new_cap <= self.capacity(),
606 "new capacity must not exceed allocated capacity"
607 );
608
609 unsafe {
610 // SAFETY: Will only write initialized memory to this ptr.
611 let uninit_spare = &mut self.memory_mut_default()[len..new_cap];
612 // SAFETY: reinterpreting the slice as a MaybeUninit<u8>.
613 // See also #![feature(maybe_uninit_write_slice)], which would replace this with safe code
614 uninit_spare.copy_from_slice(core::slice::from_raw_parts(
615 bytes.as_ptr().cast(),
616 additional,
617 ));
618 };
619 self.len = new_cap;
620 }
621
622 /// Copy an existing slice of data into Bytes that are aligned to `align`
623 fn try_from_data(align: usize, data: &[u8]) -> Result<Self, LayoutError> {
624 let controller = NativeAllocationController::alloc_with_data(data, align)?;
625
626 Ok(Self {
627 controller: Box::new(controller),
628 len: data.len(),
629 })
630 }
631
632 /// Ensure the allocation's reported alignment is at least `align`, reallocating
633 /// into a fresh controller if not. We check the controller's reported alignment
634 /// (not the raw pointer) because downstream callers such as `try_into_vec::<E>`
635 /// depend on `alloc_align()` matching the element alignment.
636 fn try_enforce_runtime_align(&mut self, align: usize) -> Result<(), LayoutError> {
637 if self.controller.alloc_align() >= align {
638 return Ok(());
639 }
640 *self = Self::try_from_data(align, self)?;
641 Ok(())
642 }
643
644 fn reserve(&mut self, additional: usize, align: usize) {
645 debug_assert!(
646 align <= default_controller::MAX_ALIGN,
647 "alignment exceeds maximum supported alignment"
648 );
649
650 let needs_to_grow = additional > self.capacity().wrapping_sub(self.len());
651 if !needs_to_grow {
652 return;
653 }
654 let Some(required_cap) = self.len().checked_add(additional) else {
655 default_controller::alloc_overflow()
656 };
657 // guarantee exponential growth for amortization
658 let new_cap = required_cap.max(self.capacity() * 2);
659 let new_cap = new_cap.max(align); // Small allocations would be pointless
660
661 match self.controller.grow(new_cap, align) {
662 Ok(()) => {}
663 Err(_err) => {
664 let new_controller: Box<dyn AllocationController> = Box::new(
665 NativeAllocationController::alloc_with_capacity(new_cap, align).unwrap(),
666 );
667 let mut new_bytes = Self {
668 controller: new_controller,
669 len: self.len,
670 };
671 // Copy memory into new bytes.
672 new_bytes.copy_from_slice(&*self);
673 *self = new_bytes;
674 }
675 }
676 }
677}
678
679impl Deref for Bytes {
680 type Target = [u8];
681
682 fn deref(&self) -> &Self::Target {
683 let memory = &self.memory_default()[0..self.len];
684 // SAFETY: By construction, bytes up to len are initialized.
685 unsafe { core::slice::from_raw_parts(memory.as_ptr().cast(), memory.len()) }
686 }
687}
688
689impl DerefMut for Bytes {
690 fn deref_mut(&mut self) -> &mut Self::Target {
691 let len = self.len;
692 // SAFETY: We only expose this as initialized memory so cannot write uninitialized memory to this slice.
693 let slice = unsafe { self.memory_mut_default() };
694 // Get initialized part of this slice.
695 let memory = &mut slice[0..len];
696 // SAFETY: By construction, bytes up to len are initialized.
697 unsafe { core::slice::from_raw_parts_mut(memory.as_mut_ptr().cast(), memory.len()) }
698 }
699}
700
701// SAFETY: Bytes behaves like a Box<[u8]> and can contain only elements that are themselves Send
702unsafe impl Send for Bytes {}
703// SAFETY: Bytes behaves like a Box<[u8]> and can contain only elements that are themselves Sync
704unsafe impl Sync for Bytes {}
705
706fn debug_from_fn<F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result>(
707 f: F,
708) -> impl core::fmt::Debug {
709 // See also: std::fmt::from_fn
710 struct FromFn<F>(F);
711 impl<F> core::fmt::Debug for FromFn<F>
712 where
713 F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
714 {
715 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
716 (self.0)(f)
717 }
718 }
719 FromFn(f)
720}
721
722impl core::fmt::Debug for Bytes {
723 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
724 let data = &**self;
725 let fmt_data = move |f: &mut core::fmt::Formatter<'_>| {
726 if data.len() > 3 {
727 // There is a nightly API `debug_more_non_exhaustive` which has `finish_non_exhaustive`
728 f.debug_list().entries(&data[0..3]).entry(&"...").finish()
729 } else {
730 f.debug_list().entries(data).finish()
731 }
732 };
733 f.debug_struct("Bytes")
734 .field("data", &debug_from_fn(fmt_data))
735 .field("len", &self.len)
736 .finish()
737 }
738}
739
740impl serde::Serialize for Bytes {
741 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
742 where
743 S: serde::Serializer,
744 {
745 serde_bytes::serialize(self.deref(), serializer)
746 }
747}
748
749impl<'de> serde::Deserialize<'de> for Bytes {
750 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
751 where
752 D: serde::Deserializer<'de>,
753 {
754 #[cold]
755 fn too_large<E: serde::de::Error>(len: usize, align: usize) -> E {
756 // max_length = largest multiple of align that is <= isize::MAX
757 // align is a power of 2, hence a multiple has the lower bits unset. Mask them off to find the largest multiple
758 let max_length = (isize::MAX as usize) & !(align - 1);
759 E::custom(core::format_args!(
760 "length too large: {len}. Expected at most {max_length} bytes"
761 ))
762 }
763
764 // TODO: we can possibly avoid one copy here by deserializing into an existing, correctly aligned, slice of bytes.
765 // We might not be able to predict the length of the data, hence it's far more convenient to let `Vec` handle the growth and re-allocations.
766 // Further, on a lot of systems, the allocator naturally aligns data to some reasonably large alignment, where no further copy is then
767 // necessary.
768 let data: Vec<u8> = serde_bytes::deserialize(deserializer)?;
769 // When deserializing, we over-align the data. This saves us from having to encode the alignment (which is platform-dependent in any case).
770 // If we had more context information here, we could enforce some (smaller) alignment per data type. But this information is only available
771 // in `TensorData`. Moreover it depends on the Deserializer there whether the datatype or data comes first.
772 let align = default_controller::MAX_ALIGN;
773 let mut bytes = Self::from_elems(data);
774 bytes
775 .try_enforce_runtime_align(align)
776 .map_err(|_| too_large(bytes.len(), align))?;
777 Ok(bytes)
778 }
779}
780
781impl Clone for Bytes {
782 fn clone(&self) -> Self {
783 if let Some(controller) = self.controller.duplicate() {
784 return Self {
785 controller,
786 len: self.len,
787 };
788 }
789
790 // unwrap here: the layout is valid as it has the alignment & size of self
791 Self::try_from_data(self.align(), self.deref()).unwrap()
792 }
793}
794
795impl PartialEq for Bytes {
796 fn eq(&self, other: &Self) -> bool {
797 self.deref() == other.deref()
798 }
799}
800
801impl Eq for Bytes {}
802
803// The type is no-std; its tests need std (test_log, std collections).
804#[cfg(all(test, feature = "std"))]
805mod tests {
806 use super::{Bytes, SplitPolicy, ViewError};
807 use alloc::{vec, vec::Vec};
808
809 const _CONST_ASSERTS: fn() = || {
810 fn test_send<T: Send>() {}
811 fn test_sync<T: Sync>() {}
812 test_send::<Bytes>();
813 test_sync::<Bytes>();
814 };
815
816 fn test_serialization_roundtrip(bytes: &Bytes) {
817 let mut serialized = Vec::new();
818 ciborium::ser::into_writer(bytes, &mut serialized).expect("serialization to succeed");
819 let roundtripped: Bytes = ciborium::de::from_reader(&mut serialized.as_slice())
820 .expect("deserialization to succeed");
821 assert_eq!(
822 bytes, &roundtripped,
823 "roundtripping through serialization didn't lead to equal Bytes"
824 );
825 }
826
827 #[test_log::test]
828 fn test_serialization() {
829 test_serialization_roundtrip(&Bytes::from_elems::<i32>(vec![]));
830 test_serialization_roundtrip(&Bytes::from_elems(vec![0xdead, 0xbeaf]));
831 }
832
833 #[test_log::test]
834 fn test_into_vec() {
835 // We test an edge case here, where the capacity (but not actual size) makes it impossible to convert to a vec
836 let mut bytes = Vec::with_capacity(6);
837 let actual_cap = bytes.capacity();
838 bytes.extend_from_slice(&[0, 1, 2, 3]);
839 let mut bytes = Bytes::from_elems::<u8>(bytes);
840
841 bytes = bytes
842 .try_into_vec::<[u8; 0]>()
843 .expect_err("Conversion should not succeed for a zero-sized type");
844 if actual_cap % 4 != 0 {
845 // We most likely get actual_cap == 6, we can't force Vec to actually do that. Code coverage should complain if the actual test misses this
846 bytes = bytes.try_into_vec::<[u8; 4]>().err().unwrap_or_else(|| {
847 panic!("Conversion should not succeed due to capacity {actual_cap} not fitting a whole number of elements");
848 });
849 }
850 bytes = bytes
851 .try_into_vec::<u16>()
852 .expect_err("Conversion should not succeed due to mismatched alignment");
853 bytes = bytes.try_into_vec::<[u8; 3]>().expect_err(
854 "Conversion should not succeed due to size not fitting a whole number of elements",
855 );
856 let bytes = bytes.try_into_vec::<[u8; 2]>().expect("Conversion should succeed for bit-convertible types of equal alignment and compatible size");
857 assert_eq!(bytes, &[[0, 1], [2, 3]]);
858 }
859
860 #[test_log::test]
861 fn test_grow() {
862 let mut bytes = Bytes::from_elems::<u8>(vec![]);
863 bytes.extend_from_byte_slice(&[0, 1, 2, 3]);
864 assert_eq!(bytes[..], [0, 1, 2, 3][..]);
865
866 let mut bytes = Bytes::from_elems(vec![42u8; 4]);
867 bytes.extend_from_byte_slice(&[0, 1, 2, 3]);
868 assert_eq!(bytes[..], [42, 42, 42, 42, 0, 1, 2, 3][..]);
869 }
870
871 #[test_log::test]
872 fn test_large_elems() {
873 let mut bytes = Bytes::from_elems(vec![42u128]);
874 const TEST_BYTES: [u8; 16] = [
875 0x12, 0x90, 0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12, 0x90, 0x78, 0x56,
876 0x34, 0x12,
877 ];
878 bytes.extend_from_byte_slice(&TEST_BYTES);
879 let vec = bytes.try_into_vec::<u128>().unwrap();
880 assert_eq!(vec, [42u128, u128::from_ne_bytes(TEST_BYTES)]);
881 }
882
883 #[test_log::test]
884 fn test_split_and_use() {
885 let bytes = Bytes::from_elems(vec![0u8, 1, 2, 3, 4, 5, 6, 7]);
886 let (left, right) = bytes.split(4, SplitPolicy::Shared).unwrap();
887 assert_eq!(&left[..], &[0, 1, 2, 3]);
888 assert_eq!(&right[..], &[4, 5, 6, 7]);
889 let left2 = left.clone();
890 assert_eq!(&left2[..], &[0, 1, 2, 3]);
891 }
892
893 #[test_log::test]
894 fn test_split_at_zero() {
895 let bytes = Bytes::from_elems(vec![10u8, 20, 30, 40]);
896 let (left, right) = bytes.split(0, SplitPolicy::Shared).unwrap();
897 assert_eq!(left.len(), 0);
898 assert_eq!(&right[..], &[10, 20, 30, 40]);
899 }
900
901 /// A native allocation with element alignment > 1 can't be detached into a
902 /// `Vec<u8>`, so it used to fail to split. With [`SplitPolicy::Shared`] it
903 /// now shares behind an `Arc`.
904 #[test_log::test]
905 fn test_split_native_over_aligned_shared() {
906 let bytes = Bytes::from_elems(vec![0u32, 1, 2, 3]);
907 let (left, right) = bytes.split(8, SplitPolicy::Shared).unwrap();
908 assert_eq!(&left[..], &[0, 0, 0, 0, 1, 0, 0, 0]);
909 assert_eq!(&right[..], &[2, 0, 0, 0, 3, 0, 0, 0]);
910 // The shared halves stay usable after cloning.
911 assert_eq!(&left.clone()[..], &left[..]);
912 }
913
914 /// [`SplitPolicy::Owned`] copies eagerly so each half is independently owned
915 /// and can be mutated in place (no copy-on-write occurs on first write).
916 #[test_log::test]
917 fn test_split_native_over_aligned_owned() {
918 let bytes = Bytes::from_elems(vec![0u32, 1, 2, 3]);
919 let (mut left, mut right) = bytes.split(8, SplitPolicy::Owned).unwrap();
920 left[0] = 9;
921 right[0] = 8;
922 assert_eq!(&left[..], &[9, 0, 0, 0, 1, 0, 0, 0]);
923 assert_eq!(&right[..], &[8, 0, 0, 0, 3, 0, 0, 0]);
924 }
925
926 #[test_log::test]
927 fn test_split_at_end() {
928 let bytes = Bytes::from_elems(vec![10u8, 20, 30, 40]);
929 let (left, right) = bytes.split(4, SplitPolicy::Owned).unwrap();
930 assert_eq!(&left[..], &[10, 20, 30, 40]);
931 assert_eq!(right.len(), 0);
932 }
933
934 /// A plain heap allocation has no zero-copy window, so `view` reports
935 /// [`ViewError::Unsupported`] rather than silently copying. `view` only
936 /// borrows, so the original stays usable.
937 #[test_log::test]
938 fn test_view_heap_unsupported() {
939 let bytes = Bytes::from_elems(vec![0u8, 1, 2, 3, 4, 5, 6, 7]);
940 assert!(matches!(bytes.view(2, 5), Err(ViewError::Unsupported)));
941 // The original is still readable.
942 assert_eq!(&bytes[..], &[0, 1, 2, 3, 4, 5, 6, 7]);
943 }
944
945 /// Sharing first (the caller owns the data) makes a zero-copy `view`
946 /// available; mutating the window then copies on write, leaving the original
947 /// untouched.
948 #[test_log::test]
949 fn test_view_shared_then_mutate() {
950 let shared = Bytes::from_elems(vec![0u32, 1, 2, 3]).shared();
951 let mut view = shared.view(4, 12).unwrap();
952 view[0] = 9;
953 assert_eq!(&view[..], &[9, 0, 0, 0, 2, 0, 0, 0]);
954 // The original is untouched by the mutation.
955 assert_eq!(
956 &shared[..],
957 &[0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]
958 );
959 }
960
961 #[test_log::test]
962 fn test_view_invalid_range() {
963 let bytes = Bytes::from_elems(vec![10u8, 20, 30, 40]);
964 // end past the length, and start > end, are both rejected.
965 assert!(bytes.view(0, 5).is_err());
966 assert!(bytes.view(3, 1).is_err());
967 }
968
969 /// `from_bytes_vec` enforces `MAX_ALIGN`, so converting the result to a Vec of
970 /// any type whose alignment is `<= MAX_ALIGN` must succeed. We iterate so the
971 /// test hits a range of underlying allocator addresses.
972 #[test_log::test]
973 fn test_from_bytes_vec_try_into_vec_aligned_type() {
974 for _ in 0..64 {
975 let bytes = Bytes::from_bytes_vec(vec![0u8; 16]);
976 let vec: Vec<u128> = bytes
977 .try_into_vec::<u128>()
978 .expect("MAX_ALIGN-aligned bytes must convert to Vec<u128>");
979 assert_eq!(vec.len(), 1);
980 }
981 }
982
983 #[test_log::test]
984 fn test_many_extends_with_growth() {
985 let mut bytes = Bytes::from_elems::<u8>(vec![]);
986 for i in 0u8..=255 {
987 bytes.extend_from_byte_slice(&[i]);
988 }
989 assert_eq!(bytes.len(), 256);
990 assert_eq!(bytes[0], 0);
991 assert_eq!(bytes[255], 255);
992 }
993}