Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions src/doc/rustc-dev-guide/src/tests/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,15 @@ The following directives will check rustc build settings and target settings:

The following directives will check LLVM support:

- `exact-llvm-major-version: 19` — ignores if the llvm major version does not
match the specified llvm major version.
- `min-llvm-version: 13.0` — ignored if the LLVM version is less than the given value
- `min-system-llvm-version: 12.0` — ignored if using a system LLVM and its
version is less than the given value
- `max-llvm-major-version: 19` — ignored if the LLVM major version is higher
than the given major version
- `ignore-llvm-version: 9.0` — ignores a specific LLVM version
- `ignore-llvm-version: 7.0 - 9.9.9` — ignores LLVM versions in a range (inclusive)
- `llvm-version: cmp` — ignore if the LLVM version doesn't match the comparator; examples of comparators that can be used:
- `=22` — ignored if the LLVM major version does not match the given value
- `>=22` — ignored if the LLVM major version is less than the given value
- `<=22` — ignored if the LLVM major version is higher than the given major version
- `>=22, <=23` — ignored if the LLVM major version is outside the given range
- all of the above can specify minor and patch version too, `=22.1`, `<=22.1`, `>=22.1`
- for a full list see [`semver` crate documentation](https://docs.rs/semver/latest/semver/struct.Comparator.html)
- `system-llvm-version: cmp` — ignored if using a system LLVM and its version does not match the comparator; see above for examples of comparators
- `ignore-llvm-version: cmp` — ignored if LLVM version **matches** the comparator; see above for examples of comparators
Comment on lines +220 to +228

@jieyouxu jieyouxu Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion (not necessarily for this PR, but): hm, right now, //@ edition uses the range literal format (e.g. 2015..2021. I think > et al. are entirely reasonable for //@ llvm-version or //@ system-llvm-version, just that edition would be a different syntax.

I almost wonder if we should just, change //@ edition to use >/</<=/>=? For edition, at the time it was added, there was just a need for a syntax, not necessarily married to ../..=.

View changes since the review

- `needs-llvm-components: powerpc` — ignores if the specific LLVM component was not built.
Note: The test will fail on CI (when
`COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS` is set) if the component does not exist.
Expand Down
113 changes: 27 additions & 86 deletions src/tools/compiletest/src/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,36 +805,14 @@ fn parse_normalize_rule(raw_value: &str) -> Option<(String, String)> {
Some((regex, replacement))
}

/// Given an llvm version string that looks like `1.2.3-rc1`, extract as semver. Note that this
/// accepts more than just strict `semver` syntax (as in `major.minor.patch`); this permits omitting
/// minor and patch version components so users can write e.g. `//@ min-llvm-version: 19` instead of
/// having to write `//@ min-llvm-version: 19.0.0`.
/// Given an llvm version string that looks like `1.2.3-rc1`, extract as semver.
///
/// Currently panics if the input string is malformed, though we really should not use panic as an
/// error handling strategy.
///
/// FIXME(jieyouxu): improve error handling
pub(crate) fn extract_llvm_version(version: &str) -> Version {
// The version substring we're interested in usually looks like the `1.2.3`, without any of the
// fancy suffix like `-rc1` or `meow`.
let version = version.trim();
let uninterested = |c: char| !c.is_ascii_digit() && c != '.';
let version_without_suffix = match version.split_once(uninterested) {
Some((prefix, _suffix)) => prefix,
None => version,
};

let components: Vec<u64> = version_without_suffix
.split('.')
.map(|s| s.parse().expect("llvm version component should consist of only digits"))
.collect();

match &components[..] {
[major] => Version::new(*major, 0, 0),
[major, minor] => Version::new(*major, *minor, 0),
[major, minor, patch] => Version::new(*major, *minor, *patch),
_ => panic!("malformed llvm version string, expected only 1-3 components: {version}"),
}
version.parse().expect("malformed LLVM version")
}

pub(crate) fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Version> {
Expand All @@ -844,7 +822,7 @@ pub(crate) fn extract_llvm_version_from_binary(binary_path: &str) -> Option<Vers
}
let version = String::from_utf8(output.stdout).ok()?;
for line in version.lines() {
if let Some(version) = line.split("LLVM version ").nth(1) {
if let Some((_, version)) = line.split_once("LLVM version ") {
return Some(extract_llvm_version(version));
}
}
Expand Down Expand Up @@ -1233,87 +1211,50 @@ fn ignore_llvm(config: &Config, line: &DirectiveLine<'_>) -> IgnoreDecision {
};
}
}
if let Some(actual_version) = &config.llvm_version {
// Note that these `min` versions will check for not just major versions.

if let Some(version_string) = config.parse_name_value_directive(line, "min-llvm-version") {
let min_version = extract_llvm_version(&version_string);
// Ignore if actual version is smaller than the minimum required version.
if *actual_version < min_version {
return IgnoreDecision::Ignore {
reason: format!(
"ignored when the LLVM version {actual_version} is older than {min_version}"
),
};
}
} else if let Some(version_string) =
config.parse_name_value_directive(line, "max-llvm-major-version")
{
let max_version = extract_llvm_version(&version_string);
// Ignore if actual major version is larger than the maximum required major version.
if actual_version.major > max_version.major {
if let Some(llvm_version) = &config.llvm_version {
if let Some(version_req) = config.parse_name_value_directive(line, "llvm-version") {
let version_req = semver::VersionReq::parse(&version_req)
.expect("malformed llvm version requirement");

if !version_req.matches(llvm_version) {
return IgnoreDecision::Ignore {
reason: format!(
"ignored when the LLVM version ({actual_version}) is newer than major\
version {}",
max_version.major
"ignored when the LLVM version {llvm_version} does not match requirement {version_req}"
),
};
}
} else if let Some(version_string) =
config.parse_name_value_directive(line, "min-system-llvm-version")
{
let min_version = extract_llvm_version(&version_string);
// Ignore if using system LLVM and actual version
// is smaller the minimum required version
if config.system_llvm && *actual_version < min_version {
}

if let Some(version_req) = config.parse_name_value_directive(line, "system-llvm-version") {
let version_req = semver::VersionReq::parse(&version_req)
.expect("malformed llvm version requirement");

// Ignore if using system LLVM and its version doesn't match the requirement
if config.system_llvm && !version_req.matches(llvm_version) {
return IgnoreDecision::Ignore {
reason: format!(
"ignored when the system LLVM version {actual_version} is older than {min_version}"
"ignored when the system LLVM version {llvm_version} does not match requirement {version_req}"
),
};
}
} else if let Some(version_range) =
} else if let Some(version_req) =
config.parse_name_value_directive(line, "ignore-llvm-version")
{
// Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
let (v_min, v_max) =
extract_version_range(&version_range, |s| Some(extract_llvm_version(s)))
.unwrap_or_else(|| {
panic!("couldn't parse version range: \"{version_range}\"");
});
if v_max < v_min {
panic!("malformed LLVM version range where {v_max} < {v_min}")
}
// Ignore if version lies inside of range.
if *actual_version >= v_min && *actual_version <= v_max {
if v_min == v_max {
return IgnoreDecision::Ignore {
reason: format!("ignored when the LLVM version is {actual_version}"),
};
} else {
return IgnoreDecision::Ignore {
reason: format!(
"ignored when the LLVM version is between {v_min} and {v_max}"
),
};
}
}
} else if let Some(version_string) =
config.parse_name_value_directive(line, "exact-llvm-major-version")
{
// Syntax is "exact-llvm-major-version: <version>"
let version = extract_llvm_version(&version_string);
if actual_version.major != version.major {
let version_req = semver::VersionReq::parse(&version_req)
.expect("malformed llvm version requirement");

// Ignore if version matches the requirement
if version_req.matches(llvm_version) {
return IgnoreDecision::Ignore {
reason: format!(
"ignored when the actual LLVM major version is {}, but the test only targets major version {}",
actual_version.major, version.major
"ignored when the system LLVM version {llvm_version} matches requirement {version_req}"
),
};
}
}
}

IgnoreDecision::Continue
}

Expand Down
6 changes: 2 additions & 4 deletions src/tools/compiletest/src/directives/directive_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"dont-require-annotations",
"edition",
"error-pattern",
"exact-llvm-major-version",
"exec-env",
"failure-status",
"filecheck-flags",
Expand Down Expand Up @@ -156,13 +155,11 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"lldb-command",
"lldb-repr",
"llvm-cov-flags",
"max-llvm-major-version",
"llvm-version",
"min-apple-lldb-version",
"min-cdb-version",
"min-gdb-version",
"min-llvm-lldb-version",
"min-llvm-version",
"min-system-llvm-version",
"minicore-compile-flags",
"needs-asm-ret",
"needs-asm-support",
Expand Down Expand Up @@ -301,6 +298,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"should-fail",
"skip-filecheck",
"stderr-per-bitwidth",
"system-llvm-version",
"test-mir-pass",
"unique-doc-out-dir",
"unset-exec-env",
Expand Down
96 changes: 42 additions & 54 deletions src/tools/compiletest/src/directives/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use semver::Version;
use crate::common::{Config, Debugger, TestMode};
use crate::directives::{
self, AuxProps, DIRECTIVE_HANDLERS_MAP, DirectivesCache, EarlyProps, Edition, EditionRange,
FileDirectives, KNOWN_DIRECTIVE_NAMES_SET, LineNumber, extract_llvm_version,
extract_version_range, line_directive, parse_edition, parse_normalize_rule,
FileDirectives, KNOWN_DIRECTIVE_NAMES_SET, LineNumber, extract_llvm_version, line_directive,
parse_edition, parse_normalize_rule,
};
use crate::executor::{CollectedTestDesc, ShouldFail, TestVariant};

Expand Down Expand Up @@ -319,52 +319,52 @@ fn revisions() {
#[test]
fn llvm_version() {
let config: Config = cfg().llvm_version("8.1.2").build();
assert!(check_ignore(&config, "//@ min-llvm-version: 9.0"));
assert!(check_ignore(&config, "//@ llvm-version: >=9.0"));

let config: Config = cfg().llvm_version("9.0.1").build();
assert!(check_ignore(&config, "//@ min-llvm-version: 9.2"));
assert!(check_ignore(&config, "//@ llvm-version: >=9.2"));

let config: Config = cfg().llvm_version("9.3.1").build();
assert!(!check_ignore(&config, "//@ min-llvm-version: 9.2"));
assert!(!check_ignore(&config, "//@ llvm-version: >=9.2"));

let config: Config = cfg().llvm_version("10.0.0").build();
assert!(!check_ignore(&config, "//@ min-llvm-version: 9.0"));
assert!(!check_ignore(&config, "//@ llvm-version: >=9.0"));

let config: Config = cfg().llvm_version("10.0.0").build();
assert!(check_ignore(&config, "//@ exact-llvm-major-version: 9.0"));
assert!(check_ignore(&config, "//@ llvm-version: =9"));

let config: Config = cfg().llvm_version("9.0.0").build();
assert!(check_ignore(&config, "//@ exact-llvm-major-version: 10.0"));
assert!(check_ignore(&config, "//@ llvm-version: =10.0"));

let config: Config = cfg().llvm_version("10.0.0").build();
assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10.0"));
assert!(!check_ignore(&config, "//@ llvm-version: =10.0"));

let config: Config = cfg().llvm_version("10.0.0").build();
assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10"));
assert!(!check_ignore(&config, "//@ llvm-version: =10"));

let config: Config = cfg().llvm_version("10.6.2").build();
assert!(!check_ignore(&config, "//@ exact-llvm-major-version: 10"));
Comment on lines 345 to 346

@jieyouxu jieyouxu Aug 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: wait, does this test case actually still pass...?

View changes since the review


let config: Config = cfg().llvm_version("19.0.0").build();
assert!(!check_ignore(&config, "//@ max-llvm-major-version: 19"));
assert!(!check_ignore(&config, "//@ llvm-version: <=19"));

let config: Config = cfg().llvm_version("19.1.2").build();
assert!(!check_ignore(&config, "//@ max-llvm-major-version: 19"));
assert!(!check_ignore(&config, "//@ llvm-version: <=19"));

let config: Config = cfg().llvm_version("20.0.0").build();
assert!(check_ignore(&config, "//@ max-llvm-major-version: 19"));
assert!(check_ignore(&config, "//@ llvm-version: <=19"));
}

#[test]
fn system_llvm_version() {
let config: Config = cfg().system_llvm(true).llvm_version("17.0.0").build();
assert!(check_ignore(&config, "//@ min-system-llvm-version: 18.0"));
assert!(check_ignore(&config, "//@ system-llvm-version: >=18"));

let config: Config = cfg().system_llvm(true).llvm_version("18.0.0").build();
assert!(!check_ignore(&config, "//@ min-system-llvm-version: 18.0"));
assert!(!check_ignore(&config, "//@ system-llvm-version: >=18"));

let config: Config = cfg().llvm_version("17.0.0").build();
assert!(!check_ignore(&config, "//@ min-system-llvm-version: 18.0"));
assert!(!check_ignore(&config, "//@ system-llvm-version: >=18"));
}

#[test]
Expand Down Expand Up @@ -533,26 +533,35 @@ fn channel() {

#[test]
fn test_extract_llvm_version() {
// Note: officially, semver *requires* that versions at the minimum have all three
// `major.minor.patch` numbers, though for test-writer's convenience we allow omitting the minor
// and patch numbers (which will be stubbed out as 0).
assert_eq!(extract_llvm_version("0"), Version::new(0, 0, 0));
assert_eq!(extract_llvm_version("0.0"), Version::new(0, 0, 0));
fn version_with_pre(major: u64, minor: u64, patch: u64, pre: &str) -> Version {
Version {
major,
minor,
patch,
pre: semver::Prerelease::new(pre).unwrap(),
build: semver::BuildMetadata::EMPTY,
}
}

assert_eq!(extract_llvm_version("0.0.0"), Version::new(0, 0, 0));
assert_eq!(extract_llvm_version("1"), Version::new(1, 0, 0));
assert_eq!(extract_llvm_version("1.2"), Version::new(1, 2, 0));
assert_eq!(extract_llvm_version("1.2.3"), Version::new(1, 2, 3));
assert_eq!(extract_llvm_version("4.5.6git"), Version::new(4, 5, 6));
assert_eq!(extract_llvm_version("4.5.6-rc1"), Version::new(4, 5, 6));
assert_eq!(extract_llvm_version("123.456.789-rc1"), Version::new(123, 456, 789));
assert_eq!(extract_llvm_version("8.1.2-rust"), Version::new(8, 1, 2));
assert_eq!(extract_llvm_version("9.0.1-rust-1.43.0-dev"), Version::new(9, 0, 1));
assert_eq!(extract_llvm_version("9.3.1-rust-1.43.0-dev"), Version::new(9, 3, 1));
assert_eq!(extract_llvm_version("10.0.0-rust"), Version::new(10, 0, 0));
// assert_eq!(extract_llvm_version("4.5.6git"), Version::new(4, 5, 6));

@WaffleLapkin WaffleLapkin Aug 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an actual version string that llvm outputs? In that case I'll have to (partially?) revert the change to extract_llvm_version...

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, this was probably just trying to hedge against trailing random things

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I take that back, I believe for certain LLVM builds, the version string can actually be NNN.N.Ngit, e.g. llvm/llvm-project#71742, in particular if the LLVM build isn't from rc/release tags?

assert_eq!(extract_llvm_version("4.5.6-rc1"), version_with_pre(4, 5, 6, "rc1"));
assert_eq!(extract_llvm_version("123.456.789-rc1"), version_with_pre(123, 456, 789, "rc1"));
assert_eq!(extract_llvm_version("8.1.2-rust"), version_with_pre(8, 1, 2, "rust"));
assert_eq!(
extract_llvm_version("9.0.1-rust-1.43.0-dev"),
version_with_pre(9, 0, 1, "rust-1.43.0-dev")
);
assert_eq!(
extract_llvm_version("9.3.1-rust-1.43.0-dev"),
version_with_pre(9, 3, 1, "rust-1.43.0-dev")
);
assert_eq!(extract_llvm_version("10.0.0-rust"), version_with_pre(10, 0, 0, "rust"));
assert_eq!(extract_llvm_version("11.1.0"), Version::new(11, 1, 0));
assert_eq!(extract_llvm_version("12.0.0libcxx"), Version::new(12, 0, 0));
assert_eq!(extract_llvm_version("12.0.0-rc3"), Version::new(12, 0, 0));
assert_eq!(extract_llvm_version("13.0.0git"), Version::new(13, 0, 0));
// assert_eq!(extract_llvm_version("12.0.0libcxx"), Version::new(12, 0, 0));
assert_eq!(extract_llvm_version("12.0.0-rc3"), version_with_pre(12, 0, 0, "rc3"));
// assert_eq!(extract_llvm_version("13.0.0git"), Version::new(13, 0, 0));
}

#[test]
Expand All @@ -573,27 +582,6 @@ fn test_llvm_version_too_many_components() {
extract_llvm_version("4.5.6.7");
}

#[test]
fn test_extract_version_range() {
let wrapped_extract = |s: &str| Some(extract_llvm_version(s));

assert_eq!(
extract_version_range("1.2.3 - 4.5.6", wrapped_extract),
Some((Version::new(1, 2, 3), Version::new(4, 5, 6)))
);
assert_eq!(
extract_version_range("0 - 4.5.6", wrapped_extract),
Some((Version::new(0, 0, 0), Version::new(4, 5, 6)))
);
assert_eq!(extract_version_range("1.2.3 -", wrapped_extract), None);
assert_eq!(extract_version_range("1.2.3 - ", wrapped_extract), None);
assert_eq!(extract_version_range("- 4.5.6", wrapped_extract), None);
assert_eq!(extract_version_range("-", wrapped_extract), None);
assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None);
assert_eq!(extract_version_range(" - 4.5.6", wrapped_extract), None);
assert_eq!(extract_version_range("0 -", wrapped_extract), None);
}

#[test]
#[should_panic(expected = "duplicate revision: `rpass1` in line ` rpass1 rpass1`")]
fn test_duplicate_revisions() {
Expand Down
2 changes: 1 addition & 1 deletion tests/assembly-llvm/asm/loongarch-type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

//@[loongarch32r] compile-flags: --target loongarch32-unknown-none
//@[loongarch32r] needs-llvm-components: loongarch
//@[loongarch32r] min-llvm-version: 22
//@[loongarch32r] llvm-version: >=22

//@[loongarch64] compile-flags: --target loongarch64-unknown-none
//@[loongarch64] needs-llvm-components: loongarch
Expand Down
2 changes: 1 addition & 1 deletion tests/assembly-llvm/asm/xtensa-types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//@ add-minicore
//@ assembly-output: emit-asm
//@ compile-flags: --target xtensa-esp32-none-elf -Zmerge-functions=disabled
//@ min-llvm-version: 22
//@ llvm-version: >=22
//@ needs-llvm-components: xtensa

#![feature(no_core, lang_items, rustc_attrs, asm_experimental_arch)]
Expand Down
Loading
Loading