Skip to main content

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