Skip to main content

ruda_runtime/runtime/storage/
base.rs

1use crate::runtime::{memory_management::ManagedMemoryBinding, server::IoError, storage_id_type};
2use core::fmt::Debug;
3
4// This ID is used to map a handle to its actual data.
5storage_id_type!(StorageId);
6
7impl core::fmt::Display for StorageId {
8    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
9        f.write_fmt(format_args!("StorageId({})", self.value))
10    }
11}
12
13/// Defines if data uses a full memory chunk or a slice of it.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct StorageUtilization {
16    /// The offset in bytes from the chunk start.
17    pub offset: u64,
18    /// The size of the slice in bytes.
19    pub size: u64,
20}
21
22/// Contains the [storage id](StorageId) of a resource and the way it is used.
23#[derive(new, Clone, Debug)]
24pub struct StorageHandle {
25    /// Storage id.
26    pub id: StorageId,
27    /// How the storage is used.
28    pub utilization: StorageUtilization,
29}
30
31impl StorageHandle {
32    /// Returns the size the handle is pointing to in memory.
33    ///
34    /// # Notes
35    ///
36    /// The result considers the offset.
37    pub fn size(&self) -> u64 {
38        self.utilization.size
39    }
40
41    /// Returns the offset of the handle.
42    pub fn offset(&self) -> u64 {
43        self.utilization.offset
44    }
45
46    /// Increase the current offset with the given value in bytes.
47    pub fn offset_start(&self, offset_bytes: u64) -> Self {
48        let utilization = StorageUtilization {
49            offset: self.offset().checked_add(offset_bytes)
50                .expect("storage offset overflow"),
51            size: self.size().checked_sub(offset_bytes)
52                .expect("storage prefix exceeds resource size"),
53        };
54
55        Self {
56            id: self.id,
57            utilization,
58        }
59    }
60
61    /// Reduce the size of the memory handle..
62    pub fn offset_end(&self, offset_bytes: u64) -> Self {
63        let utilization = StorageUtilization {
64            offset: self.offset(),
65            size: self.size().checked_sub(offset_bytes)
66                .expect("storage suffix exceeds resource size"),
67        };
68
69        Self {
70            id: self.id,
71            utilization,
72        }
73    }
74}
75
76/// Storage types are responsible for allocating and deallocating memory.
77pub trait ComputeStorage: Send {
78    /// The resource associated type determines the way data is implemented and how
79    /// it can be accessed by kernels.
80    type Resource: Send;
81
82    /// The alignment memory is allocated with in this storage.
83    fn alignment(&self) -> usize;
84
85    /// Returns the underlying resource for a specified storage handle
86    fn get(&mut self, handle: &StorageHandle) -> Self::Resource;
87
88    /// Resolve a resource while retaining its address lease in adaptive mode.
89    fn get_pinned(&mut self, handle: &StorageHandle, _binding: ManagedMemoryBinding) -> Self::Resource {
90        self.get(handle)
91    }
92
93    /// Whether this storage implements real relocation copies and completion waits.
94    fn supports_relocation(&self) -> bool { false }
95
96    /// Wait for all users of storage addresses, including work on other streams.
97    fn relocation_barrier(&mut self) -> Result<(), IoError> {
98        Err(IoError::UnsupportedIoOperation { backtrace: ruda_core::backtrace::BackTrace::capture() })
99    }
100
101    /// Enqueue a device-local copy between disjoint, equally sized reservations.
102    fn relocation_copy(&mut self, _source: &StorageHandle, _target: &StorageHandle) -> Result<(), IoError> {
103        Err(IoError::UnsupportedIoOperation { backtrace: ruda_core::backtrace::BackTrace::capture() })
104    }
105
106    /// Enqueue a group of relocation copies, with completion checked separately.
107    fn relocation_copy_batch<'a>(
108        &mut self,
109        copies: impl IntoIterator<Item = (&'a StorageHandle, &'a StorageHandle)>,
110    ) -> Result<(), IoError>
111    where
112        Self: Sized,
113    {
114        for (source, target) in copies { self.relocation_copy(source, target)?; }
115        Ok(())
116    }
117
118    /// Wait for copies even when an earlier enqueue failed. Failure is not completion.
119    fn relocation_complete(&mut self) -> Result<(), IoError> {
120        self.relocation_barrier()
121    }
122
123    /// Allocates `size` units of memory and returns a handle to it
124    fn alloc(&mut self, size: u64) -> Result<StorageHandle, IoError>;
125
126    /// Deallocates the memory pointed by the given storage id.
127    ///
128    /// These deallocations might need to be flushed with [`Self::flush`].
129    fn dealloc(&mut self, id: StorageId);
130
131    /// Flush deallocations when required.
132    fn flush(&mut self);
133}
134
135/// Access to the underlying resource.
136#[derive(new, Debug)]
137pub struct ManagedResource<Resource: Send> {
138    // This handle is here just to keep the underlying allocation alive.
139    // If the underlying allocation becomes invalid, someone else might
140    // allocate into this resource which could lead to bad behaviour.
141    #[allow(unused)]
142    binding: ManagedMemoryBinding,
143    resource: Resource,
144}
145
146impl<Resource: Send> ManagedResource<Resource> {
147    /// access the underlying resource.
148    ///
149    /// # Note
150    ///
151    /// The resource might be bigger than the part required.
152    /// (e.g. a big buffer where the handle only refers to a slice of it).
153    /// Only the part required by the handle is guaranteed to remain,
154    /// other parts of this resource *will* be re-used.
155    pub fn resource(&self) -> &Resource {
156        &self.resource
157    }
158}
159
160
161#[cfg(test)]
162mod handle_safety_tests {
163    use super::*;
164
165    #[test]
166    fn valid_handle_slicing_preserves_bounds() {
167        let handle = StorageHandle::new(StorageId::new(), StorageUtilization { offset: 8, size: 16 });
168        let sliced = handle.offset_start(4).offset_end(3);
169        assert_eq!(sliced.offset(), 12);
170        assert_eq!(sliced.size(), 9);
171        assert_eq!(handle.offset_start(16).size(), 0);
172    }
173
174    #[test]
175    #[should_panic(expected = "storage prefix exceeds resource size")]
176    fn oversized_prefix_never_wraps_in_release_mode() {
177        StorageHandle::new(StorageId::new(), StorageUtilization { offset: 0, size: 1 })
178            .offset_start(2);
179    }
180
181    #[test]
182    #[should_panic(expected = "storage suffix exceeds resource size")]
183    fn oversized_suffix_never_wraps_in_release_mode() {
184        StorageHandle::new(StorageId::new(), StorageUtilization { offset: 0, size: 1 })
185            .offset_end(2);
186    }
187
188    #[test]
189    #[should_panic(expected = "storage offset overflow")]
190    fn offset_overflow_never_wraps_in_release_mode() {
191        StorageHandle::new(StorageId::new(), StorageUtilization { offset: u64::MAX, size: 2 })
192            .offset_start(1);
193    }
194}