1use std::sync::{Arc, RwLock};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
37pub struct Rank(pub u32);
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct SymmetricBuffer {
44 pub rank: Rank,
45 pub offset: usize,
46 pub len: usize,
47}
48
49pub trait SymmetricTransport {
54 fn num_ranks(&self) -> u32;
56 fn this_rank(&self) -> Rank;
58
59 fn put(&self, buf: SymmetricBuffer, src: &[u8]) -> Result<(), CollectiveError>;
61 fn get(&self, buf: SymmetricBuffer, dst: &mut [u8]) -> Result<(), CollectiveError>;
63
64 fn barrier(&self) -> Result<(), CollectiveError>;
68}
69
70#[derive(Debug, Clone)]
71pub enum CollectiveError {
72 OutOfBounds {
74 rank: Rank,
75 offset: usize,
76 len: usize,
77 heap_size: usize,
78 },
79 LengthMismatch { expected: usize, got: usize },
81 UnknownRank { rank: Rank, num_ranks: u32 },
83 TransportError { reason: String },
85}
86
87impl std::fmt::Display for CollectiveError {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 Self::OutOfBounds {
91 rank,
92 offset,
93 len,
94 heap_size,
95 } => write!(
96 f,
97 "OOB on rank {}: offset {offset} + len {len} > heap_size {heap_size}",
98 rank.0
99 ),
100 Self::LengthMismatch { expected, got } => {
101 write!(f, "length mismatch: expected {expected}, got {got}")
102 }
103 Self::UnknownRank { rank, num_ranks } => {
104 write!(f, "unknown rank {} (have {num_ranks})", rank.0)
105 }
106 Self::TransportError { reason } => write!(f, "transport: {reason}"),
107 }
108 }
109}
110
111impl std::error::Error for CollectiveError {}
112
113#[derive(Debug)]
116pub struct SymmetricHeap {
117 storage: Vec<Arc<RwLock<Vec<u8>>>>,
118 pub heap_size: usize,
119}
120
121impl SymmetricHeap {
122 pub fn new(num_ranks: u32, heap_size: usize) -> Self {
123 let storage = (0..num_ranks)
124 .map(|_| Arc::new(RwLock::new(vec![0u8; heap_size])))
125 .collect();
126 Self { storage, heap_size }
127 }
128
129 pub fn num_ranks(&self) -> u32 {
130 self.storage.len() as u32
131 }
132
133 pub fn rank_view(&self, rank: Rank) -> Option<Arc<RwLock<Vec<u8>>>> {
134 self.storage.get(rank.0 as usize).cloned()
135 }
136}
137
138#[derive(Debug, Clone)]
144pub struct LocalTransport {
145 heap: Arc<SymmetricHeap>,
146 me: Rank,
147 barrier: Arc<std::sync::Barrier>,
152}
153
154impl LocalTransport {
155 pub fn new(num_ranks: u32, heap_size: usize, this_rank: Rank) -> Self {
156 let heap = Arc::new(SymmetricHeap::new(num_ranks, heap_size));
157 let barrier = Arc::new(std::sync::Barrier::new(num_ranks as usize));
158 Self::with_heap(heap, this_rank, barrier)
159 }
160
161 pub fn fan_out(num_ranks: u32, heap_size: usize) -> Vec<Self> {
165 let heap = Arc::new(SymmetricHeap::new(num_ranks, heap_size));
166 let barrier = Arc::new(std::sync::Barrier::new(num_ranks as usize));
167 (0..num_ranks)
168 .map(|i| Self::with_heap(heap.clone(), Rank(i), barrier.clone()))
169 .collect()
170 }
171
172 fn with_heap(heap: Arc<SymmetricHeap>, me: Rank, barrier: Arc<std::sync::Barrier>) -> Self {
173 Self { heap, me, barrier }
174 }
175
176 fn check_buf(&self, buf: SymmetricBuffer) -> Result<(), CollectiveError> {
177 if buf.rank.0 >= self.heap.num_ranks() {
178 return Err(CollectiveError::UnknownRank {
179 rank: buf.rank,
180 num_ranks: self.heap.num_ranks(),
181 });
182 }
183 if buf.offset + buf.len > self.heap.heap_size {
184 return Err(CollectiveError::OutOfBounds {
185 rank: buf.rank,
186 offset: buf.offset,
187 len: buf.len,
188 heap_size: self.heap.heap_size,
189 });
190 }
191 Ok(())
192 }
193}
194
195impl SymmetricTransport for LocalTransport {
196 fn num_ranks(&self) -> u32 {
197 self.heap.num_ranks()
198 }
199 fn this_rank(&self) -> Rank {
200 self.me
201 }
202
203 fn put(&self, buf: SymmetricBuffer, src: &[u8]) -> Result<(), CollectiveError> {
204 self.check_buf(buf)?;
205 if src.len() != buf.len {
206 return Err(CollectiveError::LengthMismatch {
207 expected: buf.len,
208 got: src.len(),
209 });
210 }
211 let view = self.heap.rank_view(buf.rank).expect("checked above");
212 let mut guard = view.write().unwrap();
213 guard[buf.offset..buf.offset + buf.len].copy_from_slice(src);
214 Ok(())
215 }
216
217 fn get(&self, buf: SymmetricBuffer, dst: &mut [u8]) -> Result<(), CollectiveError> {
218 self.check_buf(buf)?;
219 if dst.len() != buf.len {
220 return Err(CollectiveError::LengthMismatch {
221 expected: buf.len,
222 got: dst.len(),
223 });
224 }
225 let view = self.heap.rank_view(buf.rank).expect("checked above");
226 let guard = view.read().unwrap();
227 dst.copy_from_slice(&guard[buf.offset..buf.offset + buf.len]);
228 Ok(())
229 }
230
231 fn barrier(&self) -> Result<(), CollectiveError> {
232 self.barrier.wait();
235 Ok(())
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn put_then_get_round_trips() {
245 let t = LocalTransport::new(4, 1024, Rank(0));
246 let buf = SymmetricBuffer {
247 rank: Rank(2),
248 offset: 16,
249 len: 8,
250 };
251 t.put(buf, &[1, 2, 3, 4, 5, 6, 7, 8]).unwrap();
252 let mut dst = [0u8; 8];
253 t.get(buf, &mut dst).unwrap();
254 assert_eq!(&dst, &[1, 2, 3, 4, 5, 6, 7, 8]);
255 }
256
257 #[test]
258 fn fan_out_yields_one_transport_per_rank() {
259 let ts = LocalTransport::fan_out(3, 64);
260 assert_eq!(ts.len(), 3);
261 for (i, t) in ts.iter().enumerate() {
262 assert_eq!(t.this_rank(), Rank(i as u32));
263 assert_eq!(t.num_ranks(), 3);
264 }
265 }
266
267 #[test]
268 fn put_visible_to_other_rank_via_shared_heap() {
269 let ts = LocalTransport::fan_out(3, 32);
272 let payload = [9u8, 9, 9, 9];
273 ts[0]
274 .put(
275 SymmetricBuffer {
276 rank: Rank(2),
277 offset: 0,
278 len: 4,
279 },
280 &payload,
281 )
282 .unwrap();
283 let mut dst = [0u8; 4];
284 ts[2]
285 .get(
286 SymmetricBuffer {
287 rank: Rank(2),
288 offset: 0,
289 len: 4,
290 },
291 &mut dst,
292 )
293 .unwrap();
294 assert_eq!(dst, payload);
295 }
296
297 #[test]
298 fn oob_offset_errors() {
299 let t = LocalTransport::new(2, 8, Rank(0));
300 let err = t
301 .put(
302 SymmetricBuffer {
303 rank: Rank(1),
304 offset: 4,
305 len: 8,
306 },
307 &[0u8; 8],
308 )
309 .unwrap_err();
310 assert!(matches!(err, CollectiveError::OutOfBounds { .. }));
311 }
312
313 #[test]
314 fn unknown_rank_errors() {
315 let t = LocalTransport::new(2, 8, Rank(0));
316 let err = t
317 .get(
318 SymmetricBuffer {
319 rank: Rank(99),
320 offset: 0,
321 len: 4,
322 },
323 &mut [0u8; 4],
324 )
325 .unwrap_err();
326 assert!(matches!(err, CollectiveError::UnknownRank { .. }));
327 }
328
329 #[test]
330 fn length_mismatch_errors() {
331 let t = LocalTransport::new(2, 32, Rank(0));
332 let err = t
333 .put(
334 SymmetricBuffer {
335 rank: Rank(1),
336 offset: 0,
337 len: 4,
338 },
339 &[0u8; 8],
340 )
341 .unwrap_err();
342 assert!(matches!(err, CollectiveError::LengthMismatch { .. }));
343 }
344}