1use std::path::{Path, PathBuf};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum Vendor {
18 Nvidia,
20 Amd,
22 Apple,
24 Intel,
26}
27
28impl Vendor {
29 pub fn parse(s: &str) -> Option<Self> {
30 match s.to_lowercase().as_str() {
31 "nvidia" | "cuda" => Some(Self::Nvidia),
32 "amd" | "rocm" | "hip" => Some(Self::Amd),
33 "apple" | "metal" => Some(Self::Apple),
34 "intel" | "oneapi" | "sycl" => Some(Self::Intel),
35 _ => None,
36 }
37 }
38}
39
40impl std::fmt::Display for Vendor {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 match self {
43 Self::Nvidia => write!(f, "nvidia"),
44 Self::Amd => write!(f, "amd"),
45 Self::Apple => write!(f, "apple"),
46 Self::Intel => write!(f, "intel"),
47 }
48 }
49}
50
51pub trait ComputeTarget {
75 fn source_extension(&self) -> &str;
78
79 fn output_extension(&self) -> &str;
82
83 fn output_is_text(&self) -> bool;
86
87 fn find_compiler(&self) -> Option<PathBuf>;
89
90 fn compile(
101 &self,
102 source: &Path,
103 output: &Path,
104 arch: &str,
105 extra_flags: &[String],
106 ) -> Result<(), String>;
107
108 fn vendor(&self) -> Vendor;
109}
110
111pub struct NvidiaTarget {
114 nvcc_path: PathBuf,
115}
116
117impl NvidiaTarget {
118 pub fn new() -> Option<Self> {
120 let nvcc = find_nvcc()?;
121 Some(Self { nvcc_path: nvcc })
122 }
123
124 pub fn with_compiler(nvcc_path: PathBuf) -> Self {
125 Self { nvcc_path }
126 }
127}
128
129impl ComputeTarget for NvidiaTarget {
130 fn source_extension(&self) -> &str {
131 "cu"
132 }
133
134 fn output_extension(&self) -> &str {
135 "ptx"
136 }
137
138 fn output_is_text(&self) -> bool {
139 true
140 }
141
142 fn find_compiler(&self) -> Option<PathBuf> {
143 Some(self.nvcc_path.clone())
144 }
145
146 fn compile(
147 &self,
148 source: &Path,
149 output: &Path,
150 arch: &str,
151 extra_flags: &[String],
152 ) -> Result<(), String> {
153 let arch_flag = format!("-arch={arch}");
154 let mut args = vec!["--ptx".to_string(), arch_flag, "-O3".to_string()];
155 args.extend(extra_flags.iter().cloned());
156 args.push(source.to_str().unwrap().to_string());
157 args.push("-o".to_string());
158 args.push(output.to_str().unwrap().to_string());
159
160 let result = std::process::Command::new(&self.nvcc_path)
161 .args(&args)
162 .output()
163 .map_err(|e| format!("Failed to run nvcc: {e}"))?;
164
165 if result.status.success() {
166 Ok(())
167 } else {
168 let stderr = String::from_utf8_lossy(&result.stderr);
169 Err(format!(
170 "nvcc --ptx failed for {}: {}",
171 source.display(),
172 stderr
173 ))
174 }
175 }
176
177 fn vendor(&self) -> Vendor {
178 Vendor::Nvidia
179 }
180}
181
182fn find_nvcc() -> Option<PathBuf> {
185 for var in ["CUDA_HOME", "CUDA_PATH", "CUDA_ROOT"] {
186 if let Ok(dir) = std::env::var(var) {
187 let nvcc = PathBuf::from(dir).join("bin/nvcc");
188 if nvcc.exists() {
189 return Some(nvcc);
190 }
191 }
192 }
193 for path in [
194 "/usr/local/cuda/bin/nvcc",
195 "/usr/local/cuda-13.0/bin/nvcc",
196 "/usr/local/cuda-12.0/bin/nvcc",
197 "/opt/cuda/bin/nvcc",
198 ] {
199 let p = PathBuf::from(path);
200 if p.exists() {
201 return Some(p);
202 }
203 }
204 which_in_path("nvcc")
205}
206
207fn which_in_path(name: &str) -> Option<PathBuf> {
208 std::env::var_os("PATH").and_then(|paths| {
209 std::env::split_paths(&paths)
210 .map(|dir| dir.join(name))
211 .find(|p| p.exists())
212 })
213}
214
215pub fn target_for_vendor(vendor: Option<&str>) -> Box<dyn ComputeTarget> {
220 match vendor.and_then(Vendor::parse) {
221 Some(Vendor::Nvidia) | None => {
222 Box::new(NvidiaTarget::new().expect("nvcc not found — install CUDA toolkit"))
223 }
224 Some(v) => {
225 panic!("Compute target '{v}' is not yet implemented. Only 'nvidia' is supported.")
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn vendor_parse_accepts_supported_names() {
236 assert_eq!(Vendor::parse("nvidia"), Some(Vendor::Nvidia));
237 assert_eq!(Vendor::parse("CUDA"), Some(Vendor::Nvidia));
238 assert_eq!(Vendor::parse("amd"), Some(Vendor::Amd));
239 assert_eq!(Vendor::parse("rocm"), Some(Vendor::Amd));
240 assert_eq!(Vendor::parse("hip"), Some(Vendor::Amd));
241 assert_eq!(Vendor::parse("apple"), Some(Vendor::Apple));
242 assert_eq!(Vendor::parse("metal"), Some(Vendor::Apple));
243 assert_eq!(Vendor::parse("intel"), Some(Vendor::Intel));
244 assert_eq!(Vendor::parse("oneapi"), Some(Vendor::Intel));
245 assert_eq!(Vendor::parse("sycl"), Some(Vendor::Intel));
246 assert_eq!(Vendor::parse("unknown"), None);
247 }
248
249 #[test]
250 fn nvidia_target_metadata() {
251 let target = NvidiaTarget::with_compiler(PathBuf::from("nvcc"));
252 assert_eq!(target.source_extension(), "cu");
253 assert_eq!(target.output_extension(), "ptx");
254 assert!(target.output_is_text());
255 assert_eq!(target.vendor(), Vendor::Nvidia);
256 }
257}