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}