hurray_io/stream/sans_io/dest.rs
1//! Where the bytes of a read land.
2//!
3//! ADR-041 § 2 puts the destination on the core's side of the boundary: the core hands the
4//! driver somewhere to fill and never accepts payload bytes the driver placed elsewhere.
5//! The transport's write into that destination is then the only copy.
6//!
7//! § 5 makes the destination's alignment the one the buffer handle *declares*, and ADR-041
8//! left open whether a binding may supply the allocation. It may: [`DestinationAlloc`] is
9//! that seam. A Python read can land directly in a NumPy array without the state machine
10//! knowing, which is what `hurray-python/src/numpy_allocator.rs` already does elsewhere.
11
12use bytes::Bytes;
13
14use crate::aligned::AlignedBuf;
15use crate::{Error, Result};
16
17/// What the core needs for one buffer of the tensor it is reading.
18///
19/// # Examples
20///
21/// ```
22/// use hurray_io::stream::DestinationRequest;
23///
24/// let request = DestinationRequest { index: 0, byte_size: 256, alignment: 64 };
25/// assert_eq!(request.byte_size % u64::from(request.alignment), 0);
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct DestinationRequest {
29 /// Index of the buffer in the descriptor's buffer table, for diagnostics.
30 pub index: usize,
31 /// Exactly how many bytes the handle declares.
32 pub byte_size: u64,
33 /// The alignment the handle declares: a power of two, and at least 64 when
34 /// `byte_size > 0` (`buffer-protocol.md § Minimum Alignment`).
35 pub alignment: u32,
36}
37
38/// One buffer's worth of memory, filled by a driver and then handed to the caller.
39///
40/// Implement this alongside [`DestinationAlloc`] to make reads land in memory your runtime
41/// owns. The address MUST NOT move between [`as_mut_slice`][Destination::as_mut_slice]
42/// calls: a driver may hold a pointer into it across a suspension (ADR-041 § 6a).
43///
44/// # Examples
45///
46/// ```
47/// use bytes::Bytes;
48/// use hurray_io::stream::Destination;
49///
50/// /// A destination that ignores alignment — fine for a test, not for a reader.
51/// struct VecDest(Vec<u8>);
52///
53/// impl Destination for VecDest {
54/// fn as_mut_slice(&mut self) -> &mut [u8] {
55/// &mut self.0
56/// }
57/// fn freeze(self) -> Bytes {
58/// Bytes::from(self.0)
59/// }
60/// }
61///
62/// let mut dest = VecDest(vec![0u8; 4]);
63/// dest.as_mut_slice().copy_from_slice(b"hurr");
64/// assert_eq!(dest.freeze(), Bytes::from_static(b"hurr"));
65/// ```
66pub trait Destination: Send {
67 /// The bytes to fill, exactly `byte_size` long.
68 fn as_mut_slice(&mut self) -> &mut [u8];
69
70 /// Hands the filled memory out as [`Bytes`], without copying it.
71 ///
72 /// Takes `self` by value rather than `Box<Self>`: the core names this type through
73 /// [`DestinationAlloc::Dest`], so a destination is never boxed and never erased.
74 fn freeze(self) -> Bytes;
75}
76
77/// Allocates the destinations a [`StreamReaderCore`][super::StreamReaderCore] fills.
78///
79/// # Examples
80///
81/// ```
82/// use hurray_io::stream::{Destination, DestinationAlloc, SystemDestinations, DestinationRequest};
83///
84/// let mut alloc = SystemDestinations;
85/// let mut dest = alloc
86/// .allocate(DestinationRequest { index: 0, byte_size: 128, alignment: 4096 })?;
87/// assert_eq!(dest.as_mut_slice().len(), 128);
88/// # Ok::<(), hurray_io::Error>(())
89/// ```
90pub trait DestinationAlloc: Send {
91 /// The memory this allocator hands out.
92 ///
93 /// An associated type rather than `Box<dyn Destination>`: the core is generic over the
94 /// allocator, so the destination's concrete type is known throughout. That costs one
95 /// type parameter and saves a heap allocation per buffer — and it keeps the core's
96 /// auto traits (`Sync`, `UnwindSafe`, `RefUnwindSafe`) intact, which a trait object
97 /// would erase from every type that contains a reader.
98 type Dest: Destination;
99
100 /// Allocates one destination, zero-initialised, at the requested alignment.
101 ///
102 /// # Errors
103 ///
104 /// [`Error::UnallocatableBuffer`] when the request cannot be satisfied — a `byte_size`
105 /// beyond the address space, or an `alignment` this allocator cannot meet. A
106 /// descriptor off the wire can ask for either, so this MUST NOT panic.
107 fn allocate(&mut self, request: DestinationRequest) -> Result<Self::Dest>;
108}
109
110/// The default allocator: one host allocation per buffer at the declared alignment.
111///
112/// # Examples
113///
114/// ```
115/// use hurray_io::stream::{Destination, DestinationAlloc, DestinationRequest, SystemDestinations};
116///
117/// let mut dest = SystemDestinations
118/// .allocate(DestinationRequest { index: 0, byte_size: 64, alignment: 64 })?;
119/// assert_eq!(dest.as_mut_slice().len(), 64);
120/// assert_eq!(dest.as_mut_slice().as_ptr() as usize % 64, 0);
121/// # Ok::<(), hurray_io::Error>(())
122/// ```
123#[derive(Debug, Clone, Copy, Default)]
124pub struct SystemDestinations;
125
126impl DestinationAlloc for SystemDestinations {
127 type Dest = AlignedDestination;
128
129 fn allocate(&mut self, request: DestinationRequest) -> Result<Self::Dest> {
130 let buf = AlignedBuf::zeroed(request.byte_size, request.alignment).ok_or(
131 Error::UnallocatableBuffer {
132 index: request.index,
133 byte_size: request.byte_size,
134 alignment: request.alignment,
135 },
136 )?;
137 Ok(AlignedDestination(buf))
138 }
139}
140
141/// What [`SystemDestinations`] hands out: one host allocation at the declared alignment.
142///
143/// A named type because [`DestinationAlloc::Dest`] is part of the public API; the
144/// allocation itself stays an implementation detail.
145///
146/// # Examples
147///
148/// ```
149/// use hurray_io::stream::{Destination, DestinationAlloc, DestinationRequest, SystemDestinations};
150///
151/// let mut dest = SystemDestinations
152/// .allocate(DestinationRequest { index: 0, byte_size: 32, alignment: 128 })?;
153/// dest.as_mut_slice().fill(0xAB);
154/// let frozen = dest.freeze();
155/// assert_eq!(frozen.len(), 32);
156/// assert_eq!(frozen.as_ptr() as usize % 128, 0);
157/// # Ok::<(), hurray_io::Error>(())
158/// ```
159pub struct AlignedDestination(AlignedBuf);
160
161impl Destination for AlignedDestination {
162 fn as_mut_slice(&mut self) -> &mut [u8] {
163 self.0.as_mut_slice()
164 }
165
166 fn freeze(self) -> Bytes {
167 self.0.freeze()
168 }
169}