Skip to content

Commit 277b824

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 277b824

2 files changed

Lines changed: 118 additions & 133 deletions

File tree

crates/rustc_codegen_nvvm/build.rs

Lines changed: 118 additions & 133 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() {
@@ -77,90 +100,20 @@ fn sibling_llvm_tool(llvm_config: &Path, tool_prefix: &str) -> Option<PathBuf> {
77100
fn target_to_llvm_prebuilt(target: &str) -> String {
78101
let base = match target {
79102
"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",
103+
"x86_64-unknown-linux-gnu" => "linux-x86_64",
104+
"aarch64-unknown-linux-gnu" => "linux-aarch64",
82105
_ => panic!(
83-
"Unsupported target with no matching prebuilt LLVM: `{target}`, install LLVM and set LLVM_CONFIG"
106+
"Unsupported target with no matching prebuilt LLVM: `{target}`, install LLVM and set LLVM_CONFIG (or LLVM_CONFIG_19 when the `llvm19` feature is enabled)"
84107
),
85108
};
86109
format!("{base}.tar.xz")
87110
}
88111

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-
112+
fn download_prebuilt_llvm(target: &str, base_url: &str) -> PathBuf {
162113
let prebuilt_name = target_to_llvm_prebuilt(target);
163-
url = format!("{url}{prebuilt_name}");
114+
let url = format!("{base_url}{prebuilt_name}");
115+
116+
println!("cargo:warning=Downloading prebuilt LLVM from {url}");
164117

165118
let out = env::var("OUT_DIR").expect("OUT_DIR was not set");
166119
let mut easy = Easy::new();
@@ -194,19 +147,61 @@ fn find_llvm_config_llvm7(target: &str) -> PathBuf {
194147
.join(format!("llvm-config{}", std::env::consts::EXE_SUFFIX))
195148
}
196149

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

201196
if let Some(path) = sibling_llvm_tool(llvm_config, "llvm-as") {
202197
candidates.push(path);
203198
}
204199

205-
candidates.push(PathBuf::from("llvm-as-19"));
200+
candidates.push(PathBuf::from(format!("llvm-as-{}", flavor.major)));
206201
candidates.push(PathBuf::from("llvm-as"));
207202

208203
for candidate in &candidates {
209-
if llvm_version_matches(candidate, required_major) {
204+
if llvm_version_matches(candidate, flavor.major) {
210205
return candidate.clone();
211206
}
212207
}
@@ -218,8 +213,9 @@ fn find_llvm_as_llvm19(llvm_config: &Path) -> PathBuf {
218213
.join("\n");
219214

220215
fail(&format!(
221-
"LLVM 19 support is enabled, but llvm-as 19 was not found.\n\
222-
Tried:\n{tried}"
216+
"LLVM {} support is enabled, but llvm-as {} was not found.\n\
217+
Tried:\n{tried}",
218+
flavor.major, flavor.major
223219
));
224220
}
225221

@@ -238,60 +234,49 @@ pub fn tracked_env_var_os<K: AsRef<OsStr> + Display>(key: K) -> Option<OsString>
238234
env::var_os(key)
239235
}
240236

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

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`.
248241
build_helper::rerun_if_changed(Path::new("libintrinsics.ll"));
249242

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() {
243+
let input = manifest_dir.join("libintrinsics.ll");
244+
let output = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR was not set"))
245+
.join(format!("libintrinsics_v{}.bc", flavor.major));
246+
let llvm_as = find_llvm_as(llvm_config, flavor);
247+
248+
let status = Command::new(&llvm_as)
249+
.arg(&input)
250+
.arg("-o")
251+
.arg(&output)
252+
.stderr(Stdio::inherit())
253+
.stdout(Stdio::inherit())
254+
.status()
255+
.unwrap_or_else(|err| {
270256
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-
);
257+
"failed to execute llvm-as for LLVM {}: {llvm_as:?}\nerror: {err}",
258+
flavor.major
259+
))
260+
});
261+
262+
if !status.success() {
263+
fail(&format!(
264+
"llvm-as did not assemble {} successfully",
265+
input.display()
266+
));
286267
}
268+
269+
println!(
270+
"cargo:rustc-env=NVVM_LIBINTRINSICS_BC_PATH={}",
271+
output.display()
272+
);
287273
}
288274

289-
fn rustc_llvm_build(llvm19_enabled: bool) {
275+
fn rustc_llvm_build(flavor: &LlvmFlavor) {
290276
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);
277+
let llvm_config = find_llvm_config(&target, flavor);
293278

294-
configure_libintrinsics(&llvm_config, llvm19_enabled);
279+
configure_libintrinsics(&llvm_config, flavor);
295280

296281
let required_components = &["ipo", "bitreader", "bitwriter", "lto", "nvptx"];
297282

@@ -340,7 +325,7 @@ fn rustc_llvm_build(llvm19_enabled: bool) {
340325
cfg.define(&flag, None);
341326
}
342327

343-
let llvm_version_major = required_major.to_string();
328+
let llvm_version_major = flavor.major.to_string();
344329
cfg.define("LLVM_VERSION_MAJOR", Some(llvm_version_major.as_str()));
345330

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

0 commit comments

Comments
 (0)