1mod sdma;
2use super::*;
3use std::{
4 collections::VecDeque,
5 sync::{
6 Mutex, Weak,
7 atomic::{AtomicU32, AtomicU64, Ordering},
8 },
9 time::{Duration, Instant},
10};
11
12#[derive(Clone)]
14pub struct Queue(Arc<QueueInner>);
15
16pub(super) struct StreamQueues {
19 slots: Vec<Weak<QueueInner>>,
20 limit: usize,
21 next: usize,
22}
23impl Default for StreamQueues {
24 fn default() -> Self {
25 Self {
26 slots: Vec::new(),
27 limit: 8,
29 next: 0,
30 }
31 }
32}
33impl StreamQueues {
34 fn set_limit(&mut self, count: usize) -> Result<()> {
35 if count == 0 {
36 return Err(Error::Message("stream queue count must be positive".into()));
37 }
38 if count != self.limit && !self.slots.is_empty() {
39 return Err(Error::Busy(
40 "configure stream queues before creating the first stream".into(),
41 ));
42 }
43 self.limit = count;
44 Ok(())
45 }
46}
47struct QueueInner {
48 device: Device,
49 family: u32,
50 raw: *mut amdf_user_queue_t,
51 mapping: *mut amdf_user_queue_mapping_t,
52 info: amdf_user_queue_mapping_info_t,
53 state: Mutex<QueueState>,
54 transfers: Mutex<Option<[Kernel; 4]>>,
55}
56struct QueueState {
57 write: u64,
58 pending: VecDeque<Pending>,
59}
60struct Pending {
61 work: Arc<Work>,
62 value: u32,
63}
64struct Work {
65 fence: Buffer,
66 _kernels: Vec<Kernel>,
67 buffers: Vec<Buffer>,
68 leases: Mutex<Vec<memory::DeviceUse>>,
69 retired: AtomicU32,
70 active: AtomicU32,
71 dependencies: Vec<(Arc<Work>, u32)>,
72}
73unsafe impl Send for QueueInner {}
76unsafe impl Sync for QueueInner {}
77
78#[derive(Clone)]
80pub struct Completion {
81 queue: Queue,
82 work: Arc<Work>,
83 value: u32,
84}
85impl Completion {
86 pub fn is_complete(&self) -> Result<bool> {
89 Ok(self.work.retired.load(Ordering::Acquire) >= self.value)
90 }
91 pub fn refresh(&self) -> Result<bool> {
94 if !self.queue.0.poll(&self.work, self.value)? {
95 return Ok(false);
96 }
97 let mut state = self
100 .queue
101 .0
102 .state
103 .lock()
104 .map_err(|_| Error::DeviceLost("queue producer poisoned".into()))?;
105 while let Some(work) = state.pending.front() {
106 if !self.queue.0.poll(&work.work, work.value)? {
107 break;
108 }
109 state.pending.pop_front();
110 }
111 Ok(true)
112 }
113 pub fn wait_timeout(&self, timeout: Duration) -> Result<bool> {
115 let start = Instant::now();
116 loop {
117 if self.refresh()? {
118 return Ok(true);
119 }
120 if start.elapsed() >= timeout {
121 return Ok(false);
122 }
123 std::thread::yield_now();
124 }
125 }
126 pub fn wait(&self) -> Result<()> {
128 while !self.refresh()? {
129 std::thread::yield_now();
130 }
131 Ok(())
132 }
133}
134impl Work {
135 fn retire(&self, value: u32) -> Result<bool> {
136 if self.retired.load(Ordering::Acquire) >= value {
137 return Ok(true);
138 }
139 let mut leases = self
140 .leases
141 .lock()
142 .map_err(|_| Error::DeviceLost("submission ownership poisoned".into()))?;
143 if self.retired.load(Ordering::Acquire) >= value {
144 return Ok(true);
145 }
146 let completed = self.fence.fence_value()?;
147 if completed >= value {
148 for (dependency, point) in &self.dependencies {
149 if !dependency.retire(*point)? {
150 return Ok(false);
151 }
152 }
153 if completed >= self.active.load(Ordering::Acquire) {
154 leases.clear();
155 }
156 self.retired.store(completed, Ordering::Release);
157 return Ok(true);
158 }
159 Ok(false)
160 }
161}
162impl QueueInner {
163 fn poll(&self, work: &Work, value: u32) -> Result<bool> {
164 if work.retire(value)? {
165 return Ok(true);
166 }
167 let api = self.device.0.endpoint.0.instance.api.core;
168 let mut status = amdf_user_queue_status_t {
169 type_: AMDF_STRUCTURE_TYPE_USER_QUEUE_STATUS,
170 structure_size: size_of::<amdf_user_queue_status_t>() as u32,
171 ..Default::default()
172 };
173 check("user_queue_query_status", unsafe {
174 entry!(api, user_queue_query_status)(self.raw, &mut status)
175 })?;
176 if status.state != AMDF_QUEUE_STATE_ACTIVE {
177 return Err(Error::DeviceLost(format!(
178 "PM4 queue failed with native status {}",
179 status.terminal_status
180 )));
181 }
182 Ok(false)
183 }
184}
185impl Drop for QueueInner {
186 fn drop(&mut self) {
187 let pending = std::mem::take(
188 &mut self
189 .state
190 .get_mut()
191 .unwrap_or_else(|e| e.into_inner())
192 .pending,
193 );
194 let deadline = Instant::now() + Duration::from_secs(10);
195 for work in &pending {
196 loop {
197 match self.poll(&work.work, work.value) {
198 Ok(true) => break,
199 Ok(false) if Instant::now() < deadline => std::thread::yield_now(),
200 Ok(false) | Err(_) => {
201 std::mem::forget(pending);
204 std::mem::forget(self.device.clone());
205 return;
206 }
207 }
208 }
209 }
210 let api = self.device.0.endpoint.0.instance.api.core;
211 unsafe {
212 if !self.mapping.is_null() && api.user_queue_mapping_destroy.unwrap()(self.mapping) != 0
213 {
214 std::mem::forget(self.device.clone());
215 return;
216 }
217 if !self.raw.is_null() && api.user_queue_destroy.unwrap()(self.raw) != 0 {
218 std::mem::forget(self.device.clone());
219 }
220 }
221 }
222}
223
224fn header(opcode: u32, count: usize) -> u32 {
225 (3 << 30) | (((count - 2) as u32) << 16) | (opcode << 8)
226}
227fn barrier(words: &mut Vec<u32>) {
228 const GLI_INV_ALL: u32 = 1;
234 words.extend([
235 header(0x46, 2),
236 7 | (4 << 8),
237 header(0x58, 8),
238 0,
239 u32::MAX,
240 0xff,
241 0,
242 0,
243 0x0a,
244 GLI_INV_ALL | (1 << 4) | (1 << 5) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 14) | (1 << 15),
245 ]);
246}
247fn pad(words: &mut Vec<u32>) {
248 let mut count = 8 - words.len() % 8;
249 if count == 1 {
250 count += 8;
251 }
252 words.push(header(0x10, count));
253 words.resize(words.len() + count - 1, 0);
254}
255
256const INDIRECT_WORD_LIMIT: usize = 1 << 20;
259fn batch_ranges(
260 commands: impl IntoIterator<Item = (usize, bool)>,
261 profiled: bool,
262) -> Result<Vec<std::ops::Range<usize>>> {
263 let mut ranges = Vec::new();
264 let mut start = 0;
265 let mut end = 0;
266 let mut words = 0;
267 for (payload, dependency) in commands {
268 let size = payload
269 .checked_add(if dependency { 10 } else { 0 })
270 .and_then(|n| n.checked_add(if profiled { 2 * (10 + 32) } else { 0 }))
271 .filter(|n| *n < INDIRECT_WORD_LIMIT - 9)
272 .ok_or_else(|| {
273 Error::Unsupported("native command exceeds indirect command capacity".into())
274 })?;
275 if words + size >= INDIRECT_WORD_LIMIT - 9 {
276 ranges.push(start..end);
277 start = end;
278 words = 0;
279 }
280 words += size;
281 end += 1;
282 }
283 if start != end {
284 ranges.push(start..end);
285 }
286 Ok(ranges)
287}
288
289impl Device {
290 pub(crate) fn set_stream_queue_count(&self, count: usize) -> Result<()> {
291 self.0
292 .stream_queues
293 .lock()
294 .map_err(|_| Error::DeviceLost("stream queue pool poisoned".into()))?
295 .set_limit(count)
296 }
297
298 pub(crate) fn stream_queue_count(&self) -> Result<usize> {
299 Ok(self
300 .0
301 .stream_queues
302 .lock()
303 .map_err(|_| Error::DeviceLost("stream queue pool poisoned".into()))?
304 .limit)
305 }
306
307 pub(crate) fn stream_queue(&self) -> Result<Queue> {
308 let mut pool = self
309 .0
310 .stream_queues
311 .lock()
312 .map_err(|_| Error::DeviceLost("stream queue pool poisoned".into()))?;
313 let slot = pool.next;
314 if slot == pool.slots.len() {
315 pool.slots
316 .try_reserve(1)
317 .map_err(|_| Error::Message("stream queue pool allocation failed".into()))?;
318 let queue = self.queue()?;
319 pool.slots.push(Arc::downgrade(&queue.0));
320 pool.next = (slot + 1) % pool.limit;
321 return Ok(queue);
322 }
323 let queue = match pool.slots[slot].upgrade() {
324 Some(queue) => Queue(queue),
325 None => {
326 let queue = self.queue()?;
327 pool.slots[slot] = Arc::downgrade(&queue.0);
328 queue
329 }
330 };
331 pool.next = (slot + 1) % pool.limit;
332 Ok(queue)
333 }
334
335 pub fn queue(&self) -> Result<Queue> {
337 self.create_queue(AMDF_QUEUE_COMMAND_TYPE_GPU_PM4)
338 }
339 pub fn sdma_queue(&self) -> Result<Queue> {
341 self.create_queue(AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA)
342 }
343 fn create_queue(&self, command: u32) -> Result<Queue> {
344 if self.endpoint().engine() != Engine::Gpu {
345 return Err(Error::Unsupported(
346 "PM4 queues require an AMDGPU device".into(),
347 ));
348 }
349 let api = &self.0.endpoint.0.instance.api;
350 let _ = entry!(api.core, user_queue_destroy);
351 let _ = entry!(api.core, user_queue_mapping_destroy);
352 let mut endpoint = amdf_endpoint_info_t {
353 type_: AMDF_STRUCTURE_TYPE_ENDPOINT_INFO,
354 structure_size: size_of::<amdf_endpoint_info_t>() as u32,
355 ..Default::default()
356 };
357 check("endpoint_query_info", unsafe {
358 entry!(api.core, endpoint_query_info)(self.0.endpoint.0.raw, &mut endpoint)
359 })?;
360 for ordinal in 0..endpoint.queue_family_count {
361 let mut family = amdf_queue_family_info_t {
362 type_: AMDF_STRUCTURE_TYPE_QUEUE_FAMILY_INFO,
363 structure_size: size_of::<amdf_queue_family_info_t>() as u32,
364 ..Default::default()
365 };
366 check("endpoint_query_queue_family_info", unsafe {
367 entry!(api.core, endpoint_query_queue_family_info)(
368 self.0.endpoint.0.raw,
369 ordinal,
370 &mut family,
371 )
372 })?;
373 if family.command_type != command
374 || family.format_version != AMDF_GPU_PM4_QUEUE_FORMAT_VERSION_1
375 || family.publication_modes & AMDF_QUEUE_PUBLICATION_MODE_USER == 0
376 || (command == AMDF_QUEUE_COMMAND_TYPE_GPU_PM4
377 && family.format_features & AMDF_GPU_PM4_FORMAT_FEATURE_ACQUIRE_MEM_GCR as u64
378 == 0)
379 {
380 continue;
381 }
382 let options = amdf_gpu_user_queue_create_info_t {
383 type_: HRX_AMDF_STRUCTURE_TYPE_GPU_USER_QUEUE_CREATE_INFO,
384 structure_size: size_of::<amdf_gpu_user_queue_create_info_t>() as u32,
385 queue_family_ordinal: ordinal,
386 priority: AMDF_QUEUE_PRIORITY_NORMAL,
387 producer_mode: AMDF_QUEUE_PRODUCER_MODE_SINGLE,
388 required_capabilities: AMDF_USER_QUEUE_CAPABILITY_HOST_PRODUCER as u64,
389 ring_byte_length: family.minimum_ring_byte_length,
390 ..Default::default()
391 };
392 let mut raw = ptr::null_mut();
393 check("gpu.user_queue_create", unsafe {
394 entry!(api.gpu, user_queue_create)(self.0.raw, &options, &mut raw)
395 })?;
396 if raw.is_null() {
397 return Err(missing("created queue"));
398 }
399 let mut queue = QueueInner {
400 device: self.clone(),
401 family: ordinal,
402 raw,
403 mapping: ptr::null_mut(),
404 info: amdf_user_queue_mapping_info_t::default(),
405 transfers: Mutex::new(None),
406 state: Mutex::new(QueueState {
407 write: 0,
408 pending: VecDeque::with_capacity(4096),
409 }),
410 };
411 check("user_queue_map", unsafe {
412 entry!(api.core, user_queue_map)(raw, ptr::null_mut(), &mut queue.mapping)
413 })?;
414 queue.info.type_ = AMDF_STRUCTURE_TYPE_USER_QUEUE_MAPPING_INFO;
415 queue.info.structure_size = size_of::<amdf_user_queue_mapping_info_t>() as u32;
416 check("user_queue_mapping_query_info", unsafe {
417 entry!(api.core, user_queue_mapping_query_info)(queue.mapping, &mut queue.info)
418 })?;
419 let info = &queue.info;
420 if info.command_type != command
421 || info.format_version != 1
422 || !info.ring_byte_length.is_power_of_two()
423 || info.ring_byte_length < 4096
424 || info.index_bits != 64
425 || info.doorbell_bits != 64
426 || [
427 info.ring_address,
428 info.read_index_address,
429 info.write_index_address,
430 info.doorbell_address,
431 ]
432 .iter()
433 .any(|address| *address == 0 || address % 8 != 0)
434 {
435 return Err(Error::Unsupported(
436 "unexpected native GPU queue mapping contract".into(),
437 ));
438 }
439 return Ok(Queue(Arc::new(queue)));
440 }
441 Err(Error::Unsupported(
442 "no qualified native queue family for the requested engine".into(),
443 ))
444 }
445}
446
447#[derive(Default)]
450pub(crate) struct GraphArena {
451 page: Option<Buffer>,
452 used: usize,
453 fence: Option<Buffer>,
454}
455impl GraphArena {
456 fn pack(&mut self, queue: &Queue, bytes: &[u8]) -> Result<(Buffer, usize, Buffer, usize)> {
457 let fabric = queue.device().fabric();
458 let size = bytes.len().max(64).next_multiple_of(64);
459 let mut allocated = 0;
460 if self
461 .page
462 .as_ref()
463 .is_none_or(|p| size > p.len() - self.used)
464 {
465 self.page =
466 Some(fabric.allocate(size.max(64 * 1024), std::slice::from_ref(queue.device()))?);
467 self.used = 0;
468 allocated += self.page.as_ref().unwrap().len();
469 }
470 let page = self.page.as_ref().unwrap();
471 let offset = self.used;
472 page.write(offset, bytes)?;
473 self.used += size;
474 if self.fence.is_none() {
475 self.fence = Some(fabric.allocate_shared(64, std::slice::from_ref(queue.device()))?);
476 allocated += 64;
477 }
478 Ok((
479 page.clone(),
480 offset,
481 self.fence.as_ref().unwrap().clone(),
482 allocated,
483 ))
484 }
485}
486
487impl Queue {
488 pub(super) fn identity(&self) -> usize {
489 Arc::as_ptr(&self.0) as usize
490 }
491 pub unsafe fn prepare(
497 &self,
498 kernel: &Kernel,
499 grid: [u32; 3],
500 block: [u16; 3],
501 arguments: &[Argument<'_>],
502 ) -> Result<PreparedGpu> {
503 unsafe { self.prepare_inner(kernel, grid, block, arguments, None) }
504 }
505 pub(crate) unsafe fn prepare_graph(
506 &self,
507 kernel: &Kernel,
508 grid: [u32; 3],
509 block: [u16; 3],
510 arguments: &[Argument<'_>],
511 arena: &mut GraphArena,
512 ) -> Result<PreparedGpu> {
513 unsafe { self.prepare_inner(kernel, grid, block, arguments, Some(arena)) }
514 }
515 unsafe fn prepare_inner(
516 &self,
517 kernel: &Kernel,
518 grid: [u32; 3],
519 block: [u16; 3],
520 arguments: &[Argument<'_>],
521 arena: Option<&mut GraphArena>,
522 ) -> Result<PreparedGpu> {
523 if self.0.info.command_type != AMDF_QUEUE_COMMAND_TYPE_GPU_PM4 {
524 return Err(Error::Unsupported(
525 "kernel dispatch requires a compute queue".into(),
526 ));
527 }
528 if !Arc::ptr_eq(&kernel.device().0, &self.0.device.0) {
529 return Err(Error::Message(
530 "kernel belongs to another native device".into(),
531 ));
532 }
533 let fabric = Fabric(self.0.device.0.endpoint.0.instance.clone());
534 let (packed, mut buffers) = kernel.arguments(arguments)?;
535 let (argument_buffer, argument_offset, fence, argument_storage) = if let Some(arena) = arena
536 {
537 arena.pack(self, &packed)?
538 } else {
539 let buffer =
540 fabric.allocate(packed.len().max(64), std::slice::from_ref(&self.0.device))?;
541 buffer.write(0, &packed)?;
542 {
543 let bytes = buffer.len() + 64;
544 (
545 buffer,
546 0,
547 fabric.allocate_shared(64, std::slice::from_ref(&self.0.device))?,
548 bytes,
549 )
550 }
551 };
552 let address = fence.device_address(&self.0.device)?;
553 let scratch = if kernel.0.info.private_bytes != 0 {
554 let mut info = amdf_gpu_endpoint_info_t {
555 type_: HRX_AMDF_STRUCTURE_TYPE_GPU_ENDPOINT_INFO,
556 structure_size: size_of::<amdf_gpu_endpoint_info_t>() as u32,
557 ..Default::default()
558 };
559 let api = &self.0.device.0.endpoint.0.instance.api;
560 check("GPU scratch topology", unsafe {
561 entry!(api.gpu, endpoint_query_info)(self.0.device.0.endpoint.0.raw, &mut info)
562 })?;
563 if info.topology.xcc_count != 1 {
564 return Err(Error::Unsupported(
565 "scratch requires the qualified single-XCC topology".into(),
566 ));
567 }
568 let waves = info
569 .compute
570 .compute_unit_count
571 .checked_mul(info.compute.maximum_scratch_wave_count_per_compute_unit)
572 .ok_or_else(|| Error::Message("scratch topology overflow".into()))?;
573 let per_wave = (u64::from(kernel.0.info.private_bytes)
574 * u64::from(kernel.0.info.wave_size))
575 .div_ceil(256)
576 * 256;
577 let size = per_wave
578 .checked_mul(u64::from(waves))
579 .filter(|size| *size <= 512 * 1024 * 1024)
580 .ok_or_else(|| {
581 Error::Unsupported("kernel scratch exceeds 512 MiB admission limit".into())
582 })?;
583 let buffer = fabric.allocate_access(
584 size as usize,
585 std::slice::from_ref(&self.0.device),
586 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
587 256,
588 )?;
589 Some((buffer, waves, info.topology.shader_engine_count_per_xcc))
590 } else {
591 None
592 };
593 let mut words = Vec::with_capacity(160);
594 barrier(&mut words);
595 let dispatch_start = words.len();
596 words.extend(
597 kernel.dispatch_words(
598 grid,
599 block,
600 &packed,
601 argument_buffer.device_address(&self.0.device)? + argument_offset as u64,
602 scratch
603 .as_ref()
604 .map(|(buffer, waves, engines)| (buffer, *waves, *engines)),
605 )?,
606 );
607 let dispatch_end = words.len();
608 barrier(&mut words);
609 let fence_word = words.len() + 4;
610 words.extend([
611 header(0x37, 5),
612 (2 << 8) | (1 << 20),
613 address as u32,
614 (address >> 32) as u32,
615 1,
616 ]);
617 pad(&mut words);
618 let storage_bytes =
620 argument_storage + scratch.as_ref().map_or(0, |(buffer, _, _)| buffer.len());
621 buffers.push(argument_buffer.clone());
622 if let Some((scratch, _, _)) = scratch {
623 buffers.push(scratch);
624 }
625 buffers.sort_by_key(|buffer| Arc::as_ptr(&buffer.0) as usize);
626 buffers.dedup_by(|a, b| Arc::ptr_eq(&a.0, &b.0));
627 let leases = Mutex::new(Vec::with_capacity(buffers.len()));
628 Ok(PreparedGpu {
629 prefix: Default::default(),
630 queue: self.clone(),
631 inner: Arc::new(PreparedInner {
632 storage_bytes,
633 work: Arc::new(Work {
634 _kernels: vec![kernel.clone()],
635 buffers,
636 fence,
637 leases,
638 retired: AtomicU32::new(0),
639 active: AtomicU32::new(0),
640 dependencies: Vec::new(),
641 }),
642 words,
643 dispatch_range: dispatch_start..dispatch_end,
644 indirect_words: None,
645 fence_word,
646 previous: Mutex::new(0),
647 }),
648 })
649 }
650 pub unsafe fn prepare_batch(&self, commands: &[(PreparedGpu, bool)]) -> Result<PreparedGpu> {
657 self.prepare_batch_inner(commands, None, 0)
658 }
659 pub unsafe fn prepare_profiled_batch(
666 &self,
667 commands: &[(PreparedGpu, bool)],
668 labels: &[String],
669 ) -> Result<ProfiledGpu> {
670 if commands.len() != labels.len() {
671 return Err(Error::Message("profile labels must match commands".into()));
672 }
673 let capture = profile::ProfileCapture::new(self, labels)?;
674 let commands = self.prepare_batch_inner(commands, Some(&capture), 0)?;
675 Ok(ProfiledGpu { commands, capture })
676 }
677 pub(crate) unsafe fn prepare_graph_batches(
681 &self,
682 commands: &[(PreparedGpu, bool)],
683 labels: Option<&[String]>,
684 ) -> Result<(Vec<PreparedGpu>, Option<profile::ProfileCapture>)> {
685 let capture = if let Some(labels) = labels {
686 if commands.len() != labels.len() || commands.is_empty() {
687 return Err(Error::Message(
688 "profile labels must match nonempty commands".into(),
689 ));
690 }
691 Some(profile::ProfileCapture::new(self, labels)?)
692 } else {
693 None
694 };
695 let ranges = batch_ranges(
696 commands
697 .iter()
698 .map(|(command, dependency)| (command.dispatch_words().len(), *dependency)),
699 capture.is_some(),
700 )?;
701 let batches = ranges
702 .into_iter()
703 .map(|range| {
704 self.prepare_batch_inner(&commands[range.clone()], capture.as_ref(), range.start)
705 })
706 .collect::<Result<_>>()?;
707 Ok((batches, capture))
708 }
709 fn prepare_batch_inner(
710 &self,
711 commands: &[(PreparedGpu, bool)],
712 profile: Option<&profile::ProfileCapture>,
713 profile_start: usize,
714 ) -> Result<PreparedGpu> {
715 if self.0.info.command_type != AMDF_QUEUE_COMMAND_TYPE_GPU_PM4 {
716 return Err(Error::Unsupported(
717 "indirect compute batches require a PM4 queue".into(),
718 ));
719 }
720 if commands.is_empty() {
721 return Err(Error::Message("empty native command batch".into()));
722 }
723 let mut indirect = Vec::new();
724 let mut buffers = Vec::new();
725 let mut kernels = Vec::new();
726 let mut dependencies = Vec::new();
727 let timestamp_address = profile
728 .map(|p| p.buffer.device_address(self.device()))
729 .transpose()?;
730 if let Some(profile) = profile {
731 buffers.push(profile.buffer.clone());
732 }
733 for (index, (command, dependency)) in commands.iter().enumerate() {
734 if !Arc::ptr_eq(&command.queue.0, &self.0) {
735 return Err(Error::Message(
736 "batch command belongs to another queue".into(),
737 ));
738 }
739 if *dependency {
740 barrier(&mut indirect);
741 }
742 if let (Some(profile), Some(address)) = (profile, timestamp_address) {
743 barrier(&mut indirect);
744 profile
745 .api
746 .emit(address + (profile_start + index) as u64 * 16, &mut indirect)?;
747 }
748 indirect.extend_from_slice(command.dispatch_words());
749 if let (Some(profile), Some(address)) = (profile, timestamp_address) {
750 barrier(&mut indirect);
751 profile.api.emit(
752 address + (profile_start + index) as u64 * 16 + 8,
753 &mut indirect,
754 )?;
755 }
756 buffers.extend(command.inner.work.buffers.iter().cloned());
757 kernels.extend(command.inner.work._kernels.iter().cloned());
758 dependencies.extend(command.inner.work.dependencies.iter().cloned());
759 }
760 pad(&mut indirect);
761 if indirect.len() >= INDIRECT_WORD_LIMIT {
762 return Err(Error::Unsupported(format!(
763 "native batch exceeds indirect command capacity: {} words for {} commands",
764 indirect.len(),
765 commands.len()
766 )));
767 }
768 let fabric = self.device().fabric();
769 let storage = fabric.allocate_access(
770 indirect.len() * 4,
771 std::slice::from_ref(self.device()),
772 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE | AMDF_MEMORY_ACCESS_EXECUTE,
773 256,
774 )?;
775 storage.write(0, unsafe {
777 std::slice::from_raw_parts(indirect.as_ptr().cast(), indirect.len() * 4)
778 })?;
779 let ib_address = storage.device_address(self.device())?;
780 let storage_bytes = storage.len()
781 + 64
782 + profile.map_or(0, |capture| capture.buffer.len())
783 + commands
784 .iter()
785 .map(|(command, _)| command.storage_bytes())
786 .sum::<usize>();
787 buffers.push(storage);
788 buffers.sort_by_key(|buffer| Arc::as_ptr(&buffer.0) as usize);
789 buffers.dedup_by(|a, b| Arc::ptr_eq(&a.0, &b.0));
790 let fence = fabric.allocate_shared(64, std::slice::from_ref(self.device()))?;
791 let address = fence.device_address(self.device())?;
792 let mut words = Vec::new();
793 barrier(&mut words);
794 let dispatch_start = words.len();
795 words.extend([
796 header(0x3f, 4),
797 ib_address as u32,
798 (ib_address >> 32) as u32,
799 indirect.len() as u32 | (1 << 23),
800 ]);
801 let dispatch_end = words.len();
802 barrier(&mut words);
803 let fence_word = words.len() + 4;
804 words.extend([
805 header(0x37, 5),
806 (2 << 8) | (1 << 20),
807 address as u32,
808 (address >> 32) as u32,
809 1,
810 ]);
811 pad(&mut words);
812 let leases = Mutex::new(Vec::with_capacity(buffers.len()));
813 Ok(PreparedGpu {
814 prefix: Default::default(),
815 queue: self.clone(),
816 inner: Arc::new(PreparedInner {
817 storage_bytes,
818 work: Arc::new(Work {
819 fence,
820 _kernels: kernels,
821 buffers,
822 leases,
823 retired: AtomicU32::new(0),
824 active: AtomicU32::new(0),
825 dependencies,
826 }),
827 words,
828 dispatch_range: dispatch_start..dispatch_end,
829 indirect_words: Some(indirect),
830 fence_word,
831 previous: Mutex::new(0),
832 }),
833 })
834 }
835 pub fn prepare_wait(&self, completion: &Completion) -> Result<PreparedGpu> {
838 if self.device().id() != completion.queue.device().id() {
839 return Err(Error::Message("event belongs to another device".into()));
840 }
841 let dependencies = {
847 let state = completion
848 .queue
849 .0
850 .state
851 .lock()
852 .map_err(|_| Error::DeviceLost("event producer poisoned".into()))?;
853 match state.pending.iter().position(|pending| {
854 Arc::ptr_eq(&pending.work, &completion.work) && pending.value == completion.value
855 }) {
856 Some(end) => state
857 .pending
858 .iter()
859 .take(end + 1)
860 .map(|pending| (pending.work.clone(), pending.value))
861 .collect(),
862 None => vec![(completion.work.clone(), completion.value)],
863 }
864 };
865 if self.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA {
866 return self.prepare_sdma_wait(completion, dependencies);
867 }
868 let address = completion.work.fence.device_address(self.device())?;
869 let fence = self
870 .device()
871 .fabric()
872 .allocate_shared(64, std::slice::from_ref(self.device()))?;
873 let output = fence.device_address(self.device())?;
874 let mut words = Vec::with_capacity(40);
875 words.extend([
877 header(0x3c, 7),
878 5 | (1 << 4),
879 address as u32,
880 (address >> 32) as u32,
881 completion.value,
882 u32::MAX,
883 4,
884 ]);
885 barrier(&mut words);
886 let dispatch_end = words.len();
887 let fence_word = words.len() + 4;
888 words.extend([
889 header(0x37, 5),
890 (2 << 8) | (1 << 20),
891 output as u32,
892 (output >> 32) as u32,
893 1,
894 ]);
895 pad(&mut words);
896 Ok(PreparedGpu {
897 prefix: Default::default(),
898 queue: self.clone(),
899 inner: Arc::new(PreparedInner {
900 storage_bytes: fence.len(),
901 work: Arc::new(Work {
902 fence,
903 _kernels: Vec::new(),
904 buffers: Vec::new(),
905 leases: Mutex::new(Vec::new()),
906 retired: AtomicU32::new(0),
907 active: AtomicU32::new(0),
908 dependencies,
909 }),
910 words,
911 dispatch_range: 0..dispatch_end,
912 indirect_words: None,
913 fence_word,
914 previous: Mutex::new(0),
915 }),
916 })
917 }
918 pub unsafe fn dispatch(
924 &self,
925 kernel: &Kernel,
926 grid: [u32; 3],
927 block: [u16; 3],
928 arguments: &[Argument<'_>],
929 ) -> Result<Completion> {
930 unsafe { self.prepare(kernel, grid, block, arguments)?.dispatch() }
931 }
932}
933
934#[derive(Clone)]
937pub struct PreparedGpu {
938 prefix: Option<Arc<[PreparedGpu]>>,
941 queue: Queue,
942 inner: Arc<PreparedInner>,
943}
944struct PreparedInner {
945 storage_bytes: usize,
946 work: Arc<Work>,
947 words: Vec<u32>,
948 dispatch_range: std::ops::Range<usize>,
949 indirect_words: Option<Vec<u32>>,
951 fence_word: usize,
952 previous: Mutex<u32>,
953}
954impl PreparedGpu {
955 fn dispatch_words(&self) -> &[u32] {
956 self.inner
957 .indirect_words
958 .as_deref()
959 .unwrap_or_else(|| &self.inner.words[self.inner.dispatch_range.clone()])
960 }
961
962 fn reserve_fence(&mut self, reservation: Option<Arc<crate::residency::MemoryReservation>>) {
963 let inner = Arc::get_mut(&mut self.inner).expect("new prepared transfer");
964 let work = Arc::get_mut(&mut inner.work).expect("new transfer work");
965 Arc::get_mut(&mut work.fence.0)
966 .expect("new transfer fence")
967 .reservation = reservation;
968 }
969
970 pub(crate) fn retain_parts(&self) -> Result<impl Drop> {
972 if self.prefix.is_none() {
973 return Ok(Vec::new());
974 }
975 self.inner
976 .work
977 .buffers
978 .iter()
979 .map(Buffer::retain_use)
980 .collect::<Result<Vec<_>>>()
981 }
982
983 #[cfg(test)]
984 pub(crate) fn exhaust_final_timeline(&self) {
985 *self.inner.previous.lock().unwrap() = u32::MAX;
986 }
987
988 pub(crate) fn storage_bytes(&self) -> usize {
991 self.inner.storage_bytes
992 + self.prefix.as_ref().map_or(0, |parts| {
993 parts.iter().map(Self::storage_bytes).sum::<usize>()
994 })
995 }
996
997 pub(crate) fn parts(&self) -> impl Iterator<Item = &Self> {
998 self.prefix
999 .iter()
1000 .flat_map(|parts| parts.iter())
1001 .chain(std::iter::once(self))
1002 }
1003
1004 pub(crate) unsafe fn dispatch_part_wait(&self) -> Result<Completion> {
1007 unsafe { self.dispatch_one(true) }
1008 }
1009
1010 pub unsafe fn dispatch(&self) -> Result<Completion> {
1019 if self.prefix.is_some() {
1020 unsafe { self.dispatch_wait() }
1021 } else {
1022 unsafe { self.dispatch_one(false) }
1023 }
1024 }
1025
1026 pub(crate) unsafe fn dispatch_wait(&self) -> Result<Completion> {
1029 let _uses = self.retain_parts()?;
1030 let mut last: Option<Completion> = None;
1031 for part in self.parts() {
1032 match unsafe { part.dispatch_one(true) } {
1033 Ok(done) => last = Some(done),
1034 Err(error) => {
1035 if let Some(done) = last {
1036 done.wait()?;
1037 }
1038 return Err(error);
1039 }
1040 }
1041 }
1042 Ok(last.expect("prepared command has a final part"))
1043 }
1044
1045 unsafe fn dispatch_one(&self, wait: bool) -> Result<Completion> {
1046 let mut previous = self
1047 .inner
1048 .previous
1049 .lock()
1050 .map_err(|_| Error::DeviceLost("prepared command poisoned".into()))?;
1051 let value = previous
1052 .checked_add(1)
1053 .ok_or_else(|| Error::DeviceLost("prepared completion timeline exhausted".into()))?;
1054 let words = &self.inner.words;
1055 let capacity = self.queue.0.info.ring_byte_length / 4;
1056 let sdma = self.queue.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA;
1057 let index_scale = if sdma { 4 } else { 1 };
1058 let (mut state, cursor, wrap, required) = loop {
1059 let mut state = self
1060 .queue
1061 .0
1062 .state
1063 .lock()
1064 .map_err(|_| Error::DeviceLost("queue producer poisoned".into()))?;
1065 while let Some(work) = state.pending.front() {
1067 if !self.queue.0.poll(&work.work, work.value)? {
1068 break;
1069 }
1070 state.pending.pop_front();
1071 }
1072 let cursor = state.write & (capacity - 1);
1073 let wrap = if words.len() as u64 > capacity - cursor {
1074 capacity - cursor
1075 } else {
1076 0
1077 };
1078 let required = wrap + words.len() as u64;
1079 let read = unsafe {
1080 (&*(self.queue.0.info.read_index_address as *const AtomicU64))
1081 .load(Ordering::Acquire)
1082 } / index_scale;
1083 let outstanding = state.write.wrapping_sub(read) & (capacity - 1);
1084 let full = if state.pending.len() >= 4096 {
1085 Some("native queue submission capacity")
1086 } else if required >= capacity - outstanding {
1087 Some("native command ring is full")
1088 } else {
1089 None
1090 };
1091 let Some(reason) = full else {
1092 break (state, cursor, wrap, required);
1093 };
1094 if !wait {
1095 return Err(Error::Busy(reason.into()));
1096 }
1097 let oldest = state.pending.front().map(|pending| Completion {
1098 queue: self.queue.clone(),
1099 work: pending.work.clone(),
1100 value: pending.value,
1101 });
1102 drop(state);
1103 wait_for_capacity(oldest)?;
1104 };
1105 let published = state
1106 .write
1107 .checked_add(required)
1108 .ok_or_else(|| Error::DeviceLost("native queue index exhausted".into()))?;
1109 let native_published = published
1110 .checked_mul(index_scale)
1111 .ok_or_else(|| Error::DeviceLost("native queue byte index exhausted".into()))?;
1112 let work = self.inner.work.clone();
1113 let mut leases = work
1114 .leases
1115 .lock()
1116 .map_err(|_| Error::DeviceLost("submission ownership poisoned".into()))?;
1117 if leases.is_empty() {
1118 for buffer in &work.buffers {
1119 match buffer.retain_use() {
1120 Ok(lease) => leases.push(lease),
1121 Err(error) => {
1122 leases.clear();
1123 return Err(error);
1124 }
1125 }
1126 }
1127 }
1128 work.active.store(value, Ordering::Release);
1129 state.pending.push_back(Pending {
1131 work: work.clone(),
1132 value,
1133 });
1134 drop(leases);
1135 unsafe {
1136 let ring = self.queue.0.info.ring_address as *mut u32;
1137 if wrap != 0 {
1138 ptr::write_bytes(ring.add(cursor as usize), 0, wrap as usize);
1139 if !sdma {
1140 ptr::write(ring.add(cursor as usize), header(0x10, wrap as usize));
1141 }
1142 }
1143 let cursor = if wrap == 0 { cursor as usize } else { 0 };
1144 ptr::copy_nonoverlapping(words.as_ptr(), ring.add(cursor), words.len());
1145 ptr::write(ring.add(cursor + self.inner.fence_word), value);
1146 (&*(self.queue.0.info.write_index_address as *const AtomicU64))
1147 .store(native_published, Ordering::Release);
1148 std::sync::atomic::fence(Ordering::SeqCst);
1149 ptr::write_volatile(
1150 self.queue.0.info.doorbell_address as *mut u64,
1151 native_published,
1152 );
1153 }
1154 state.write = published;
1155 *previous = value;
1156 Ok(Completion {
1157 queue: self.queue.clone(),
1158 work,
1159 value,
1160 })
1161 }
1162}
1163
1164fn wait_for_capacity(oldest: Option<Completion>) -> Result<()> {
1165 oldest
1168 .ok_or_else(|| Error::Busy("native command ring is full without pending work".into()))?
1169 .wait()
1170}
1171
1172impl Queue {
1173 pub fn family_ordinal(&self) -> u32 {
1175 self.0.family
1176 }
1177 pub fn command(&self) -> QueueCommand {
1179 if self.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA {
1180 QueueCommand::Sdma
1181 } else {
1182 QueueCommand::Pm4
1183 }
1184 }
1185 pub fn device(&self) -> &Device {
1187 &self.0.device
1188 }
1189 fn transfer_kernels(&self) -> Result<[Kernel; 4]> {
1190 let mut kernels = self
1191 .0
1192 .transfers
1193 .lock()
1194 .map_err(|_| Error::DeviceLost("transfer compiler poisoned".into()))?;
1195 if kernels.is_none() {
1196 let compiler = crate::loom::Compiler::for_target(None, self.device().target())?;
1197 let source =
1198 crate::loom::CxxSource::new("hrx-transfers.cpp", include_str!("transfers.cpp"));
1199 let module = compiler.import_cxx(source)?;
1200 let request = |symbol: &str| {
1204 crate::loom::Specialization::new(symbol)
1205 .with_config(format!("{symbol}.workgroup_count.x"), "16777216")
1206 .with_config(format!("{symbol}.workgroup_count.y"), "1")
1207 .with_config(format!("{symbol}.workgroup_count.z"), "1")
1208 };
1209 let mut entries = Vec::new();
1210 for symbol in ["hrx_copy", "hrx_fill", "hrx_copy_words", "hrx_fill_words"] {
1211 let artifact = module.compile(&request(symbol))?;
1212 entries.push(unsafe { self.device().load(&artifact) }?);
1214 }
1215 *kernels = Some(entries.try_into().unwrap_or_else(|_| unreachable!()));
1216 }
1217 Ok(kernels.as_ref().unwrap().clone())
1218 }
1219 pub fn prepare_copy(
1223 &self,
1224 destination: &Buffer,
1225 destination_offset: usize,
1226 source: &Buffer,
1227 source_offset: usize,
1228 length: usize,
1229 ) -> Result<PreparedGpu> {
1230 transfer_range(destination, destination_offset, length)?;
1231 transfer_range(source, source_offset, length)?;
1232 let dst = destination
1233 .device_address(self.device())?
1234 .checked_add(destination_offset as u64)
1235 .ok_or_else(|| Error::Message("copy address overflow".into()))?;
1236 let src = source
1237 .device_address(self.device())?
1238 .checked_add(source_offset as u64)
1239 .ok_or_else(|| Error::Message("copy address overflow".into()))?;
1240 if dst < src.saturating_add(length as u64) && src < dst.saturating_add(length as u64) {
1241 return Err(Error::Message("native copy ranges overlap".into()));
1242 }
1243 self.prepare_transfer(length, |offset, bytes| {
1244 self.prepare_copy_part(
1245 destination,
1246 destination_offset + offset,
1247 source,
1248 source_offset + offset,
1249 bytes,
1250 )
1251 })
1252 }
1253
1254 fn prepare_transfer(
1255 &self,
1256 length: usize,
1257 mut prepare: impl FnMut(usize, usize) -> Result<PreparedGpu>,
1258 ) -> Result<PreparedGpu> {
1259 let sdma = self.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA;
1260 let limit = if sdma {
1261 sdma::transfer_limit(self.0.info.ring_byte_length as usize)?
1262 } else {
1263 (u32::MAX as usize) & !255
1266 };
1267 if length <= limit {
1268 return prepare(0, length);
1269 }
1270 let mut parts = transfer_chunks(length, limit)
1271 .map(|(offset, bytes)| prepare(offset, bytes))
1272 .collect::<Result<Vec<_>>>()?;
1273 let mut last = parts.pop().expect("validated nonempty transfer");
1274 if sdma {
1275 last.prefix = Some(parts.into());
1276 Ok(last)
1277 } else {
1278 parts.push(last);
1279 let commands: Vec<_> = parts.into_iter().map(|part| (part, false)).collect();
1282 unsafe { self.prepare_batch(&commands) }
1283 }
1284 }
1285
1286 fn prepare_copy_part(
1288 &self,
1289 destination: &Buffer,
1290 destination_offset: usize,
1291 source: &Buffer,
1292 source_offset: usize,
1293 length: usize,
1294 ) -> Result<PreparedGpu> {
1295 transfer_range(destination, destination_offset, length)?;
1296 transfer_range(source, source_offset, length)?;
1297 if Arc::ptr_eq(&destination.0, &source.0)
1298 && destination_offset < source_offset + length
1299 && source_offset < destination_offset + length
1300 {
1301 return Err(Error::Message("native copy ranges overlap".into()));
1302 }
1303 if self.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA {
1304 return self.prepare_sdma_copy(
1305 destination,
1306 destination_offset,
1307 source,
1308 source_offset,
1309 length,
1310 );
1311 }
1312 let kernels = self.transfer_kernels()?;
1313 let wide = (destination_offset | source_offset | length) & 7 == 0;
1314 let copy = &kernels[if wide { 2 } else { 0 }];
1315 let elements = if wide { length / 8 } else { length };
1316 let count = (elements as u64).to_le_bytes();
1317 unsafe {
1318 self.prepare(
1319 copy,
1320 [elements.div_ceil(256) as u32, 1, 1],
1321 [256, 1, 1],
1322 &[
1323 Argument::Buffer(destination, destination_offset),
1324 Argument::Buffer(source, source_offset),
1325 Argument::Value(&count),
1326 ],
1327 )
1328 }
1329 }
1330 pub(crate) fn prepare_copy_reserved(
1331 &self,
1332 destination: &Buffer,
1333 destination_offset: usize,
1334 source: &Buffer,
1335 source_offset: usize,
1336 length: usize,
1337 budget: Option<&crate::residency::MemoryBudget>,
1338 ) -> Result<PreparedGpu> {
1339 let count = if self.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA {
1342 length.div_ceil(sdma::transfer_limit(self.0.info.ring_byte_length as usize)?)
1343 } else {
1344 1
1345 };
1346 let bytes = count
1347 .checked_mul(64)
1348 .ok_or_else(|| Error::Message("transfer fence size overflow".into()))?;
1349 let reservation = budget
1350 .map(|budget| budget.reserve(bytes).map(Arc::new))
1351 .transpose()?;
1352 let mut command = self.prepare_copy(
1353 destination,
1354 destination_offset,
1355 source,
1356 source_offset,
1357 length,
1358 )?;
1359 if let Some(prefix) = &mut command.prefix {
1360 for part in Arc::get_mut(prefix).expect("new transfer prefix") {
1361 part.reserve_fence(reservation.clone());
1362 }
1363 }
1364 command.reserve_fence(reservation);
1365 Ok(command)
1366 }
1367 pub fn prepare_fill(
1369 &self,
1370 destination: &Buffer,
1371 offset: usize,
1372 length: usize,
1373 value: u8,
1374 ) -> Result<PreparedGpu> {
1375 transfer_range(destination, offset, length)?;
1376 self.prepare_transfer(length, |first, bytes| {
1377 self.prepare_fill_part(destination, offset + first, bytes, value)
1378 })
1379 }
1380
1381 fn prepare_fill_part(
1383 &self,
1384 destination: &Buffer,
1385 offset: usize,
1386 length: usize,
1387 value: u8,
1388 ) -> Result<PreparedGpu> {
1389 transfer_range(destination, offset, length)?;
1390 if self.0.info.command_type == AMDF_QUEUE_COMMAND_TYPE_GPU_SDMA {
1391 return self.prepare_sdma_fill(destination, offset, length, value);
1392 }
1393 let kernels = self.transfer_kernels()?;
1394 let wide = (offset | length) & 7 == 0;
1395 let fill = &kernels[if wide { 3 } else { 1 }];
1396 let elements = if wide { length / 8 } else { length };
1397 let count = (elements as u64).to_le_bytes();
1398 let value = u32::from(value).to_le_bytes();
1399 unsafe {
1400 self.prepare(
1401 fill,
1402 [elements.div_ceil(256) as u32, 1, 1],
1403 [256, 1, 1],
1404 &[
1405 Argument::Buffer(destination, offset),
1406 Argument::Value(&count),
1407 Argument::Value(&value),
1408 ],
1409 )
1410 }
1411 }
1412}
1413fn transfer_chunks(length: usize, limit: usize) -> impl Iterator<Item = (usize, usize)> {
1414 (0..length)
1415 .step_by(limit)
1416 .map(move |offset| (offset, (length - offset).min(limit)))
1417}
1418
1419fn transfer_range(buffer: &Buffer, offset: usize, length: usize) -> Result<()> {
1420 if length == 0
1421 || offset
1422 .checked_add(length)
1423 .is_none_or(|end| end > buffer.len())
1424 {
1425 return Err(Error::Message(
1426 "native transfer range must be nonempty and in bounds".into(),
1427 ));
1428 }
1429 Ok(())
1430}
1431
1432#[cfg(test)]
1433mod event_tests {
1434 use super::*;
1435
1436 #[test]
1437 fn large_transfer_chunks_cover_boundaries_without_wrapping() {
1438 let limit = (u32::MAX as usize) & !255;
1439 for length in [
1440 1,
1441 limit - 1,
1442 limit,
1443 limit + 1,
1444 1usize << 32,
1445 (1usize << 33) + 19,
1446 ] {
1447 let mut end = 0;
1448 for (offset, bytes) in transfer_chunks(length, limit) {
1449 assert_eq!(offset, end);
1450 assert_eq!(offset % 8, 0);
1451 assert!((1..=limit).contains(&bytes));
1452 assert!(bytes.div_ceil(256) <= u32::MAX as usize / 256);
1453 end += bytes;
1454 }
1455 assert_eq!(end, length);
1456 }
1457 }
1458
1459 #[test]
1460 #[ignore = "requires gfx1151 and native SDMA"]
1461 fn split_transfer_failure_drains_prefix_and_retains_fence_budget() -> Result<()> {
1462 let gpu = Device::open(Engine::Gpu, 0)?;
1463 let queue = gpu.sdma_queue()?;
1464 let limit = sdma::transfer_limit(queue.0.info.ring_byte_length as usize)?;
1465 let bytes = 2 * limit + 17;
1466 let source = gpu
1467 .fabric()
1468 .allocate_shared(bytes, std::slice::from_ref(&gpu))?;
1469 let target = gpu
1470 .fabric()
1471 .allocate_shared(bytes, std::slice::from_ref(&gpu))?;
1472 for offset in [0, limit, 2 * limit] {
1474 source.write(offset, &[0x51])?;
1475 }
1476 target.write(2 * limit, &[0])?;
1477 let manager = crate::residency::ResidencyManager::new(4096)?;
1478 let command =
1479 queue.prepare_copy_reserved(&target, 0, &source, 0, bytes, Some(&manager.budget()))?;
1480 assert_eq!(command.parts().count(), 3);
1481 assert_eq!(manager.statistics().reserved_bytes, 3 * 64);
1482 command.exhaust_final_timeline();
1483 assert!(matches!(
1484 unsafe { command.dispatch() },
1485 Err(Error::DeviceLost(_))
1486 ));
1487 for offset in [0, limit] {
1488 let mut actual = [0];
1489 target.read(offset, &mut actual)?;
1490 assert_eq!(actual, [0x51]);
1491 }
1492 let mut tail = [0];
1493 target.read(2 * limit, &mut tail)?;
1494 assert_eq!(tail, [0]);
1495 for part in command.prefix.as_ref().unwrap().iter() {
1496 assert_eq!(part.inner.work.retired.load(Ordering::Acquire), 1);
1497 }
1498 drop(command);
1499 assert_eq!(manager.statistics().reserved_bytes, 0);
1500 Ok(())
1501 }
1502
1503 #[test]
1504 fn a_full_ring_without_pending_work_returns_busy() {
1505 assert!(matches!(wait_for_capacity(None), Err(Error::Busy(_))));
1506 }
1507
1508 #[test]
1509 fn stream_queue_configuration_is_positive_and_frozen_after_first_use() -> Result<()> {
1510 let mut pool = StreamQueues::default();
1511 assert!(pool.set_limit(0).is_err());
1512 pool.set_limit(2)?;
1513 assert_eq!(pool.limit, 2);
1514 assert!(
1515 pool.slots.is_empty(),
1516 "configuration must not allocate queues"
1517 );
1518 pool.slots.push(Weak::new());
1520 pool.set_limit(2)?;
1521 assert!(matches!(pool.set_limit(8), Err(Error::Busy(_))));
1522 assert_eq!(pool.limit, 2);
1523 Ok(())
1524 }
1525
1526 #[test]
1527 #[ignore = "requires gfx1151 and provisioned native libraries"]
1528 fn stream_queue_pool_is_bounded_shared_and_weak() -> Result<()> {
1529 let device = Device::open(Engine::Gpu, 0)?;
1530 device.set_stream_queue_count(2)?;
1531 let queues = std::thread::scope(|scope| {
1532 let workers: Vec<_> = (0..8)
1533 .map(|_| {
1534 scope.spawn(|| {
1535 let reopened = Device::open(Engine::Gpu, 0)?;
1536 (0..16)
1537 .map(|_| reopened.stream_queue())
1538 .collect::<Result<Vec<_>>>()
1539 })
1540 })
1541 .collect();
1542 workers
1543 .into_iter()
1544 .map(|worker| worker.join().unwrap())
1545 .collect::<Result<Vec<_>>>()
1546 })?;
1547 let identities: std::collections::HashSet<_> = queues
1548 .iter()
1549 .flatten()
1550 .map(|queue| Arc::as_ptr(&queue.0))
1551 .collect();
1552 assert_eq!(identities.len(), 2);
1553 let weak: Vec<_> = queues
1554 .iter()
1555 .flatten()
1556 .map(|queue| Arc::downgrade(&queue.0))
1557 .collect();
1558 drop(queues);
1559 assert!(weak.iter().all(|queue| queue.upgrade().is_none()));
1560 let replacement = device.stream_queue()?;
1561 assert_eq!(replacement.device().id(), device.id());
1562 Ok(())
1563 }
1564
1565 #[test]
1566 #[ignore = "requires gfx1151 and provisioned native libraries"]
1567 fn shared_queue_submission_waits_for_other_producers() -> Result<()> {
1568 let device = Device::open(Engine::Gpu, 0)?;
1569 let fabric = device.fabric();
1570 let queue = device.stream_queue()?;
1571 let gate = Completion {
1572 queue: queue.clone(),
1573 work: Arc::new(Work {
1574 fence: fabric.allocate_shared(64, std::slice::from_ref(&device))?,
1575 _kernels: Vec::new(),
1576 buffers: Vec::new(),
1577 leases: Mutex::new(Vec::new()),
1578 retired: AtomicU32::new(0),
1579 active: AtomicU32::new(1),
1580 dependencies: Vec::new(),
1581 }),
1582 value: 1,
1583 };
1584 let buffer = fabric.allocate(64, std::slice::from_ref(&device))?;
1585 let fill = queue.prepare_fill(&buffer, 0, 64, 19)?;
1586 let final_fill = queue.prepare_fill(&buffer, 0, 64, 42)?;
1587 unsafe { queue.prepare_wait(&gate)?.dispatch()? };
1588 let mut submitted = 0;
1589 loop {
1590 match unsafe { fill.dispatch() } {
1591 Ok(_) => submitted += 1,
1592 Err(Error::Busy(_)) => break,
1593 Err(error) => {
1594 gate.work.fence.write(0, &1u32.to_le_bytes())?;
1595 return Err(error);
1596 }
1597 }
1598 }
1599 let (sent, received) = std::sync::mpsc::channel();
1600 let worker = std::thread::spawn(move || {
1601 sent.send(unsafe { final_fill.dispatch_wait() }).unwrap();
1602 });
1603 let blocked = received.recv_timeout(Duration::from_millis(50));
1604 gate.work.fence.write(0, &1u32.to_le_bytes())?;
1605 worker.join().unwrap();
1606 assert!(submitted > 0);
1607 assert!(matches!(
1608 blocked,
1609 Err(std::sync::mpsc::RecvTimeoutError::Timeout)
1610 ));
1611 let done = received.recv().unwrap()?;
1612 assert!(done.wait_timeout(Duration::from_secs(5))?);
1613 let mut actual = [0; 64];
1614 buffer.read(0, &mut actual)?;
1615 assert_eq!(actual, [42; 64]);
1616 Ok(())
1617 }
1618
1619 #[test]
1620 fn graph_batch_ranges_reserve_barriers_markers_and_padding() -> Result<()> {
1621 assert!(batch_ranges([], false)?.is_empty());
1622 assert_eq!(batch_ranges([(100, false); 3], false)?, vec![0..3]);
1623 let near_limit = INDIRECT_WORD_LIMIT - 20;
1624 assert_eq!(
1625 batch_ranges([(near_limit, true), (1, false)], false)?,
1626 vec![0..1, 1..2]
1627 );
1628 assert!(batch_ranges([(INDIRECT_WORD_LIMIT - 9, false)], false).is_err());
1629 assert!(batch_ranges([(usize::MAX, true)], true).is_err());
1630 assert_eq!(
1631 batch_ranges([(INDIRECT_WORD_LIMIT / 2 - 10, false); 2], false)?,
1632 vec![0..2]
1633 );
1634 assert_eq!(
1635 batch_ranges([(INDIRECT_WORD_LIMIT / 2 - 10, false); 2], true)?,
1636 vec![0..1, 1..2]
1637 );
1638 Ok(())
1639 }
1640
1641 #[test]
1642 #[ignore = "requires gfx1151 and provisioned native libraries"]
1643 fn observing_an_event_retires_every_earlier_submission() -> Result<()> {
1644 let device = Device::open(Engine::Gpu, 0)?;
1645 let fabric = device.fabric();
1646 let producer = device.queue()?;
1647 let consumer = device.queue()?;
1648 let gate = Completion {
1652 queue: device.queue()?,
1653 work: Arc::new(Work {
1654 fence: fabric.allocate_shared(64, std::slice::from_ref(&device))?,
1655 _kernels: Vec::new(),
1656 buffers: Vec::new(),
1657 leases: Mutex::new(Vec::new()),
1658 retired: AtomicU32::new(0),
1659 active: AtomicU32::new(1),
1660 dependencies: Vec::new(),
1661 }),
1662 value: 1,
1663 };
1664 let first = fabric.allocate(64, std::slice::from_ref(&device))?;
1665 let last = fabric.allocate(64, std::slice::from_ref(&device))?;
1666 unsafe { producer.prepare_wait(&gate)?.dispatch()? };
1667 unsafe {
1668 producer
1669 .prepare_fill(&first, 0, first.len(), 37)?
1670 .dispatch()?
1671 };
1672 let event = unsafe {
1673 producer
1674 .prepare_fill(&last, 0, last.len(), 91)?
1675 .dispatch()?
1676 };
1677 let observed = unsafe { consumer.prepare_wait(&event)?.dispatch()? };
1678 assert!(matches!(first.read(0, &mut [0; 64]), Err(Error::Busy(_))));
1679 gate.work.fence.write(0, &1u32.to_le_bytes())?;
1680 if !observed.wait_timeout(Duration::from_secs(5))? {
1681 std::mem::forget(observed);
1682 return Err(Error::DeviceLost("event prefix did not retire".into()));
1683 }
1684 let mut bytes = [0; 64];
1687 first.read(0, &mut bytes)?;
1688 assert_eq!(bytes, [37; 64]);
1689 last.read(0, &mut bytes)?;
1690 assert_eq!(bytes, [91; 64]);
1691 Ok(())
1692 }
1693}