Skip to content

Commit dbdee10

Browse files
brandonrosclaude
andcommitted
refactor(rustc_codegen_nvvm): parameterize build.rs over LlvmFlavor
Collapse the per-LLVM duplication in the build script into a single `LlvmFlavor` struct with two const instances (LLVM7, LLVM19). One `find_llvm_config`, `find_llvm_as`, `configure_libintrinsics`, and `rustc_llvm_build` now drive both toolchain paths; `required_major_llvm_version`, `find_llvm_config_llvm7`, `find_llvm_config_llvm19`, and `find_llvm_as_llvm19` are gone. Functional changes that fall out of the refactor: - Prebuilt LLVM download now works for the `llvm19` feature too, gated on `USE_PREBUILT_LLVM=1` or as an automatic fallback when no LLVM 19 toolchain is found locally. New `PREBUILT_LLVM_URL_LLVM19` points at the `llvm-19.1.7` release tag. - Prebuilt download now supports `linux-x86_64` and `linux-aarch64` in addition to `windows-x86_64`. The "currently disabled because of segfaults" note on Linux x86_64 is gone — the prebuild repos that produce these archives have been refactored to fix the underlying issue. - `PREBUILT_LLVM_URL_LLVM7` retagged to lowercase `llvm-7.1.0/` to match the new release-tag scheme used by the prebuild repos. - `libintrinsics.bc` is no longer checked in; both LLVM versions now assemble `libintrinsics.ll` on the fly using the `llvm-as` that ships next to the resolved `llvm-config`. Removes the only remaining version-specific branch and means the LLVM 7 path can no longer drift silently when the `.ll` changes. The LLVM 7 candidate-search behavior is also slightly stricter: previously `LLVM_CONFIG` only had to literally start with "7" (matching 7, 70, 700...) and a mismatched env var skipped straight to download; now major-version match is exact and PATH `llvm-config` is tried as a fallback before downloading. `USE_PREBUILT_LLVM=1` still forces direct download. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 929562e commit dbdee10

2 files changed

Lines changed: 130 additions & 136 deletions

File tree

crates/rustc_codegen_nvvm/build.rs

Lines changed: 130 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,38 @@ use curl::easy::Easy;
1010
use tar::Archive;
1111
use xz::read::XzDecoder;
1212

13+
struct LlvmFlavor {
14+
major: u8,
15+
config_env: &'static str,
16+
default_binary: &'static str,
17+
probe_cuda_home: bool,
18+
prebuilt_url: &'static str,
19+
}
20+
21+
const LLVM7: LlvmFlavor = LlvmFlavor {
22+
major: 7,
23+
config_env: "LLVM_CONFIG",
24+
default_binary: "llvm-config",
25+
probe_cuda_home: false,
26+
prebuilt_url: PREBUILT_LLVM_URL_LLVM7,
27+
};
28+
29+
const LLVM19: LlvmFlavor = LlvmFlavor {
30+
major: 19,
31+
config_env: "LLVM_CONFIG_19",
32+
default_binary: "llvm-config-19",
33+
probe_cuda_home: true,
34+
prebuilt_url: PREBUILT_LLVM_URL_LLVM19,
35+
};
36+
1337
static PREBUILT_LLVM_URL_LLVM7: &str =
14-
"https://github.com/rust-gpu/rustc_codegen_nvvm-llvm/releases/download/LLVM-7.1.0/";
38+
"https://github.com/rust-gpu/rustc_codegen_nvvm-llvm/releases/download/llvm-7.1.0/";
39+
static PREBUILT_LLVM_URL_LLVM19: &str =
40+
"https://github.com/rust-gpu/rustc_codegen_nvvm-llvm/releases/download/llvm-19.1.7/";
1541

1642
fn main() {
17-
rustc_llvm_build(llvm19_enabled());
43+
let flavor = if llvm19_enabled() { &LLVM19 } else { &LLVM7 };
44+
rustc_llvm_build(flavor);
1845
}
1946

