1use super::*;
3use std::{
4 collections::VecDeque,
5 sync::{
6 Mutex,
7 atomic::{AtomicI64, AtomicU32, AtomicU64, Ordering},
8 },
9 time::{Duration, Instant},
10};
11
12#[derive(Clone)]
14pub struct AqlQueue(Arc<QueueState>);
15struct QueueState {
16 device: Device,
17 raw: *mut amdf_user_queue_t,
18 mapping: *mut amdf_user_queue_mapping_t,
19 info: amdf_user_queue_mapping_info_t,
20 scratch: Option<Buffer>,
21 maximum_private_bytes: u32,
22 budget: Option<crate::residency::MemoryBudget>,
23 producer: Mutex<Producer>,
24}
25struct Producer {
26 write: u64,
27 pending: VecDeque<(Arc<Work>, u64)>,
28}
29unsafe impl Send for QueueState {}
32unsafe impl Sync for QueueState {}
33struct Work {
34 signal: Buffer,
35 buffers: Vec<Buffer>,
36 _kernel: Kernel,
37 leases: Mutex<Vec<memory::DeviceUse>>,
38 active: AtomicU64,
39 retired: AtomicU64,
40}
41impl Work {
42 fn refresh(&self, point: u64) -> Result<bool> {
43 if self.retired.load(Ordering::Acquire) >= point {
44 return Ok(true);
45 }
46 let value = unsafe {
47 (&*self.signal.host_pointer().add(8).cast::<AtomicI64>()).load(Ordering::Acquire)
48 };
49 let completed = i64::MAX
50 .checked_sub(value)
51 .filter(|v| *v >= 0)
52 .ok_or_else(|| Error::DeviceLost("invalid AQL completion signal".into()))?
53 as u64;
54 if completed < point {
55 return Ok(false);
56 }
57 let mut leases = self
58 .leases
59 .lock()
60 .map_err(|_| Error::DeviceLost("AQL leases poisoned".into()))?;
61 if completed >= self.active.load(Ordering::Acquire) {
62 leases.clear();
63 }
64 self.retired.fetch_max(completed, Ordering::Release);
65 Ok(true)
66 }
67}
68impl Drop for QueueState {
69 fn drop(&mut self) {
70 let pending = std::mem::take(
71 &mut self
72 .producer
73 .get_mut()
74 .unwrap_or_else(|e| e.into_inner())
75 .pending,
76 );
77 let deadline = Instant::now() + Duration::from_secs(10);
78 for (work, point) in &pending {
79 loop {
80 match work.refresh(*point) {
81 Ok(true) => break,
82 Ok(false) if Instant::now() < deadline => std::thread::yield_now(),
83 _ => {
84 std::mem::forget(pending);
85 std::mem::forget(self.device.clone());
86 std::mem::forget(self.scratch.take());
87 return;
88 }
89 }
90 }
91 }
92 let api = self.device.0.endpoint.0.instance.api.core;
93 unsafe {
94 if (!self.mapping.is_null()
95 && api.user_queue_mapping_destroy.unwrap()(self.mapping) != 0)
96 || api.user_queue_destroy.unwrap()(self.raw) != 0
97 {
98 std::mem::forget(self.device.clone());
99 std::mem::forget(self.scratch.take());
100 }
101 }
102 }
103}
104#[derive(Clone)]
106pub struct PreparedAql {
107 queue: AqlQueue,
108 work: Arc<Work>,
109 packets: Vec<[u32; 16]>,
110 next: Arc<Mutex<u64>>,
111 storage_bytes: usize,
112}
113#[derive(Clone)]
115pub struct AqlCompletion {
116 queue: AqlQueue,
117 work: Arc<Work>,
118 point: u64,
119}
120impl AqlCompletion {
121 pub fn is_complete(&self) -> bool {
123 self.work.retired.load(Ordering::Acquire) >= self.point
124 }
125 pub fn refresh(&self) -> Result<bool> {
127 if !self.work.refresh(self.point)? {
128 let api = self.queue.0.device.0.endpoint.0.instance.api.core;
129 let mut info = amdf_user_queue_status_t {
130 type_: AMDF_STRUCTURE_TYPE_USER_QUEUE_STATUS,
131 structure_size: size_of::<amdf_user_queue_status_t>() as u32,
132 ..Default::default()
133 };
134 check("AQL queue status", unsafe {
135 entry!(api, user_queue_query_status)(self.queue.0.raw, &mut info)
136 })?;
137 if info.state != AMDF_QUEUE_STATE_ACTIVE {
138 return Err(Error::DeviceLost(format!(
139 "AQL queue status {}",
140 info.terminal_status
141 )));
142 }
143 return Ok(false);
144 }
145 let mut producer = self
146 .queue
147 .0
148 .producer
149 .lock()
150 .map_err(|_| Error::DeviceLost("AQL producer poisoned".into()))?;
151 while let Some((work, point)) = producer.pending.front() {
152 if !work.refresh(*point)? {
153 break;
154 }
155 producer.pending.pop_front();
156 }
157 Ok(true)
158 }
159 pub fn wait_timeout(&self, timeout: Duration) -> Result<bool> {
161 let start = Instant::now();
162 loop {
163 if self.refresh()? {
164 return Ok(true);
165 }
166 if start.elapsed() >= timeout {
167 return Ok(false);
168 }
169 std::thread::yield_now();
170 }
171 }
172 pub fn wait(&self) -> Result<()> {
174 while !self.refresh()? {
175 std::thread::yield_now();
176 }
177 Ok(())
178 }
179}
180impl Device {
181 pub fn aql_queue(&self, maximum_private_bytes: u32) -> Result<AqlQueue> {
185 self.aql_queue_budgeted(maximum_private_bytes, None)
186 }
187 pub fn aql_queue_budgeted(
189 &self,
190 maximum_private_bytes: u32,
191 budget: Option<&crate::residency::MemoryBudget>,
192 ) -> Result<AqlQueue> {
193 if self.endpoint().engine() != Engine::Gpu {
194 return Err(Error::Unsupported("AQL requires a GPU".into()));
195 }
196 let family = self
197 .endpoint()
198 .queue_capabilities()?
199 .into_iter()
200 .find(|f| {
201 f.command == QueueCommand::Aql
202 && f.format_version == 1
203 && f.user_publication
204 && f.system_release
205 && f.system_acquire
206 })
207 .ok_or_else(|| Error::Unsupported("no native AQL v1 family".into()))?;
208 let api = &self.0.endpoint.0.instance.api;
209 let _ = entry!(api.core, user_queue_destroy);
210 let _ = entry!(api.core, user_queue_mapping_destroy);
211 let mut scratch_info = amdf_gpu_queue_scratch_t::default();
212 let scratch = if maximum_private_bytes != 0 {
213 let mut endpoint = amdf_gpu_endpoint_info_t {
214 type_: HRX_AMDF_STRUCTURE_TYPE_GPU_ENDPOINT_INFO,
215 structure_size: size_of::<amdf_gpu_endpoint_info_t>() as u32,
216 ..Default::default()
217 };
218 check("AQL scratch topology", unsafe {
219 entry!(api.gpu, endpoint_query_info)(self.0.endpoint.0.raw, &mut endpoint)
220 })?;
221 let engines = u64::from(endpoint.topology.xcc_count)
222 * u64::from(endpoint.topology.shader_engine_count_per_xcc);
223 if engines == 0 {
224 return Err(Error::Unsupported("empty AQL scratch topology".into()));
225 }
226 let slots = u64::from(endpoint.compute.compute_unit_count).div_ceil(engines)
227 * engines
228 * u64::from(endpoint.compute.maximum_scratch_wave_count_per_compute_unit);
229 let bytes = (u64::from(maximum_private_bytes) * 64)
230 .next_multiple_of(1024)
231 .checked_mul(slots)
232 .filter(|bytes| {
233 *bytes != 0 && *bytes <= 512 * 1024 * 1024 && slots <= u64::from(u32::MAX)
234 })
235 .ok_or_else(|| {
236 Error::Unsupported("AQL scratch exceeds the 512 MiB admission limit".into())
237 })?;
238 let buffer = self.fabric().allocate_owned(
239 bytes as usize,
240 self,
241 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
242 4096,
243 false,
244 budget,
245 )?;
246 scratch_info = amdf_gpu_queue_scratch_t {
247 memory: buffer.0.raw,
248 byte_length: bytes,
249 maximum_private_segment_byte_length: maximum_private_bytes,
250 maximum_wave_count: slots as u32,
251 ..Default::default()
252 };
253 Some(buffer)
254 } else {
255 None
256 };
257 let options = amdf_gpu_user_queue_create_info_t {
258 type_: HRX_AMDF_STRUCTURE_TYPE_GPU_USER_QUEUE_CREATE_INFO,
259 structure_size: size_of::<amdf_gpu_user_queue_create_info_t>() as u32,
260 queue_family_ordinal: family.ordinal,
261 priority: AMDF_QUEUE_PRIORITY_NORMAL,
262 producer_mode: AMDF_QUEUE_PRODUCER_MODE_SINGLE,
263 required_capabilities: AMDF_USER_QUEUE_CAPABILITY_HOST_PRODUCER as u64,
264 scratch: scratch_info,
265 ..Default::default()
266 };
267 let mut raw = ptr::null_mut();
268 check("AQL queue create", unsafe {
269 entry!(api.gpu, user_queue_create)(self.0.raw, &options, &mut raw)
270 })?;
271 if raw.is_null() {
272 return Err(missing("AQL queue"));
273 }
274 let mut queue = QueueState {
275 device: self.clone(),
276 raw,
277 mapping: ptr::null_mut(),
278 info: Default::default(),
279 scratch,
280 maximum_private_bytes,
281 budget: budget.cloned(),
282 producer: Mutex::new(Producer {
283 write: 0,
284 pending: VecDeque::with_capacity(4096),
285 }),
286 };
287 check("AQL queue map", unsafe {
288 entry!(api.core, user_queue_map)(raw, ptr::null_mut(), &mut queue.mapping)
289 })?;
290 queue.info.type_ = AMDF_STRUCTURE_TYPE_USER_QUEUE_MAPPING_INFO;
291 queue.info.structure_size = size_of::<amdf_user_queue_mapping_info_t>() as u32;
292 check("AQL mapping info", unsafe {
293 entry!(api.core, user_queue_mapping_query_info)(queue.mapping, &mut queue.info)
294 })?;
295 let info = &queue.info;
296 if info.command_type != AMDF_QUEUE_COMMAND_TYPE_GPU_AQL
297 || info.format_version != 1
298 || info.index_bits != 64
299 || info.doorbell_bits != 64
300 || !info.ring_byte_length.is_power_of_two()
301 || info.ring_byte_length < 128
302 || [
303 info.ring_address,
304 info.read_index_address,
305 info.write_index_address,
306 info.doorbell_address,
307 ]
308 .iter()
309 .any(|address| *address == 0 || address % 8 != 0)
310 {
311 return Err(Error::Unsupported("unexpected AQL mapping contract".into()));
312 }
313 Ok(AqlQueue(Arc::new(queue)))
314 }
315}
316impl AqlQueue {
317 pub fn device(&self) -> &Device {
319 &self.0.device
320 }
321 pub unsafe fn prepare(
326 &self,
327 kernel: &Kernel,
328 grid: [u32; 3],
329 block: [u16; 3],
330 arguments: &[Argument<'_>],
331 ) -> Result<PreparedAql> {
332 if !Arc::ptr_eq(&kernel.device().0, &self.device().0) {
333 return Err(Error::Message(
334 "AQL kernel belongs to another device".into(),
335 ));
336 }
337 if kernel.0.info.private_bytes > self.0.maximum_private_bytes {
338 return Err(Error::Unsupported(
339 "kernel exceeds fixed AQL scratch capacity".into(),
340 ));
341 }
342 let mut extents = [0; 3];
343 for axis in 0..3 {
344 if block[axis] == 0
345 || grid[axis] == 0
346 || (kernel.workgroup_size()[axis] != 0
347 && kernel.workgroup_size()[axis] != u32::from(block[axis]))
348 {
349 return Err(Error::Message(
350 "AQL launch geometry does not match kernel".into(),
351 ));
352 }
353 extents[axis] = grid[axis]
354 .checked_mul(u32::from(block[axis]))
355 .ok_or_else(|| Error::Message("AQL grid extent overflow".into()))?;
356 }
357 if block.iter().map(|v| u64::from(*v)).product::<u64>() > 1024 {
358 return Err(Error::Message(
359 "AQL workgroup exceeds gfx1151 capacity".into(),
360 ));
361 }
362 let specialized = kernel.for_aql(
363 grid,
364 self.0.info.ring_address,
365 self.0.info.ring_byte_length / 64 - 1,
366 arguments,
367 )?;
368 let clear = if let Some(state) = &specialized.0.race_state {
369 let (utility, clear_grid) =
370 state.clear_kernel(self.device(), kernel.shadow_budget())?;
371 Some(unsafe {
373 self.prepare(
374 &utility,
375 clear_grid,
376 [64, 1, 1],
377 &[Argument::Buffer(&state.shadow, 0)],
378 )
379 }?)
380 } else {
381 None
382 };
383 let kernel = &specialized;
384 let (packed, mut buffers) = kernel.arguments(arguments)?;
385 let fabric = self.device().fabric();
386 let kernarg = fabric.allocate_owned(
387 packed.len().max(64),
388 self.device(),
389 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
390 64,
391 true,
392 self.0.budget.as_ref(),
393 )?;
394 kernarg.write(0, &packed)?;
395 let kernarg_address = kernarg.device_address(self.device())?;
396 let descriptor = kernel
397 .0
398 .code
399 .device_address(self.device())?
400 .checked_add(kernel.0.info.descriptor_offset)
401 .ok_or_else(|| Error::Message("AQL descriptor address overflow".into()))?;
402 let invalidation = [
405 0xc0065800u32,
406 0,
407 u32::MAX,
408 0xff,
409 0,
410 0,
411 0x0a,
412 1 | (1 << 4) | (1 << 5) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 14) | (1 << 15),
413 ];
414 let publication = fabric.allocate_owned(
415 4096,
416 self.device(),
417 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_EXECUTE,
418 4096,
419 false,
420 self.0.budget.as_ref(),
421 )?;
422 publication.write(
423 0,
424 &invalidation
425 .into_iter()
426 .flat_map(u32::to_le_bytes)
427 .collect::<Vec<_>>(),
428 )?;
429 let ib = publication.device_address(self.device())?;
430 if ib >= (1 << 48) || ib + 32 > (1 << 48) {
431 return Err(Error::Unsupported(
432 "AQL publication IB is outside the 48-bit address domain".into(),
433 ));
434 }
435 let signal = fabric.allocate_owned(
436 64,
437 self.device(),
438 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
439 64,
440 true,
441 self.0.budget.as_ref(),
442 )?;
443 signal.write(0, &1i64.to_le_bytes())?;
444 signal.write(8, &i64::MAX.to_le_bytes())?;
445 let signal_address = signal.device_address(self.device())?;
446 let mut publish = [0; 16];
447 publish[0] = (1 << 16) | (1 << 8);
448 publish[1] = 0xc0023f00;
449 publish[2] = ib as u32;
450 publish[3] = (ib >> 32) as u32;
451 publish[4] = (1 << 23) | 8;
452 publish[5] = 10;
453 let mut dispatch = [0; 16];
454 dispatch[0] = 2 | (1 << 8) | (2 << 9) | (2 << 11) | (3 << 16);
455 dispatch[1] = u32::from(block[0]) | (u32::from(block[1]) << 16);
456 dispatch[2] = u32::from(block[2]);
457 dispatch[3..6].copy_from_slice(&extents);
458 dispatch[6] = kernel.0.info.private_bytes;
459 dispatch[7] = kernel.0.info.local_bytes;
460 dispatch[8] = descriptor as u32;
461 dispatch[9] = (descriptor >> 32) as u32;
462 dispatch[10] = kernarg_address as u32;
463 dispatch[11] = (kernarg_address >> 32) as u32;
464 dispatch[14] = signal_address as u32;
465 dispatch[15] = (signal_address >> 32) as u32;
466 let mut packets = vec![publish];
467 if let Some(clear) = clear {
468 let mut packet = *clear.packets.last().unwrap();
469 packet[14..16].fill(0);
470 packets.push(packet);
471 buffers.extend(clear.work.buffers.iter().cloned());
474 }
475 packets.push(dispatch);
476 buffers.extend([kernarg, publication]);
477 buffers.sort_by_key(Buffer::identity);
478 buffers.dedup_by(|a, b| a.same_backing(b));
479 let storage_bytes = signal.len()
480 + buffers
481 .iter()
482 .filter(|buffer| {
483 !arguments.iter().any(|argument| {
484 matches!(argument,
485 Argument::Buffer(bound, _) if buffer.same_backing(bound))
486 })
487 })
488 .map(Buffer::len)
489 .sum::<usize>();
490 let leases = Mutex::new(Vec::with_capacity(buffers.len()));
491 Ok(PreparedAql {
492 queue: self.clone(),
493 work: Arc::new(Work {
494 signal,
495 buffers,
496 _kernel: kernel.clone(),
497 leases,
498 active: AtomicU64::new(0),
499 retired: AtomicU64::new(0),
500 }),
501 packets,
502 next: Arc::new(Mutex::new(0)),
503 storage_bytes,
504 })
505 }
506}
507impl PreparedAql {
508 pub(crate) fn storage_bytes(&self) -> usize {
509 self.storage_bytes
510 }
511
512 pub unsafe fn dispatch(&self) -> Result<AqlCompletion> {
516 let mut next = self
517 .next
518 .lock()
519 .map_err(|_| Error::DeviceLost("AQL dispatch poisoned".into()))?;
520 let point = next
521 .checked_add(1)
522 .filter(|point| *point <= i64::MAX as u64)
523 .ok_or_else(|| Error::DeviceLost("AQL timeline exhausted".into()))?;
524 let mut producer = self
525 .queue
526 .0
527 .producer
528 .lock()
529 .map_err(|_| Error::DeviceLost("AQL producer poisoned".into()))?;
530 while let Some((work, point)) = producer.pending.front() {
531 if !work.refresh(*point)? {
532 break;
533 }
534 producer.pending.pop_front();
535 }
536 let info = &self.queue.0.info;
537 let capacity = info.ring_byte_length / 64;
538 let read =
539 unsafe { (&*(info.read_index_address as *const AtomicU64)).load(Ordering::Acquire) };
540 let end = producer
541 .write
542 .checked_add(self.packets.len() as u64)
543 .ok_or_else(|| Error::DeviceLost("AQL ring index exhausted".into()))?;
544 if producer.pending.len() == 4096 || end.saturating_sub(read) > capacity {
545 return Err(Error::Busy("AQL ring or pending capacity exhausted".into()));
546 }
547 let mut leases = self
548 .work
549 .leases
550 .lock()
551 .map_err(|_| Error::DeviceLost("AQL leases poisoned".into()))?;
552 if leases.is_empty() {
553 for buffer in &self.work.buffers {
554 match buffer.retain_use() {
555 Ok(lease) => leases.push(lease),
556 Err(error) => {
557 leases.clear();
558 return Err(error);
559 }
560 }
561 }
562 }
563 self.work.active.store(point, Ordering::Release);
564 producer.pending.push_back((self.work.clone(), point));
565 unsafe {
566 (&*(info.write_index_address as *const AtomicU64)).store(end, Ordering::Release);
567 for (offset, packet) in self.packets.iter().enumerate() {
568 let index = producer.write + offset as u64;
569 let slot =
570 (info.ring_address as *mut u32).add(((index & (capacity - 1)) * 16) as usize);
571 ptr::copy_nonoverlapping(packet.as_ptr().add(1), slot.add(1), 15);
572 (&*slot.cast::<AtomicU32>()).store(packet[0], Ordering::Release);
573 std::sync::atomic::fence(Ordering::SeqCst);
574 ptr::write_volatile(info.doorbell_address as *mut u64, index);
575 }
576 }
577 producer.write = end;
578 *next = point;
579 Ok(AqlCompletion {
580 queue: self.queue.clone(),
581 work: self.work.clone(),
582 point,
583 })
584 }
585}
586
587#[cfg(test)]
588mod sanitizer_tests {
589 use super::*;
590 use crate::loom::{
591 Compiler, CompilerOptions, SanitizerChecks, SanitizerOptions, SanitizerReporting,
592 Specialization,
593 };
594
595 #[test]
596 #[ignore = "requires current native compiler/bridge and gfx1151"]
597 fn address_shadow_guards_null_wrap_and_every_native_load_width() -> Result<()> {
598 let device = Device::open(Engine::Gpu, 0)?;
599 let queue = device.aql_queue(0)?;
600 let compiler = Compiler::shared(
601 None,
602 CompilerOptions {
603 sanitizer: SanitizerOptions {
604 checks: SanitizerChecks {
605 access: true,
606 ..Default::default()
607 },
608 reporting: SanitizerReporting::ReportOnly,
609 },
610 ..Default::default()
611 },
612 )?;
613 for wave in [32, 64] {
614 for width in [1, 2, 4, 8, 16, 32, 64, 128] {
615 let source = format!(
616 r#"
617amdgpu.target<gfx1151> @target {{subgroup_size = {wave}}}
618kernel.def target(@target) @check_range() {{
619 %one = index.constant 1 : index
620 kernel.launch.config workgroups(%one, %one, %one) workgroup_size(%one, %one, %one) : index
621}} launch(%input: buffer, %output: buffer) {{
622 %zero = index.constant 0 : offset
623 %index = index.constant 0 : index
624 %value = scalar.constant 9 : i32
625 %global = buffer.assume.memory_space<global> %input : buffer
626 %view = buffer.view %global[%zero] : buffer -> view<{width}xi8>
627 sanitizer.assert.access<read> %view[0] {{static_extents = [{width}]}} : view<{width}xi8>
628 %out = buffer.assume.memory_space<global> %output : buffer
629 %out_view = buffer.view %out[%zero] : buffer -> view<1xi32>
630 view.store %value, %out_view[%index] : i32, view<1xi32>
631 kernel.return
632}}
633"#
634 );
635 let artifact = compiler
636 .module(&source)
637 .compile(&Specialization::new("check_range"))?;
638 let kernel = unsafe {
639 device.load_sanitized(&artifact, &SanitizerRuntimeOptions::default())
640 }?;
641 let input = device
642 .fabric()
643 .allocate(width + 1, std::slice::from_ref(&device))?;
644 let output = device.fabric().allocate(4, std::slice::from_ref(&device))?;
645 let prepared = unsafe {
646 queue.prepare(
647 &kernel,
648 [1; 3],
649 [1; 3],
650 &[Argument::Buffer(&input, 0), Argument::Buffer(&output, 0)],
651 )
652 }?;
653 let packet = prepared.packets.last().unwrap();
654 let kernarg_address = u64::from(packet[10]) | (u64::from(packet[11]) << 32);
655 let kernarg = prepared
656 .work
657 .buffers
658 .iter()
659 .find(|b| b.device_address(&device).ok() == Some(kernarg_address))
660 .unwrap();
661 let base = input.device_address(&device)?;
662 for (pointer, valid) in [
663 (base, true),
664 (base + 1, true),
665 (base + 2, false),
666 (base - 1, false),
667 (0, false),
668 (1, false),
669 (u64::MAX - 1, false),
670 (u64::MAX - 7, false),
671 (1 << 63, false),
672 ] {
673 kernarg.write(0, &pointer.to_le_bytes())?;
677 output.write(0, &17u32.to_le_bytes())?;
678 let done = unsafe { prepared.dispatch() }?;
679 assert!(
680 done.wait_timeout(Duration::from_secs(10))?,
681 "wave{wave} width{width} pointer{pointer:x}"
682 );
683 let reports = kernel.sanitizer_reports()?;
684 assert_eq!(reports.dropped, 0);
685 assert_eq!(
686 reports.reports.len(),
687 usize::from(!valid),
688 "wave{wave} width{width} pointer{pointer:x}: {reports:?}"
689 );
690 if !valid {
691 let address = reports.reports[0].address.as_ref().unwrap();
692 assert_eq!(address.fault_address, pointer);
693 assert_eq!(address.access_bytes, width as u64);
694 }
695 let mut actual = [0; 4];
696 output.read(0, &mut actual)?;
697 assert_eq!(u32::from_le_bytes(actual), if valid { 9 } else { 17 });
698 }
699 }
700 }
701 Ok(())
702 }
703
704 #[test]
705 #[ignore = "requires current native compiler/bridge and gfx1151"]
706 fn address_checks_keep_valid_and_invalid_lanes_separate() -> Result<()> {
707 let device = Device::open(Engine::Gpu, 0)?;
708 let queue = device.aql_queue(0)?;
709 let compiler = Compiler::shared(
710 None,
711 CompilerOptions {
712 sanitizer: SanitizerOptions {
713 checks: SanitizerChecks {
714 access: true,
715 ..Default::default()
716 },
717 reporting: SanitizerReporting::ReportOnly,
718 },
719 ..Default::default()
720 },
721 )?;
722 for wave in [32, 64] {
723 let source = format!(
724 r#"
725amdgpu.target<gfx1151> @target {{subgroup_size = {wave}}}
726kernel.def target(@target) @check_lanes() {{
727 %one = index.constant 1 : index
728 %threads = index.constant 64 : index
729 kernel.launch.config workgroups(%one, %one, %one) workgroup_size(%threads, %one, %one) : index
730}} launch(%input: buffer, %output: buffer) {{
731 %zero = index.constant 0 : offset
732 %lane = kernel.workitem.id<x> : index
733 %value = scalar.constant 9 : i32
734 %global = buffer.assume.memory_space<global> %input : buffer
735 %view = buffer.view %global[%zero] : buffer -> view<64xi8>
736 sanitizer.assert.access<read> %view[%lane] : view<64xi8>
737 %out = buffer.assume.memory_space<global> %output : buffer
738 %out_view = buffer.view %out[%zero] : buffer -> view<64xi32>
739 view.store %value, %out_view[%lane] : i32, view<64xi32>
740 kernel.return
741}}
742"#
743 );
744 let artifact = compiler
745 .module(&source)
746 .compile(&Specialization::new("check_lanes"))?;
747 let kernel =
748 unsafe { device.load_sanitized(&artifact, &SanitizerRuntimeOptions::default()) }?;
749 let input = device
750 .fabric()
751 .allocate(33, std::slice::from_ref(&device))?;
752 let output = device
753 .fabric()
754 .allocate(256, std::slice::from_ref(&device))?;
755 output.write(0, &[0; 256])?;
756 let prepared = unsafe {
757 queue.prepare(
758 &kernel,
759 [1; 3],
760 [64, 1, 1],
761 &[Argument::Buffer(&input, 0), Argument::Buffer(&output, 0)],
762 )
763 }?;
764 let done = unsafe { prepared.dispatch() }?;
765 assert!(done.wait_timeout(Duration::from_secs(10))?);
766 let reports = kernel.sanitizer_reports()?;
767 assert_eq!(reports.dropped, 0);
768 assert!(!reports.reports.is_empty());
769 let base = input.device_address(&device)?;
770 for report in reports.reports {
771 let address = report.address.unwrap();
772 assert!((base + 33..base + 64).contains(&address.fault_address));
773 assert!((33..64).contains(&report.workitem_x));
774 }
775 if wave == 32 {
776 let mut first_wave = [0; 128];
777 output.read(0, &mut first_wave)?;
778 for word in first_wave.as_chunks::<4>().0 {
779 assert_eq!(*word, 9u32.to_le_bytes());
780 }
781 }
782 }
783 Ok(())
784 }
785}