1use super::*;
2use std::{ffi::CString, sync::Mutex};
3#[allow(
4 non_camel_case_types,
5 non_snake_case,
6 non_upper_case_globals,
7 dead_code,
8 missing_docs,
9 unsafe_op_in_unsafe_fn,
10 clippy::all
11)]
12#[path = "bridge_ffi.rs"]
13pub(super) mod ffi;
14use ffi::*;
15
16pub(super) fn bridge_check(api: &Bridge, status: i32) -> Result<()> {
17 if status == 0 {
18 return Ok(());
19 }
20 let message = unsafe { CStr::from_ptr(api.hrx_fabric_error()) }.to_string_lossy();
21 Err(Error::Backend {
22 backend: "native executable",
23 operation: "executable",
24 code: status,
25 message: message.into_owned().into_boxed_str(),
26 })
27}
28
29#[derive(Clone)]
31pub struct Kernel(pub(super) Arc<KernelInner>);
32pub(super) struct KernelInner {
33 pub(super) device: Device,
34 pub(super) code: Buffer,
35 image: Arc<GpuImage>,
36 sanitizer: Option<Arc<super::sanitizer::Feedback>>,
37 feedback_global: std::ops::Range<usize>,
38 instrumentation: Option<ShadowTemplate>,
39 address_state: Option<super::sanitizer::AddressState>,
40 pub(super) race_state: Option<super::sanitizer::RaceState>,
41 pub(super) info: hrx_fabric_gpu_info,
42}
43struct ShadowTemplate {
44 race_global: std::ops::Range<usize>,
45 address_global: std::ops::Range<usize>,
46 options: super::SanitizerRuntimeOptions,
47}
48struct GpuImage {
49 api: Arc<Bridge>,
50 raw: *mut hrx_fabric_gpu_image,
51}
52unsafe impl Send for GpuImage {}
55unsafe impl Sync for GpuImage {}
56impl Drop for GpuImage {
57 fn drop(&mut self) {
58 unsafe { self.api.hrx_fabric_gpu_image_close(self.raw) };
59 }
60}
61
62pub enum Argument<'a> {
64 Buffer(&'a Buffer, usize),
66 Value(&'a [u8]),
68}
69impl Kernel {
70 pub fn sanitizer_reports(&self) -> Result<super::SanitizerReports> {
74 self.0
75 .sanitizer
76 .as_ref()
77 .ok_or_else(|| Error::Unsupported("kernel has no sanitizer runtime".into()))?
78 .drain()
79 }
80 pub(crate) fn same_entry(&self, other: &Self) -> bool {
81 Arc::ptr_eq(&self.0, &other.0)
82 }
83 pub(crate) fn argument_layout(&self) -> Result<Vec<(u32, usize)>> {
84 (0..self.0.info.argument_count)
85 .map(|index| {
86 let mut info = hrx_fabric_gpu_argument::default();
87 bridge_check(&self.0.image.api, unsafe {
88 self.0.image.api.hrx_fabric_gpu_argument_info(
89 self.0.image.raw,
90 index,
91 &mut info,
92 )
93 })?;
94 Ok((info.kind, info.size as usize))
95 })
96 .collect()
97 }
98 pub fn workgroup_size(&self) -> [u32; 3] {
100 self.0.info.workgroup_size
101 }
102 pub fn subgroup_size(&self) -> u32 {
104 self.0.info.wave_size
105 }
106 pub fn workgroup_storage_bytes(&self) -> u64 {
108 u64::from(self.0.info.local_bytes)
109 }
110 pub fn device(&self) -> &Device {
112 &self.0.device
113 }
114 pub(super) fn arguments(&self, arguments: &[Argument<'_>]) -> Result<(Vec<u8>, Vec<Buffer>)> {
115 if arguments.len() != self.0.info.argument_count as usize {
116 return Err(Error::Message("native argument count mismatch".into()));
117 }
118 let mut packed = vec![0; self.0.info.kernarg_bytes as usize];
119 let mut resources = vec![self.0.code.clone()];
120 if let Some(feedback) = &self.0.sanitizer {
121 resources.push(feedback.buffer().clone());
122 }
123 if let Some(state) = &self.0.address_state {
124 resources.push(state.shadow.clone());
125 }
126 if let Some(state) = &self.0.race_state {
127 resources.extend([state.shadow.clone(), state.queue_state.clone()]);
128 }
129 for (index, argument) in arguments.iter().enumerate() {
130 let mut info = hrx_fabric_gpu_argument::default();
131 bridge_check(&self.0.image.api, unsafe {
132 self.0.image.api.hrx_fabric_gpu_argument_info(
133 self.0.image.raw,
134 index as u32,
135 &mut info,
136 )
137 })?;
138 let offset = info.offset as usize;
139 let end = offset
140 .checked_add(info.size as usize)
141 .filter(|end| *end <= packed.len())
142 .ok_or_else(|| Error::Message("native argument range overflow".into()))?;
143 let destination = &mut packed[offset..end];
144 match (info.kind, argument) {
145 (1, Argument::Value(bytes)) if bytes.len() == destination.len() => {
146 destination.copy_from_slice(bytes)
147 }
148 (2, Argument::Buffer(buffer, offset))
149 if destination.len() == 8 && *offset < buffer.len() =>
150 {
151 let address = buffer
152 .device_address(self.device())?
153 .checked_add(*offset as u64)
154 .ok_or_else(|| Error::Message("device argument address overflow".into()))?;
155 destination.copy_from_slice(&address.to_le_bytes());
156 resources.push((*buffer).clone());
157 }
158 _ => {
159 return Err(Error::Message(format!(
160 "native argument {index} has an incompatible kind, offset or size"
161 )));
162 }
163 }
164 }
165 Ok((packed, resources))
166 }
167 pub(super) fn for_aql(
168 &self,
169 grid: [u32; 3],
170 ring: u64,
171 mask: u64,
172 arguments: &[Argument<'_>],
173 ) -> Result<Self> {
174 let Some(template) = &self.0.instrumentation else {
175 return Ok(self.clone());
176 };
177 let code = self.device().fabric().allocate_owned(
178 self.0.code.len(),
179 self.device(),
180 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE | AMDF_MEMORY_ACCESS_EXECUTE,
181 4096,
182 false,
183 template.options.memory_budget.as_ref(),
184 )?;
185 let address_state = if template.address_global.is_empty() {
186 None
187 } else {
188 let mut ranges = vec![(code.device_address(self.device())?, code.len())];
189 for argument in arguments {
190 if let Argument::Buffer(buffer, _) = argument {
191 ranges.push((buffer.device_address(self.device())?, buffer.len()));
192 }
193 }
194 Some(super::sanitizer::AddressState::new(
195 self.device(),
196 &template.options,
197 ranges,
198 )?)
199 };
200 let race_state = if template.race_global.is_empty() {
201 None
202 } else {
203 let mut options = template.options.clone();
204 options.maximum_shadow_bytes -= address_state.as_ref().map_or(0, |s| s.shadow.len());
205 Some(super::sanitizer::RaceState::new(
206 self.device(),
207 &options,
208 self.0.info.local_bytes,
209 grid,
210 ring,
211 mask,
212 )?)
213 };
214 let mut contents = vec![0; code.len()];
215 bridge_check(&self.0.image.api, unsafe {
216 self.0.image.api.hrx_fabric_gpu_image_load(
217 self.0.image.raw,
218 contents.as_mut_ptr(),
219 contents.len(),
220 code.device_address(self.device())?,
221 )
222 })?;
223 self.0
224 .sanitizer
225 .as_ref()
226 .ok_or_else(|| missing("sanitizer feedback"))?
227 .configure(&mut contents[self.0.feedback_global.clone()])?;
228 if let Some(state) = &address_state {
229 state.configure(&mut contents[template.address_global.clone()])?;
230 }
231 if let Some(state) = &race_state {
232 state.configure(&mut contents[template.race_global.clone()])?;
233 }
234 code.write(0, &contents)?;
235 Ok(Self(Arc::new(KernelInner {
236 device: self.device().clone(),
237 code,
238 image: self.0.image.clone(),
239 sanitizer: self.0.sanitizer.clone(),
240 feedback_global: self.0.feedback_global.clone(),
241 instrumentation: None,
242 race_state,
243 address_state,
244 info: self.0.info,
245 })))
246 }
247 pub(super) fn shadow_budget(&self) -> Option<&crate::residency::MemoryBudget> {
248 self.0
249 .instrumentation
250 .as_ref()
251 .and_then(|r| r.options.memory_budget.as_ref())
252 }
253 pub(super) fn dispatch_words(
254 &self,
255 grid: [u32; 3],
256 block: [u16; 3],
257 kernarg: &[u8],
258 address: u64,
259 scratch: Option<(&Buffer, u32, u32)>,
260 ) -> Result<Vec<u32>> {
261 if self.0.sanitizer.is_some() {
262 return Err(Error::Unsupported(
263 "sanitizer instrumentation requires AQL dispatch".into(),
264 ));
265 }
266 let mut words = vec![0; 128];
267 let mut count = 0;
268 bridge_check(&self.0.image.api, unsafe {
269 self.0.image.api.hrx_fabric_gpu_dispatch(
270 self.0.image.raw,
271 self.0.code.device_address(self.device())?,
272 block.as_ptr(),
273 grid.as_ptr(),
274 address,
275 kernarg.as_ptr(),
276 kernarg.len(),
277 scratch
278 .map(|(buffer, _, _)| buffer.device_address(self.device()))
279 .transpose()?
280 .unwrap_or(0),
281 scratch.map_or(0, |(buffer, _, _)| buffer.len() as u64),
282 scratch.map_or(0, |(_, waves, _)| waves),
283 scratch.map_or(0, |(_, _, engines)| engines),
284 words.as_mut_ptr(),
285 words.len() as u32,
286 &mut count,
287 )
288 })?;
289 if count as usize > words.len() {
290 return Err(Error::Message("native command output overflow".into()));
291 }
292 words.truncate(count as usize);
293 Ok(words)
294 }
295}
296
297impl Device {
298 pub unsafe fn load(&self, artifact: &crate::loom::Artifact) -> Result<Kernel> {
304 if self.endpoint().engine() != Engine::Gpu || artifact.target() != self.target().as_str() {
305 return Err(Error::Unsupported("artifact and GPU target differ".into()));
306 }
307 unsafe { self.load_bytes(artifact.bytes(), artifact.symbol()) }
308 }
309 pub unsafe fn load_sanitized(
320 &self,
321 artifact: &crate::loom::Artifact,
322 options: &super::SanitizerRuntimeOptions,
323 ) -> Result<Kernel> {
324 if artifact.target() != self.target().as_str() {
325 return Err(Error::Unsupported("artifact and GPU target differ".into()));
326 }
327 unsafe { self.load_image(artifact.bytes(), artifact.symbol(), Some(options), None) }
328 }
329 pub unsafe fn load_bytes(&self, bytes: &[u8], symbol: &str) -> Result<Kernel> {
334 unsafe { self.load_image(bytes, symbol, None, None) }
335 }
336 pub(super) unsafe fn load_image(
337 &self,
338 bytes: &[u8],
339 symbol: &str,
340 sanitizer_options: Option<&super::SanitizerRuntimeOptions>,
341 code_budget: Option<&crate::residency::MemoryBudget>,
342 ) -> Result<Kernel> {
343 if self.endpoint().engine() != Engine::Gpu {
344 return Err(Error::Unsupported("GPU code requires a GPU device".into()));
345 }
346 let api = &self.0.endpoint.0.instance.api;
347 let bridge = api.load_bridge()?;
348 let symbol = CString::new(symbol)
349 .map_err(|_| Error::Message("kernel symbol contains NUL".into()))?;
350 let mut raw = ptr::null_mut();
351 let mut info = hrx_fabric_gpu_info::default();
352 bridge_check(&bridge, unsafe {
353 bridge.hrx_fabric_gpu_image_open(
354 bytes.as_ptr(),
355 bytes.len(),
356 symbol.as_ptr(),
357 &mut raw,
358 &mut info,
359 )
360 })?;
361 if raw.is_null() {
362 return Err(missing("GPU image"));
363 }
364 let image = GpuImage { api: bridge, raw };
365 let global = |name: &std::ffi::CStr| -> Result<std::ops::Range<usize>> {
366 let mut offset = 0;
367 let mut length = 0;
368 bridge_check(&image.api, unsafe {
369 image.api.hrx_fabric_gpu_global_info(
370 image.raw,
371 name.as_ptr(),
372 &mut offset,
373 &mut length,
374 )
375 })?;
376 let start = usize::try_from(offset)
377 .map_err(|_| Error::Message("global offset overflow".into()))?;
378 let length = usize::try_from(length)
379 .map_err(|_| Error::Message("global size overflow".into()))?;
380 let end = start
381 .checked_add(length)
382 .filter(|end| *end as u64 <= info.storage_bytes)
383 .ok_or_else(|| Error::Message("global extent outside image".into()))?;
384 Ok(start..end)
385 };
386 let feedback_global = global(c"iree_feedback_config")?;
387 match (feedback_global.is_empty(), sanitizer_options.is_some()) {
388 (false, false) => {
389 return Err(Error::Unsupported(
390 "artifact requires sanitizer feedback; use load_sanitized".into(),
391 ));
392 }
393 (true, true) => {
394 return Err(Error::Unsupported(
395 "artifact has no structured sanitizer feedback global".into(),
396 ));
397 }
398 _ => (),
399 }
400 let race_global = global(c"iree_tsan_config")?;
401 let address_global = global(c"iree_asan_config")?;
402 for range in [&race_global, &address_global] {
403 if !range.is_empty() && range.len() != 96 {
404 return Err(Error::Unsupported(
405 "unexpected sanitizer shadow configuration ABI".into(),
406 ));
407 }
408 }
409 let instrumentation = if race_global.is_empty() && address_global.is_empty() {
410 None
411 } else {
412 Some(ShadowTemplate {
413 race_global,
414 address_global,
415 options: sanitizer_options
416 .ok_or_else(|| {
417 Error::Unsupported("shadow instrumentation requires load_sanitized".into())
418 })?
419 .clone(),
420 })
421 };
422 let sites_global = global(c"loom_sanitizer_sites")?;
423 let size = usize::try_from(info.storage_bytes)
424 .map_err(|_| Error::Message("GPU image exceeds address space".into()))?;
425 let fabric = Fabric(self.0.endpoint.0.instance.clone());
426 let code = fabric.allocate_owned(
427 size,
428 self,
429 AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE | AMDF_MEMORY_ACCESS_EXECUTE,
430 4096,
431 false,
432 code_budget,
433 )?;
434 let mut contents = vec![0; size];
435 bridge_check(&image.api, unsafe {
436 image.api.hrx_fabric_gpu_image_load(
437 image.raw,
438 contents.as_mut_ptr(),
439 contents.len(),
440 code.device_address(self)?,
441 )
442 })?;
443 let sanitizer = sanitizer_options
444 .map(|options| {
445 let feedback =
446 super::sanitizer::Feedback::new(self, options, &contents[sites_global])?;
447 let config = contents
448 .get_mut(feedback_global.clone())
449 .filter(|bytes| bytes.len() == 64)
450 .ok_or_else(|| {
451 Error::Unsupported("unexpected feedback configuration ABI".into())
452 })?;
453 feedback.configure(config)?;
454 Ok::<_, Error>(Arc::new(feedback))
455 })
456 .transpose()?;
457 code.write(0, &contents)?;
458 Ok(Kernel(Arc::new(KernelInner {
459 sanitizer,
460 feedback_global,
461 instrumentation,
462 race_state: None,
463 address_state: None,
464 device: self.clone(),
465 code,
466 image: Arc::new(image),
467 info,
468 })))
469 }
470}
471
472pub(super) type BridgeSlot = Mutex<Option<Arc<Bridge>>>;
473
474impl Api {
475 pub(super) fn bridge_path(&self) -> std::path::PathBuf {
478 std::env::var_os("HRX_FABRIC_LIBRARY")
479 .map(std::path::PathBuf::from)
480 .unwrap_or_else(|| self.directory.join("libhrx_fabric.so"))
481 }
482
483 pub(super) fn load_bridge(&self) -> Result<Arc<Bridge>> {
484 let api = self;
485
486 let mut slot = api
487 .bridge
488 .lock()
489 .map_err(|_| Error::Message("bridge loader poisoned".into()))?;
490 if slot.is_none() {
491 *slot = Some(Arc::new(unsafe { Bridge::new(api.bridge_path())? }));
492 }
493 Ok(slot.as_ref().unwrap().clone())
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500 use crate::loom::{Compiler, CxxSource, Specialization};
501
502 #[test]
503 #[ignore = "requires native compiler, bridge, libamdf and gfx1151"]
504 fn replacing_retired_code_at_the_same_address_invalidates_all_instruction_lines() -> Result<()>
505 {
506 let gpu = Device::open(Engine::Gpu, 0)?;
507 let fabric = gpu.fabric();
508 let compiler = Compiler::for_target(None, gpu.target())?;
509 let mut kernels = Vec::new();
510 for stamp in [97u32, 173] {
511 let source = format!(
512 "#include <hip/hip_runtime.h>\n\
513 __global__ [[loom::workgroup_size(64, 1, 1), loom::workgroup_count(256, 1, 1)]]\n\
514 void stamp(unsigned* output) {{\n\
515 unsigned i = blockIdx.x * 64u + threadIdx.x;\n\
516 output[i] = i ^ {stamp}u;\n\
517 }}"
518 );
519 let artifact = compiler
520 .import_cxx(CxxSource::new("stamp.cpp", source))?
521 .compile(&Specialization::new("stamp"))?;
522 kernels.push(unsafe { gpu.load(&artifact) }?);
524 }
525 let mut replacement = Arc::try_unwrap(kernels.pop().unwrap().0)
526 .unwrap_or_else(|_| panic!("newly loaded fixture unexpectedly shared"));
527 let original = kernels.pop().unwrap();
528 assert_eq!(
529 original.0.info.storage_bytes,
530 replacement.info.storage_bytes
531 );
532 replacement.code = original.0.code.clone();
537 let replacement = Kernel(Arc::new(replacement));
538 let queue = gpu.queue()?;
539 let output = fabric.allocate(256 * 64 * 4, std::slice::from_ref(&gpu))?;
540 let mut actual = vec![0; output.len()];
541 for _ in 0..8 {
542 for (kernel, stamp) in [(&original, 97u32), (&replacement, 173u32)] {
543 let mut contents = vec![0; kernel.0.info.storage_bytes as usize];
544 bridge_check(&kernel.0.image.api, unsafe {
545 kernel.0.image.api.hrx_fabric_gpu_image_load(
546 kernel.0.image.raw,
547 contents.as_mut_ptr(),
548 contents.len(),
549 kernel.0.code.device_address(&gpu)?,
550 )
551 })?;
552 kernel.0.code.write(0, &contents)?;
553 output.write(0, &vec![0xcd; output.len()])?;
554 let done = unsafe {
556 queue.dispatch(
557 kernel,
558 [256, 1, 1],
559 [64, 1, 1],
560 &[Argument::Buffer(&output, 0)],
561 )
562 }?;
563 if !done.wait_timeout(std::time::Duration::from_secs(5))? {
564 std::mem::forget(done);
565 return Err(Error::DeviceLost(
566 "code-reuse fixture did not retire".into(),
567 ));
568 }
569 output.read(0, &mut actual)?;
570 for (index, bytes) in actual.as_chunks::<4>().0.iter().enumerate() {
571 assert_eq!(
572 u32::from_le_bytes(*bytes),
573 index as u32 ^ stamp,
574 "stale executable at element {index}, expected stamp {stamp}"
575 );
576 }
577 }
578 }
579 Ok(())
580 }
581}