Skip to main content

amdgpu_device_libs_build/
lib.rs

1// Update: cargo readme > README.md
2
3#![allow(clippy::needless_doctest_main)]
4//! Build script support for `amdgpu-device-libs`.
5//!
6//! Adds linker flags to link device-libs.
7//! Add `amdgpu-device-libs-build` as a `build-dependency` and call it in the build script.
8//! ```rust,no_run
9//! // build.rs
10//! fn main() {
11//!     amdgpu_device_libs_build::build();
12//! }
13//! ```
14//!
15//! This links to the [ROCm device-libs](https://github.com/ROCm/llvm-project/tree/amd-staging/amd/device-libs) and a pre-compiled helper library.
16//! The libraries are linked from a ROCm installation.
17//! If libraries are found not found by default, set the environment variable
18//! `HIP_DEVICE_LIB_PATH` to the bitcode files. The path should end with `amdgcn/bitcode`
19//! and contain `.bc` files.
20//! See the documentation of [`amdgpu-device-libs`](https://docs.rs/amdgpu-device-libs) for more information.
21
22use std::env;
23use std::path::PathBuf;
24use std::process::Command;
25
26/// Build arguments for use in build.rs or proc-macros.
27#[derive(Default)]
28pub struct Build {
29    pub link_args: Vec<String>,
30    pub used_env_vars: Vec<String>,
31    pub used_files: Vec<String>,
32}
33
34/// Get build arguments from environment variables and flags.
35pub fn get_link_args(mut is_wave64_enabled: bool, target_cpu: &str) -> Build {
36    let mut build = Build::default();
37
38    let cur_dir = env!("CARGO_MANIFEST_DIR");
39
40    let mut device_libs = None;
41    // 1. Try HIP_DEVICE_LIB_PATH
42    if let Ok(v) = env::var("HIP_DEVICE_LIB_PATH") {
43        build.used_env_vars.push("HIP_DEVICE_LIB_PATH".into());
44        device_libs = Some(v);
45    } else {
46        // 2. Use `hipconfig -l` ../lib/clang/*/
47        let mut hipconfig = Command::new("hipconfig");
48        hipconfig.arg("-l");
49
50        let hipconfig_r = hipconfig.output();
51        if let Ok(r) = &hipconfig_r {
52            if !r.status.success() {
53                panic!(
54                    "`hipconfig -l` exited unsuccessfully, either fix this or set $HIP_DEVICE_LIB_PATH"
55                );
56            }
57            let s =
58                String::from_utf8(r.stdout.clone()).expect("`hipconfig -l` returned invalid utf-8");
59            let p = PathBuf::from(s)
60                .canonicalize()
61                .expect("Failed to canonicalize device libs path")
62                .parent()
63                .expect("Device libs path must have parent")
64                .join("lib")
65                .join("clang");
66            // Sort all children and take the last (presumably latest)
67            let mut folders = Vec::new();
68            // Ignore failure to read dir, it may not exist
69            if let Ok(dir) = std::fs::read_dir(&p) {
70                for d in dir {
71                    let d = d.expect("Failed to list lib/clang directory content");
72                    if d.file_type().expect("Failed to get file type").is_dir() {
73                        folders.push(d.path());
74                    }
75                }
76            }
77            folders.sort();
78            if let Some(last) = folders.last() {
79                device_libs = Some(format!("{}/amdgcn/bitcode", last.display()));
80            }
81        }
82
83        // 3. Fallback for backwards compat, try ROCM_DEVICE_LIB_PATH or ROCM_PATH, add /amdgcn/bitcode
84        if device_libs.is_none()
85            && let Ok(v) = env::var("ROCM_DEVICE_LIB_PATH").or_else(|_| env::var("ROCM_PATH"))
86        {
87            build.used_env_vars.push("ROCM_PATH".into());
88            build.used_env_vars.push("ROCM_DEVICE_LIB_PATH".into());
89            device_libs = Some(format!("{}/amdgcn/bitcode", v));
90        }
91    }
92    let device_libs = device_libs.expect("Device libs not found, must set $HIP_DEVICE_LIB_PATH or provide a path through `hipconfig -l`");
93
94    let gfxip = target_cpu
95        .strip_prefix("gfx")
96        .unwrap_or_else(|| panic!("target-cpu '{target_cpu}' did not start with gfx"));
97
98    build.link_args.push(format!("{device_libs}/ockl.bc"));
99    build
100        .link_args
101        .push(format!("{device_libs}/oclc_isa_version_{gfxip}.bc"));
102    build
103        .link_args
104        .push(format!("{device_libs}/oclc_abi_version_600.bc"));
105
106    // wave64 is the default on gfx9 and before
107    is_wave64_enabled |= gfxip.starts_with('9') && gfxip.len() == 3;
108    is_wave64_enabled |= gfxip.starts_with("9-") && gfxip.ends_with("-generic");
109    let wavesize = if is_wave64_enabled { 64 } else { 32 };
110    build.link_args.push(format!(
111        "{device_libs}/oclc_wavefrontsize64_{}.bc",
112        if is_wave64_enabled { "on" } else { "off" }
113    ));
114
115    build
116        .used_files
117        .push(format!("{cur_dir}/util{wavesize}.bc"));
118    build.link_args.push(format!("{cur_dir}/util{wavesize}.bc"));
119
120    // Workarounds to make linker-plugin-lto work
121    build.link_args.push("--undefined-version".into());
122    build.link_args.push("--no-gc-sections".into());
123    build
124}
125
126/// Link libraries for `amdgpu-device-libs`.
127///
128/// Call in a cargo buildscript:
129/// ```rust,no_run
130/// // build.rs
131/// fn main() {
132///     amdgpu_device_libs_build::build();
133/// }
134/// ```
135#[cfg(feature = "rustflags")]
136pub fn build() {
137    use std::collections::HashSet;
138
139    use rustflags::Flag;
140
141    // Find out target cpu and enabled features
142    let mut target_features = env::var("CARGO_CFG_TARGET_FEATURE")
143        .unwrap_or_default()
144        .split(',')
145        .filter(|s| !s.is_empty())
146        .map(|s| s.to_string())
147        .collect::<HashSet<_>>();
148
149    let mut target_cpu = None;
150    for flag in rustflags::from_env() {
151        if let Flag::Codegen { opt, value } = flag {
152            if opt == "target-cpu" {
153                target_cpu = value;
154            } else if opt == "target-feature"
155                && let Some(feat) = value
156            {
157                if let Some(feat) = feat.strip_prefix('-') {
158                    target_features.remove(feat);
159                } else {
160                    let feat = feat.trim_start_matches('+');
161                    target_features.insert(feat.into());
162                }
163            }
164        }
165    }
166    let target_cpu = target_cpu.expect("Did not find target-cpu in RUSTFLAGS");
167    let is_wave64_enabled = target_features.contains("wavefrontsize64");
168    let mut build = get_link_args(is_wave64_enabled, &target_cpu);
169    build.used_env_vars.push("CARGO_CFG_TARGET_FEATURE".into());
170
171    for v in &build.used_env_vars {
172        println!("cargo::rerun-if-env-changed={v}");
173    }
174    for f in &build.used_files {
175        println!("cargo::rerun-if-changed={f}");
176    }
177    for a in &build.link_args {
178        println!("cargo::rustc-link-arg={a}");
179    }
180}