2047
fn fail(s: &str) -> ! {
@@ -43,10 +70,6 @@ fn llvm19_enabled() -> bool {
4370
tracked_env_var_os("CARGO_FEATURE_LLVM19").is_some()
4471
}
4572

46-
fn required_major_llvm_version(llvm19_enabled: bool) -> u8 {
47-
if llvm19_enabled { 19 } else { 7 }
48-
}
49-
5073
fn command_version(path: &Path) -> Option<String> {
5174
let output = Command::new(path).arg("--version").output().ok()?;
5275
if !output.status.success() {
@@ -69,98 +92,37 @@ fn llvm_version_matches(path: &Path, required_major: u8) -> bool {
6992
}
7093

7194
fn sibling_llvm_tool(llvm_config: &Path, tool_prefix: &str) -> Option<PathBuf> {
72-
let file_name = llvm_config.file_name()?.to_str()?;
73-
let suffix = file_name.strip_prefix("llvm-config")?;
74-
Some(llvm_config.with_file_name(format!("{tool_prefix}{suffix}")))
95+
// Ask llvm-config where its install tree lives rather than deriving lexically.
96+
// Lexical derivation breaks when llvm-config is exposed via a single symlink
97+
// into /usr/bin/ but the rest of the toolchain stays in the install prefix
98+
// (e.g. /usr/bin/llvm-config -> /opt/llvm-7/bin/llvm-config, with /opt/llvm-7/bin
99+
// off PATH). It also handles source-built toolchains where tool names are
100+
// unsuffixed (`llvm-as`) versus apt-packaged ones (`llvm-as-19`).
101+
let output = Command::new(llvm_config).arg("--bindir").output().ok()?;
102+
if !output.status.success() {
103+
return None;
104+
}
105+
let bindir = String::from_utf8(output.stdout).ok()?.trim().to_string();
106+
Some(PathBuf::from(bindir).join(tool_prefix))
75107
}
76108

77109
fn target_to_llvm_prebuilt(target: &str) -> String {
78110
let base = match target {
79111
"x86_64-pc-windows-msvc" => "windows-x86_64",
80-
// NOTE(RDambrosio016): currently disabled because of weird issues with segfaults and building the C++ shim
81-
// "x86_64-unknown-linux-gnu" => "linux-x86_64",
112+
"x86_64-unknown-linux-gnu" => "linux-x86_64",
113+
"aarch64-unknown-linux-gnu" => "linux-aarch64",
82114
_ => panic!(
83-
"Unsupported target with no matching prebuilt LLVM: `{target}`, install LLVM and set LLVM_CONFIG"
115+
"Unsupported target with no matching prebuilt LLVM: `{target}`, install LLVM and set LLVM_CONFIG (or LLVM_CONFIG_19 when the `llvm19` feature is enabled)"
84116
),
85117
};
86118
format!("{base}.tar.xz")
87119
}
88120

89-
fn find_llvm_config(target: &str, llvm19_enabled: bool) -> PathBuf {
90-
if llvm19_enabled {
91-
return find_llvm_config_llvm19();
92-
}
93-
94-
find_llvm_config_llvm7(target)
95-
}
96-
97-
fn find_llvm_config_llvm19() -> PathBuf {
98-
let required_major = required_major_llvm_version(true);
99-
let mut candidates = Vec::new();
100-
101-
if let Some(path) = tracked_env_var_os("LLVM_CONFIG_19") {
102-
candidates.push(PathBuf::from(path));
103-
}
104-
105-
candidates.push(PathBuf::from("llvm-config-19"));
106-
107-
if let Some(cuda_home) = tracked_env_var_os("CUDA_HOME") {
108-
let cuda_home = PathBuf::from(cuda_home);
109-
candidates.push(cuda_home.join("nvvm").join("bin").join("llvm-config"));
110-
candidates.push(cuda_home.join("bin").join("llvm-config"));
111-
}
112-
113-
for candidate in &candidates {
114-
if llvm_version_matches(candidate, required_major) {
115-
return candidate.clone();
116-
}
117-
}
118-
119-
let tried = candidates
120-
.iter()
121-
.map(|candidate| format!(" - {}", candidate.display()))
122-
.collect::<Vec<_>>()
123-
.join("\n");
124-
125-
fail(&format!(
126-
"LLVM 19 support is enabled, but no LLVM 19 toolchain was found.\n\
127-
Tried:\n{tried}\n\n\
128-
Set LLVM_CONFIG_19=/path/to/llvm-config from an LLVM 19 installation."
129-
));
130-
}
131-
132-
fn find_llvm_config_llvm7(target: &str) -> PathBuf {
133-
let required_major = required_major_llvm_version(false);
134-
// first, if LLVM_CONFIG is set then see if its llvm version if 7.x, if so, use that.
135-
let config_env = tracked_env_var_os("LLVM_CONFIG");
136-
// if LLVM_CONFIG is not set, try using llvm-config as a normal app in PATH.
137-
let path_to_try = config_env.unwrap_or_else(|| "llvm-config".into());
138-
139-
// if USE_PREBUILT_LLVM is set to 1 then download prebuilt llvm without trying llvm-config
140-
if tracked_env_var_os("USE_PREBUILT_LLVM") != Some("1".into()) {
141-
let cmd = Command::new(&path_to_try).arg("--version").output();
142-
143-
if let Ok(out) = cmd {
144-
let version = String::from_utf8(out.stdout).unwrap();
145-
if version.starts_with(&required_major.to_string()) {
146-
return PathBuf::from(path_to_try);
147-
}
148-
println!(
149-
"cargo:warning=Prebuilt llvm-config version does not start with {required_major}"
150-
);
151-
} else {
152-
println!("cargo:warning=Failed to run prebuilt llvm-config");
153-
}
154-
}
155-
156-
// otherwise, download prebuilt LLVM.
157-
println!("cargo:warning=Downloading prebuilt LLVM");
158-
let mut url = tracked_env_var_os("PREBUILT_LLVM_URL")
159-
.map(|x| x.to_string_lossy().to_string())
160-
.unwrap_or_else(|| PREBUILT_LLVM_URL_LLVM7.to_string());
161-
121+
fn download_prebuilt_llvm(target: &str, base_url: &str) -> PathBuf {
162122
let prebuilt_name = target_to_llvm_prebuilt(target);
163-
url = format!("{url}{prebuilt_name}");
123+
let url = format!("{base_url}{prebuilt_name}");
124+
125+
println!("cargo:warning=Downloading prebuilt LLVM from {url}");
164126

165127
let out = env::var("OUT_DIR").expect("OUT_DIR was not set");
166128
let mut easy = Easy::new();
@@ -194,19 +156,61 @@ fn find_llvm_config_llvm7(target: &str) -> PathBuf {
194156
.join(format!("llvm-config{}", std::env::consts::EXE_SUFFIX))
195157
}
196158

197-
fn find_llvm_as_llvm19(llvm_config: &Path) -> PathBuf {
198-
let required_major = required_major_llvm_version(true);
159+
fn find_llvm_config(target: &str, flavor: &LlvmFlavor) -> PathBuf {
160+
// USE_PREBUILT_LLVM=1 skips local probing and goes straight to download.
161+
if tracked_env_var_os("USE_PREBUILT_LLVM") != Some("1".into()) {
162+
let mut candidates = Vec::new();
163+
164+
if let Some(path) = tracked_env_var_os(flavor.config_env) {
165+
candidates.push(PathBuf::from(path));
166+
}
167+
168+
candidates.push(PathBuf::from(flavor.default_binary));
169+
170+
if flavor.probe_cuda_home
171+
&& let Some(cuda_home) = tracked_env_var_os("CUDA_HOME")
172+
{
173+
let cuda_home = PathBuf::from(cuda_home);
174+
candidates.push(cuda_home.join("nvvm").join("bin").join("llvm-config"));
175+
candidates.push(cuda_home.join("bin").join("llvm-config"));
176+
}
177+
178+
for candidate in &candidates {
179+
if llvm_version_matches(candidate, flavor.major) {
180+
return candidate.clone();
181+
}
182+
}
183+
184+
let tried = candidates
185+
.iter()
186+
.map(|candidate| format!(" - {}", candidate.display()))
187+
.collect::<Vec<_>>()
188+
.join("\n");
189+
190+
println!(
191+
"cargo:warning=No matching LLVM {} toolchain found, falling back to prebuilt LLVM. Tried:\n{}",
192+
flavor.major, tried
193+
);
194+
}
195+
196+
let url = tracked_env_var_os("PREBUILT_LLVM_URL")
197+
.map(|x| x.to_string_lossy().to_string())
198+
.unwrap_or_else(|| flavor.prebuilt_url.to_string());
199+
download_prebuilt_llvm(target, &url)
200+
}
201+
202+
fn find_llvm_as(llvm_config: &Path, flavor: &LlvmFlavor) -> PathBuf {
199203
let mut candidates = Vec::new();
200204

201205
if let Some(path) = sibling_llvm_tool(llvm_config, "llvm-as") {
202206
candidates.push(path);
203207
}
204208

205-
candidates.push(PathBuf::from("llvm-as-19"));
209+
candidates.push(PathBuf::from(format!("llvm-as-{}", flavor.major)));
206210
candidates.push(PathBuf::from("llvm-as"));
207211

208212
for candidate in &candidates {
209-
if llvm_version_matches(candidate, required_major) {
213+
if llvm_version_matches(candidate, flavor.major) {
210214
return candidate.clone();
211215
}
212216
}
@@ -218,8 +222,9 @@ fn find_llvm_as_llvm19(llvm_config: &Path) -> PathBuf {
218222
.join("\n");
219223

220224
fail(&format!(
221-
"LLVM 19 support is enabled, but llvm-as 19 was not found.\n\
222-
Tried:\n{tried}"
225+
"LLVM {} support is enabled, but llvm-as {} was not found.\n\
226+
Tried:\n{tried}",
227+
flavor.major, flavor.major
223228
));
224229
}
225230

@@ -238,60 +243,49 @@ pub fn tracked_env_var_os<K: AsRef<OsStr> + Display>(key: K) -> Option<OsString>
238243
env::var_os(key)
239244
}
240245

241-
fn configure_libintrinsics(llvm_config: &Path, llvm19_enabled: bool) {
246+
fn configure_libintrinsics(llvm_config: &Path, flavor: &LlvmFlavor) {
242247
let manifest_dir =
243248
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR was not set"));
244249

245-
// Both paths share `libintrinsics.ll`. The LLVM 7 build consumes the checked-in
246-
// `libintrinsics.bc` (regenerate manually with `llvm-as-7` when the .ll changes).
247-
// The LLVM 19 build assembles the same .ll on the fly with `llvm-as-19`.
248250
build_helper::rerun_if_changed(Path::new("libintrinsics.ll"));
249251

250-
if llvm19_enabled {
251-
let input = manifest_dir.join("libintrinsics.ll");
252-
let output = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR was not set"))
253-
.join("libintrinsics_v19.bc");
254-
let llvm_as = find_llvm_as_llvm19(llvm_config);
255-
256-
let status = Command::new(&llvm_as)
257-
.arg(&input)
258-
.arg("-o")
259-
.arg(&output)
260-
.stderr(Stdio::inherit())
261-
.stdout(Stdio::inherit())
262-
.status()
263-
.unwrap_or_else(|err| {
264-
fail(&format!(
265-
"failed to execute llvm-as for LLVM 19: {llvm_as:?}\nerror: {err}"
266-
))
267-
});
268-
269-
if !status.success() {
252+
let input = manifest_dir.join("libintrinsics.ll");
253+
let output = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR was not set"))
254+
.join(format!("libintrinsics_v{}.bc", flavor.major));
255+
let llvm_as = find_llvm_as(llvm_config, flavor);
256+
257+
let status = Command::new(&llvm_as)
258+
.arg(&input)
259+
.arg("-o")
260+
.arg(&output)
261+
.stderr(Stdio::inherit())
262+
.stdout(Stdio::inherit())
263+
.status()
264+
.unwrap_or_else(|err| {
270265
fail(&format!(
271-
"llvm-as did not assemble {} successfully",
272-
input.display()
273-
));
274-
}
275-
276-
println!(
277-
"cargo:rustc-env=NVVM_LIBINTRINSICS_BC_PATH={}",
278-
output.display()
279-
);
280-
} else {
281-
build_helper::rerun_if_changed(Path::new("libintrinsics.bc"));
282-
println!(
283-
"cargo:rustc-env=NVVM_LIBINTRINSICS_BC_PATH={}",
284-
manifest_dir.join("libintrinsics.bc").display()
285-
);
266+
"failed to execute llvm-as for LLVM {}: {llvm_as:?}\nerror: {err}",
267+
flavor.major
268+
))
269+
});
270+
271+
if !status.success() {
272+
fail(&format!(
273+
"llvm-as did not assemble {} successfully",
274+
input.display()
275+
));
286276
}
277+
278+
println!(
279+
"cargo:rustc-env=NVVM_LIBINTRINSICS_BC_PATH={}",
280+
output.display()
281+
);
287282
}
288283

289-
fn rustc_llvm_build(llvm19_enabled: bool) {
284+
fn rustc_llvm_build(flavor: &LlvmFlavor) {
290285
let target = env::var("TARGET").expect("TARGET was not set");
291-
let llvm_config = find_llvm_config(&target, llvm19_enabled);
292-
let required_major = required_major_llvm_version(llvm19_enabled);
286+
let llvm_config = find_llvm_config(&target, flavor);
293287

294-
configure_libintrinsics(&llvm_config, llvm19_enabled);
288+
configure_libintrinsics(&llvm_config, flavor);
295289

296290
let required_components = &["ipo", "bitreader", "bitwriter", "lto", "nvptx"];
297291

@@ -340,7 +334,7 @@ fn rustc_llvm_build(llvm19_enabled: bool) {
340334
cfg.define(&flag, None);
341335
}
342336

343-
let llvm_version_major = required_major.to_string();
337+
let llvm_version_major = flavor.major.to_string();
344338
cfg.define("LLVM_VERSION_MAJOR", Some(llvm_version_major.as_str()));
345339

346340
if tracked_env_var_os("LLVM_RUSTLLVM").is_some() {
-4.05 KB
Binary file not shown.

0 commit comments

Comments
 (0)