1use super::{
2 executable::{bridge_check, ffi::*},
3 memory::DeviceUse,
4 *,
5};
6use std::{
7 collections::VecDeque,
8 ffi::CString,
9 sync::{
10 Mutex,
11 atomic::{AtomicU64, Ordering},
12 },
13 time::Duration,
14};
15
16pub struct XdnaBinding<'a> {
18 pub buffer: &'a Buffer,
20 pub offset: usize,
22 pub length: usize,
24}
25#[derive(Clone)]
29pub struct XdnaProgram(Arc<Program>);
30struct Program {
31 bridge: Arc<Bridge>,
32 device: Device,
33 raw: *mut hrx_fabric_xdna_program,
34 buffers: Vec<Buffer>,
35 state: Mutex<State>,
36 retired: AtomicU64,
37 charges: Vec<Arc<crate::residency::MemoryReservation>>,
38}
39#[derive(Default)]
40struct State {
41 pending: VecDeque<u64>,
42 leases: Vec<DeviceUse>,
43 retired: u64,
44}
45unsafe impl Send for Program {}
48unsafe impl Sync for Program {}
49impl Program {
50 fn wait(&self, state: &mut State, submission: u64, timeout: u64) -> Result<bool> {
51 if submission <= state.retired {
52 return Ok(true);
53 }
54 let status = unsafe {
55 self.bridge
56 .hrx_fabric_xdna_wait(self.raw, submission, timeout)
57 };
58 if status == u64::from(AMDF_STATUS_CODE_DEADLINE_EXCEEDED) {
59 return Ok(false);
60 }
61 check("XDNA retirement", status)?;
62 state.retired = submission;
63 while state
64 .pending
65 .front()
66 .is_some_and(|point| *point <= submission)
67 {
68 state.pending.pop_front();
69 }
70 if state.pending.is_empty() {
71 state.leases.clear();
72 }
73 self.retired.store(submission, Ordering::Release);
74 Ok(true)
75 }
76}
77impl Drop for Program {
78 fn drop(&mut self) {
79 let mut state = self
80 .state
81 .lock()
82 .unwrap_or_else(std::sync::PoisonError::into_inner);
83 let retired = match state.pending.back() {
84 Some(submission) => {
85 let submission = *submission;
86 self.wait(&mut state, submission, 10_000_000_000)
87 .unwrap_or(false)
88 }
89 None => true,
90 };
91 if !retired || unsafe { self.bridge.hrx_fabric_xdna_close(self.raw) } != 0 {
92 std::mem::forget(std::mem::take(&mut state.leases));
94 std::mem::forget(self.buffers.clone());
95 std::mem::forget(std::mem::take(&mut self.charges));
96 std::mem::forget(self.device.clone());
97 std::mem::forget(self.bridge.clone());
98 }
99 }
100}
101#[derive(Clone)]
103pub struct XdnaCompletion {
104 program: XdnaProgram,
105 submission: u64,
106}
107impl XdnaCompletion {
108 pub fn is_complete(&self) -> Result<bool> {
110 Ok(self.program.0.retired.load(Ordering::Acquire) >= self.submission)
111 }
112 pub fn refresh(&self) -> Result<bool> {
114 self.wait_timeout(Duration::ZERO)
115 }
116 pub fn wait_timeout(&self, timeout: Duration) -> Result<bool> {
118 let mut state = self
119 .program
120 .0
121 .state
122 .lock()
123 .map_err(|_| Error::DeviceLost("XDNA queue poisoned".into()))?;
124 self.program.0.wait(
125 &mut state,
126 self.submission,
127 timeout.as_nanos().min(u128::from(u64::MAX - 1)) as u64,
128 )
129 }
130 pub fn wait(&self) -> Result<()> {
132 let mut state = self
133 .program
134 .0
135 .state
136 .lock()
137 .map_err(|_| Error::DeviceLost("XDNA queue poisoned".into()))?;
138 self.program.0.wait(&mut state, self.submission, u64::MAX)?;
139 Ok(())
140 }
141}
142impl XdnaProgram {
143 pub unsafe fn wrap_transaction(
152 mut self,
153 prefix: &[u8],
154 prefix_operations: u32,
155 suffix: &[u8],
156 suffix_operations: u32,
157 retained: &[Buffer],
158 budget: Option<&crate::residency::MemoryBudget>,
159 ) -> Result<Self> {
160 let program = Arc::get_mut(&mut self.0)
161 .ok_or_else(|| Error::Busy("XDNA command is shared".into()))?;
162 let state = program
163 .state
164 .get_mut()
165 .map_err(|_| Error::DeviceLost("XDNA queue poisoned".into()))?;
166 if !state.pending.is_empty() || state.retired != 0 {
167 return Err(Error::Busy(
168 "XDNA command has already been submitted".into(),
169 ));
170 }
171 for buffer in retained {
172 buffer.device_address(&program.device)?;
173 }
174 let mut length = 0;
175 bridge_check(&program.bridge, unsafe {
176 program.bridge.hrx_fabric_xdna_command_copy(
177 program.raw,
178 ptr::null_mut(),
179 0,
180 &mut length,
181 )
182 })?;
183 let mut original = vec![0; length];
184 bridge_check(&program.bridge, unsafe {
185 program.bridge.hrx_fabric_xdna_command_copy(
186 program.raw,
187 original.as_mut_ptr(),
188 length,
189 &mut length,
190 )
191 })?;
192 let bytes = compose_transaction(
193 &original,
194 prefix,
195 prefix_operations,
196 suffix,
197 suffix_operations,
198 )?;
199 if let Some(budget) = budget {
200 program.charges.push(Arc::new(budget.reserve(bytes.len())?));
201 }
202 program.buffers.extend_from_slice(retained);
204 program
205 .buffers
206 .sort_by_key(|buffer| Arc::as_ptr(&buffer.0) as usize);
207 program.buffers.dedup_by(|a, b| Arc::ptr_eq(&a.0, &b.0));
208 state.leases.reserve(program.buffers.len());
209 bridge_check(&program.bridge, unsafe {
210 program
211 .bridge
212 .hrx_fabric_xdna_command_replace(program.raw, bytes.as_ptr(), bytes.len())
213 })?;
214 Ok(self)
215 }
216 pub unsafe fn dispatch(&self) -> Result<XdnaCompletion> {
222 let mut state = self
223 .0
224 .state
225 .lock()
226 .map_err(|_| Error::DeviceLost("XDNA queue poisoned".into()))?;
227 if state.pending.len() == 128 {
228 let oldest = *state.pending.front().unwrap();
229 if !self.0.wait(&mut state, oldest, 0)? {
230 return Err(Error::Busy(
231 "XDNA pending invocation capacity exhausted".into(),
232 ));
233 }
234 }
235 if state.leases.is_empty() {
236 for buffer in &self.0.buffers {
237 match buffer.retain_use() {
238 Ok(lease) => state.leases.push(lease),
239 Err(error) => {
240 state.leases.clear();
241 return Err(error);
242 }
243 }
244 }
245 }
246 let mut submission = 0;
247 if let Err(error) = check("XDNA submit", unsafe {
248 self.0
249 .bridge
250 .hrx_fabric_xdna_submit(self.0.raw, &mut submission)
251 }) {
252 if state.pending.is_empty() {
253 state.leases.clear();
254 }
255 return Err(error);
256 }
257 state.pending.push_back(submission);
258 Ok(XdnaCompletion {
259 program: self.clone(),
260 submission,
261 })
262 }
263}
264impl Device {
265 pub unsafe fn prepare_xdna(
270 &self,
271 artifact: &crate::loom::Artifact,
272 columns: u16,
273 bindings: &[XdnaBinding<'_>],
274 ) -> Result<XdnaProgram> {
275 if self.endpoint().engine() != Engine::Xdna || artifact.target() != self.target().as_str() {
276 return Err(Error::Unsupported("artifact and XDNA target differ".into()));
277 }
278 if !(1..=8).contains(&columns) || bindings.len() > u32::MAX as usize {
279 return Err(Error::Message(
280 "invalid XDNA context width or binding count".into(),
281 ));
282 }
283 let mut native = Vec::with_capacity(bindings.len());
284 let mut buffers = Vec::with_capacity(bindings.len());
285 for binding in bindings {
286 if binding.length == 0
287 || binding
288 .offset
289 .checked_add(binding.length)
290 .is_none_or(|end| end > binding.buffer.len())
291 {
292 return Err(Error::Message("XDNA binding range is out of bounds".into()));
293 }
294 let address = binding
295 .buffer
296 .device_address(self)?
297 .checked_add(binding.offset as u64)
298 .ok_or_else(|| Error::Message("XDNA binding address overflow".into()))?;
299 native.push(hrx_fabric_xdna_binding {
300 memory: binding.buffer.0.raw.cast(),
301 host_pointer: binding.buffer.host_pointer().cast(),
302 allocation_length: binding.buffer.len() as u64,
303 offset: binding.offset as u64,
304 length: binding.length as u64,
305 address,
306 });
307 buffers.push(binding.buffer.clone());
308 }
309 buffers.sort_by_key(|buffer| Arc::as_ptr(&buffer.0) as usize);
310 buffers.dedup_by(|a, b| Arc::ptr_eq(&a.0, &b.0));
311 let api = &self.0.endpoint.0.instance.api;
312 let bridge = api.load_bridge()?;
313 let symbol = CString::new(artifact.symbol())
314 .map_err(|_| Error::Message("symbol contains NUL".into()))?;
315 let mut raw = ptr::null_mut();
316 let status = unsafe {
317 bridge.hrx_fabric_xdna_open(
318 std::ptr::from_ref(api.core).cast(),
319 std::ptr::from_ref(api.xdna).cast(),
320 self.0.endpoint.0.instance.raw.cast(),
321 self.0.endpoint.0.raw.cast(),
322 self.0.raw.cast(),
323 artifact.bytes().as_ptr(),
324 artifact.bytes().len(),
325 symbol.as_ptr(),
326 columns,
327 native.len() as u32,
328 native.as_ptr(),
329 &mut raw,
330 )
331 };
332 let result = bridge_check(&bridge, status);
334 if raw.is_null() {
335 return result.and_then(|()| Err(missing("XDNA program")));
336 }
337 let state = Mutex::new(State {
338 pending: VecDeque::with_capacity(128),
339 leases: Vec::with_capacity(buffers.len()),
340 ..State::default()
341 });
342 let program = XdnaProgram(Arc::new(Program {
343 bridge,
344 device: self.clone(),
345 raw,
346 buffers,
347 state,
348 retired: AtomicU64::new(0),
349 charges: Vec::new(),
350 }));
351 result?;
352 Ok(program)
353 }
354}
355
356fn compose_transaction(
357 original: &[u8],
358 prefix: &[u8],
359 prefix_operations: u32,
360 suffix: &[u8],
361 suffix_operations: u32,
362) -> Result<Vec<u8>> {
363 let invalid = || Error::Message("invalid or oversized AIE2P transaction 0.1".into());
364 if original.len() < 16
365 || original[..4] != [0, 1, 4, 6]
366 || original[5] != 1
367 || original[4] == 0
368 || original[4] > 8
369 || u32::from_le_bytes(original[12..16].try_into().unwrap()) as usize != original.len()
370 {
371 return Err(invalid());
372 }
373 let length = original
374 .len()
375 .checked_add(prefix.len())
376 .and_then(|n| n.checked_add(suffix.len()))
377 .filter(|n| *n <= u32::MAX as usize)
378 .ok_or_else(invalid)?;
379 let operations = u32::from_le_bytes(original[8..12].try_into().unwrap())
380 .checked_add(prefix_operations)
381 .and_then(|n| n.checked_add(suffix_operations))
382 .ok_or_else(invalid)?;
383 let mut bytes = Vec::with_capacity(length);
384 bytes.extend_from_slice(&original[..16]);
385 bytes.extend_from_slice(prefix);
386 bytes.extend_from_slice(&original[16..]);
387 bytes.extend_from_slice(suffix);
388 bytes[8..12].copy_from_slice(&operations.to_le_bytes());
389 bytes[12..16].copy_from_slice(&(length as u32).to_le_bytes());
390 Ok(bytes)
391}
392
393#[cfg(test)]
394mod transaction_tests {
395 use super::compose_transaction;
396
397 fn original() -> Vec<u8> {
398 let mut command = vec![0, 1, 4, 6, 1, 1, 0, 0];
399 command.extend_from_slice(&2u32.to_le_bytes());
400 command.extend_from_slice(&24u32.to_le_bytes());
401 command.extend_from_slice(&[11, 22, 33, 44, 55, 66, 77, 88]);
402 command
403 }
404
405 #[test]
406 fn wrapping_preserves_original_body_and_updates_only_extent_and_count() {
407 let original = original();
408 let result = compose_transaction(&original, &[9, 8, 7, 6], 1, &[5, 4, 3, 2], 3).unwrap();
409 assert_eq!(&result[..8], &original[..8]);
410 assert_eq!(u32::from_le_bytes(result[8..12].try_into().unwrap()), 6);
411 assert_eq!(u32::from_le_bytes(result[12..16].try_into().unwrap()), 32);
412 assert_eq!(&result[16..20], &[9, 8, 7, 6]);
413 assert_eq!(&result[20..28], &original[16..]);
414 assert_eq!(&result[28..], &[5, 4, 3, 2]);
415 }
416
417 #[test]
418 fn wrapping_rejects_malformed_headers_and_operation_overflow() {
419 let original = original();
420 for length in 0..original.len() {
421 assert!(compose_transaction(&original[..length], &[], 0, &[], 0).is_err());
422 }
423 for (offset, value) in [
424 (0, 1),
425 (1, 2),
426 (2, 5),
427 (3, 7),
428 (4, 0),
429 (4, 9),
430 (5, 2),
431 (12, 16),
432 ] {
433 let mut malformed = original.clone();
434 malformed[offset] = value;
435 assert!(compose_transaction(&malformed, &[], 0, &[], 0).is_err());
436 }
437 assert!(compose_transaction(&original, &[], u32::MAX, &[], 0).is_err());
438 assert!(compose_transaction(&original, &[], u32::MAX - 2, &[], 1).is_err());
439 }
440}