1use std::{
4 ffi::c_void,
5 fmt,
6 path::{Path, PathBuf},
7 sync::Arc,
8};
9
10use libloading::Library;
11
12const HIP_NAMES: &[&str] = &["libamdhip64.so.7", "libamdhip64.so.6", "libamdhip64.so"];
13const ROCBLAS_NAMES: &[&str] = &[
14 "librocblas.so.5",
15 "librocblas.so.4",
16 "librocblas.so.0",
17 "librocblas.so",
18];
19const ROCBLASLT_NAMES: &[&str] = &["librocblaslt.so.0", "librocblaslt.so"];
20
21const HIP_SYMBOLS: &[&str] = &[
22 "hipInit",
23 "hipRuntimeGetVersion",
24 "hipGetDeviceCount",
25 "hipMalloc",
26 "hipFree",
27 "hipMemcpy",
28 "hipDeviceSynchronize",
29];
30const ROCBLAS_SYMBOLS: &[&str] = &[
31 "rocblas_create_handle",
32 "rocblas_destroy_handle",
33 "rocblas_sgemm",
34 "rocblas_gemm_ex",
35];
36const ROCBLASLT_SYMBOLS: &[&str] = &["rocblaslt_create_handle", "rocblaslt_destroy_handle"];
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct RocmSymbolEvidence {
41 pub name: String,
43 pub present: bool,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct RocmAbiEvidence {
50 pub hip_library: String,
52 pub rocblas_library: String,
54 pub rocblaslt_library: Option<String>,
56 pub hip_runtime_version: Option<i32>,
58 pub observed_gfx_targets: Vec<String>,
60 pub hip_symbols: Vec<RocmSymbolEvidence>,
62 pub rocblas_symbols: Vec<RocmSymbolEvidence>,
64 pub rocblaslt_symbols: Vec<RocmSymbolEvidence>,
66}
67
68impl RocmAbiEvidence {
69 pub fn is_complete(&self) -> bool {
71 cfg!(target_os = "linux")
72 && !self.observed_gfx_targets.is_empty()
73 && self.hip_symbols.iter().all(|symbol| symbol.present)
74 && self.rocblas_symbols.iter().all(|symbol| symbol.present)
75 }
76}
77
78pub struct RocmLibrarySet {
80 evidence: RocmAbiEvidence,
81 hip: Library,
82 rocblas: Library,
83 rocblaslt: Option<Library>,
84}
85
86impl RocmLibrarySet {
87 pub fn new(
89 evidence: RocmAbiEvidence,
90 hip: Library,
91 rocblas: Library,
92 rocblaslt: Option<Library>,
93 ) -> Self {
94 Self {
95 evidence,
96 hip,
97 rocblas,
98 rocblaslt,
99 }
100 }
101
102 pub fn evidence(&self) -> &RocmAbiEvidence {
104 &self.evidence
105 }
106
107 pub fn handles(&self) -> (&Library, &Library, Option<&Library>) {
109 (&self.hip, &self.rocblas, self.rocblaslt.as_ref())
110 }
111
112 pub(crate) fn execution_handles(&self) -> (&Library, &Library) {
113 (&self.hip, &self.rocblas)
114 }
115}
116
117impl fmt::Debug for RocmLibrarySet {
118 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119 formatter
120 .debug_struct("RocmLibrarySet")
121 .field("evidence", &self.evidence)
122 .finish_non_exhaustive()
123 }
124}
125
126#[derive(Clone, Debug)]
128pub struct RocmRuntimeProbe {
129 pub runtime: Option<Arc<RocmLibrarySet>>,
131 pub evidence: Option<RocmAbiEvidence>,
133 pub diagnostics: Vec<String>,
135}
136
137impl RocmRuntimeProbe {
138 pub fn fake_present(evidence: RocmAbiEvidence) -> Self {
141 Self {
142 runtime: None,
143 evidence: Some(evidence),
144 diagnostics: Vec::new(),
145 }
146 }
147
148 pub fn is_available(&self) -> bool {
150 self.evidence
151 .as_ref()
152 .is_some_and(RocmAbiEvidence::is_complete)
153 }
154}
155
156#[derive(Clone, Debug, PartialEq, Eq)]
158pub struct RocmLoadError {
159 pub message: String,
161}
162
163impl fmt::Display for RocmLoadError {
164 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
165 formatter.write_str(&self.message)
166 }
167}
168
169impl std::error::Error for RocmLoadError {}
170
171pub trait DynamicRocmLoader {
173 fn discover(&self) -> Result<RocmRuntimeProbe, RocmLoadError>;
175}
176
177pub trait RocmProbePort {
179 fn probe_rocm(&self) -> Result<RocmRuntimeProbe, RocmLoadError>;
181}
182
183impl<T: DynamicRocmLoader + ?Sized> RocmProbePort for T {
184 fn probe_rocm(&self) -> Result<RocmRuntimeProbe, RocmLoadError> {
185 self.discover()
186 }
187}
188
189#[derive(Clone, Debug)]
191pub struct RocmRuntimeLoader {
192 search_dirs: Vec<PathBuf>,
193 search_system: bool,
194 observed_gfx_targets: Vec<String>,
195}
196
197impl Default for RocmRuntimeLoader {
198 fn default() -> Self {
199 Self {
200 search_dirs: Vec::new(),
201 search_system: true,
202 observed_gfx_targets: Vec::new(),
203 }
204 }
205}
206
207impl RocmRuntimeLoader {
208 pub fn new() -> Self {
210 Self::default()
211 }
212
213 pub fn with_search_dirs(search_dirs: Vec<PathBuf>) -> Self {
215 Self {
216 search_dirs,
217 search_system: true,
218 observed_gfx_targets: Vec::new(),
219 }
220 }
221
222 pub fn with_search_dirs_only(search_dirs: Vec<PathBuf>) -> Self {
225 Self {
226 search_dirs,
227 search_system: false,
228 observed_gfx_targets: Vec::new(),
229 }
230 }
231
232 pub fn with_observed_gfx_targets(mut self, targets: Vec<String>) -> Self {
234 self.observed_gfx_targets = targets;
235 self
236 }
237}
238
239impl DynamicRocmLoader for RocmRuntimeLoader {
240 fn discover(&self) -> Result<RocmRuntimeProbe, RocmLoadError> {
241 let mut diagnostics = Vec::new();
242 if !cfg!(target_os = "linux") {
243 return Err(RocmLoadError {
244 message: "ROCm provider is supported only on Linux".to_owned(),
245 });
246 }
247 let (hip_name, hip) = self.open_first(HIP_NAMES, &mut diagnostics)?;
248 let (rocblas_name, rocblas) = self.open_first(ROCBLAS_NAMES, &mut diagnostics)?;
249 let rocblaslt = self.open_first(ROCBLASLT_NAMES, &mut diagnostics).ok();
250
251 let hip_symbols = symbol_evidence(&hip, HIP_SYMBOLS);
252 let rocblas_symbols = symbol_evidence(&rocblas, ROCBLAS_SYMBOLS);
253 let rocblaslt_symbols = rocblaslt
254 .as_ref()
255 .map(|(_, library)| symbol_evidence(library, ROCBLASLT_SYMBOLS))
256 .unwrap_or_default();
257 let hip_runtime_version = hip_runtime_version(&hip).ok();
258 let observed_gfx_targets = self.observed_gfx_targets.clone();
259 let evidence = RocmAbiEvidence {
260 hip_library: hip_name,
261 rocblas_library: rocblas_name,
262 rocblaslt_library: rocblaslt.as_ref().map(|(name, _)| name.clone()),
263 hip_runtime_version,
264 observed_gfx_targets,
265 hip_symbols,
266 rocblas_symbols,
267 rocblaslt_symbols,
268 };
269 if !evidence.is_complete() {
270 return Ok(RocmRuntimeProbe {
271 runtime: None,
272 evidence: Some(evidence),
273 diagnostics,
274 });
275 }
276 let runtime = Arc::new(RocmLibrarySet::new(
277 evidence.clone(),
278 hip,
279 rocblas,
280 rocblaslt.map(|(_, library)| library),
281 ));
282 Ok(RocmRuntimeProbe {
283 runtime: Some(runtime),
284 evidence: Some(evidence),
285 diagnostics,
286 })
287 }
288}
289
290impl RocmRuntimeLoader {
291 fn open_first(
292 &self,
293 names: &[&str],
294 diagnostics: &mut Vec<String>,
295 ) -> Result<(String, Library), RocmLoadError> {
296 for name in candidate_paths(&self.search_dirs, names, self.search_system) {
297 match open_library(&name) {
298 Ok(library) => return Ok((name.display().to_string(), library)),
299 Err(error) => diagnostics.push(format!("{}: {error}", name.display())),
300 }
301 }
302 Err(RocmLoadError {
303 message: format!("ROCm library was not found; tried {}", names.join(", ")),
304 })
305 }
306}
307
308#[derive(Clone, Debug)]
310pub struct FakeRocmLoader {
311 probe: Result<RocmRuntimeProbe, RocmLoadError>,
312}
313
314impl FakeRocmLoader {
315 pub fn available() -> Self {
317 Self {
318 probe: Ok(RocmRuntimeProbe::fake_present(complete_fake_evidence())),
319 }
320 }
321
322 pub fn incomplete() -> Self {
324 let mut evidence = complete_fake_evidence();
325 if let Some(symbol) = evidence
326 .rocblas_symbols
327 .iter_mut()
328 .find(|symbol| symbol.name == "rocblas_sgemm")
329 {
330 symbol.present = false;
331 }
332 Self {
333 probe: Ok(RocmRuntimeProbe {
334 runtime: None,
335 evidence: Some(evidence),
336 diagnostics: vec!["missing rocblas_sgemm".to_owned()],
337 }),
338 }
339 }
340
341 pub fn without_rocblaslt() -> Self {
343 let mut evidence = complete_fake_evidence();
344 if let Some(symbol) = evidence
345 .rocblaslt_symbols
346 .iter_mut()
347 .find(|symbol| symbol.name == "rocblaslt_create_handle")
348 {
349 symbol.present = false;
350 }
351 Self {
352 probe: Ok(RocmRuntimeProbe {
353 runtime: None,
354 evidence: Some(evidence),
355 diagnostics: vec!["missing rocblaslt_create_handle".to_owned()],
356 }),
357 }
358 }
359
360 pub fn absent() -> Self {
362 Self {
363 probe: Err(RocmLoadError {
364 message: "ROCm runtime absent".to_owned(),
365 }),
366 }
367 }
368}
369
370impl DynamicRocmLoader for FakeRocmLoader {
371 fn discover(&self) -> Result<RocmRuntimeProbe, RocmLoadError> {
372 self.probe.clone()
373 }
374}
375
376pub fn discover_rocm_runtime() -> Result<RocmRuntimeProbe, RocmLoadError> {
378 RocmRuntimeLoader::new().discover()
379}
380
381fn complete_fake_evidence() -> RocmAbiEvidence {
382 RocmAbiEvidence {
383 hip_library: "fake-libamdhip64".to_owned(),
384 rocblas_library: "fake-librocblas".to_owned(),
385 rocblaslt_library: Some("fake-librocblaslt".to_owned()),
386 hip_runtime_version: Some(6_300_000),
387 observed_gfx_targets: vec!["gfx1103".to_owned()],
388 hip_symbols: HIP_SYMBOLS
389 .iter()
390 .map(|name| RocmSymbolEvidence {
391 name: (*name).to_owned(),
392 present: true,
393 })
394 .collect(),
395 rocblas_symbols: ROCBLAS_SYMBOLS
396 .iter()
397 .map(|name| RocmSymbolEvidence {
398 name: (*name).to_owned(),
399 present: true,
400 })
401 .collect(),
402 rocblaslt_symbols: ROCBLASLT_SYMBOLS
403 .iter()
404 .map(|name| RocmSymbolEvidence {
405 name: (*name).to_owned(),
406 present: true,
407 })
408 .collect(),
409 }
410}
411
412fn candidate_paths(search_dirs: &[PathBuf], names: &[&str], search_system: bool) -> Vec<PathBuf> {
413 let mut candidates = Vec::new();
414 for directory in search_dirs {
415 for name in names {
416 candidates.push(directory.join(name));
417 }
418 }
419 if search_system {
420 candidates.extend(names.iter().map(PathBuf::from));
421 }
422 candidates
423}
424
425fn symbol_evidence(library: &Library, names: &[&str]) -> Vec<RocmSymbolEvidence> {
426 names
427 .iter()
428 .map(|name| RocmSymbolEvidence {
429 name: (*name).to_owned(),
430 present: symbol_present(library, name),
431 })
432 .collect()
433}
434
435fn open_library(path: &Path) -> Result<Library, libloading::Error> {
436 unsafe { Library::new(path) }
438}
439
440fn symbol_present(library: &Library, name: &str) -> bool {
441 let mut bytes = name.as_bytes().to_vec();
442 bytes.push(0);
443 unsafe { library.get::<*mut c_void>(&bytes).is_ok() }
446}
447
448fn hip_runtime_version(library: &Library) -> Result<i32, RocmLoadError> {
449 type HipInit = unsafe extern "C" fn(u32) -> i32;
450 type HipRuntimeGetVersion = unsafe extern "C" fn(*mut i32) -> i32;
451 type HipGetDeviceCount = unsafe extern "C" fn(*mut i32) -> i32;
452 unsafe {
455 let hip_init = library
456 .get::<HipInit>(b"hipInit\0")
457 .map_err(|error| RocmLoadError {
458 message: error.to_string(),
459 })?;
460 let get_version = library
461 .get::<HipRuntimeGetVersion>(b"hipRuntimeGetVersion\0")
462 .map_err(|error| RocmLoadError {
463 message: error.to_string(),
464 })?;
465 let get_device_count = library
466 .get::<HipGetDeviceCount>(b"hipGetDeviceCount\0")
467 .map_err(|error| RocmLoadError {
468 message: error.to_string(),
469 })?;
470 let init_status = hip_init(0);
471 if init_status != 0 {
472 return Err(RocmLoadError {
473 message: format!("hipInit failed with status {init_status}"),
474 });
475 }
476 let mut device_count = 0;
477 let count_status = get_device_count(&mut device_count);
478 if count_status != 0 || device_count <= 0 {
479 return Err(RocmLoadError {
480 message: format!("hipGetDeviceCount failed with status {count_status}"),
481 });
482 }
483 let mut version = 0;
484 let version_status = get_version(&mut version);
485 if version_status != 0 {
486 return Err(RocmLoadError {
487 message: format!("hipRuntimeGetVersion failed with status {version_status}"),
488 });
489 }
490 Ok(version)
491 }
492}