diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index e6fbba943a48c..8efd9918fb492 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -3,10 +3,11 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; -use std::sync::OnceLock; +use std::sync::{LazyLock, OnceLock}; use std::{fmt, iter}; use build_helper::git::GitConfig; +use regex::Regex; use semver::Version; use serde::de::{Deserialize, Deserializer, Error as _}; use test::{ColorConfig, OutputFormat}; @@ -477,6 +478,19 @@ impl Config { ASM_SUPPORTED_ARCHS.contains(&self.target_cfg().arch.as_str()) } + pub fn has_atomic(&self, size: &str) -> bool { + static TARGET_HAS_ATOMIC: LazyLock = + LazyLock::new(|| Regex::new(r#"target_has_atomic="(?[0-9a-zA-Z]+)""#).unwrap()); + + TARGET_HAS_ATOMIC + .captures_iter(&rustc_output( + self, + &["--print=cfg", "--target", &self.target], + Default::default(), + )) + .any(|caps| &caps["size"] == size) + } + pub fn git_config(&self) -> GitConfig<'_> { GitConfig { git_repository: &self.git_repository, diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs index 0c47ef871d217..6f68fd4424504 100644 --- a/src/tools/compiletest/src/directive-list.rs +++ b/src/tools/compiletest/src/directive-list.rs @@ -154,6 +154,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "needs-sanitizer-thread", "needs-std-debug-assertions", "needs-symlink", + "needs-target-has-atomic", "needs-threads", "needs-unwind", "needs-wasmtime", diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index 77570c58db5cf..f7cc6e72019b5 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -171,9 +171,12 @@ pub(super) fn handle_needs( }, ]; - let (name, comment) = match ln.split_once([':', ' ']) { - Some((name, comment)) => (name, Some(comment)), - None => (ln, None), + // Because `needs-target-has-atomic` accepts comma separated arguments following a colon to + // specify data sizes, we check whether comment starts with colon. + let (name, comment, comment_starts_with_colon) = if let Some(index) = ln.find([':', ' ']) { + (&ln[..index], Some(&ln[index + 1..]), ln.as_bytes()[index] == b':') + } else { + (ln, None, false) }; if !name.starts_with("needs-") { @@ -185,6 +188,11 @@ pub(super) fn handle_needs( return IgnoreDecision::Continue; } + // Check here because `needs-target-has-atomic` requires parsing comments. + if name == "needs-target-has-atomic" { + return handle_needs_target_has_atomic(comment_starts_with_colon, comment, config); + } + let mut found_valid = false; for need in needs { if need.name == name { @@ -210,6 +218,42 @@ pub(super) fn handle_needs( } } +fn handle_needs_target_has_atomic( + comment_starts_with_colon: bool, + comment: Option<&str>, + config: &Config, +) -> IgnoreDecision { + // `needs-target-has-atomic` requires comma-separated data sizes following a collon. + if !comment_starts_with_colon || comment.is_none() { + return IgnoreDecision::Error { + message: "`needs-target-has-atomic` requires data sizes for atomic operations".into(), + }; + } + let comment = comment.unwrap(); + + // Parse the comment to specify data sizes. + for size in comment.split(',').map(|size| size.trim()) { + if !["ptr", "128", "64", "32", "16", "8"].contains(&size) { + return IgnoreDecision::Error { + message: "expected values for `needs-target-has-atomic` are: `128`, `16`, \\ + `32`, `64`, `8`, and `ptr`" + .into(), + }; + } + if !config.has_atomic(size) { + return IgnoreDecision::Ignore { + reason: if size == "ptr" { + "ignored on targets without ptr-size atomic operations".into() + } else { + format!("ignored on targets without {size}-bit atomic operations") + }, + }; + } + } + + IgnoreDecision::Continue +} + struct Need { name: &'static str, condition: bool, diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index 4d75c38dd3206..3c2d1df954b32 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -718,6 +718,31 @@ fn threads_support() { } } +#[test] +fn target_has_atomic_support() { + let atomics = [ + ("aarch64-unknown-linux-gnu", &["8", "16", "32", "64", "128", "ptr"][..], &[][..]), + ("aarch64-apple-darwin", &["8", "16", "32", "64", "128", "ptr"], &[]), + ("i686-pc-windows-gnu", &["8", "16", "32", "64", "ptr"], &["128"]), + ("i686-pc-windows-msvc", &["8", "16", "32", "64", "ptr"], &["128"]), + ("i686-unknown-linux-gnu", &["8", "16", "32", "64", "ptr"], &["128"]), + ("x86_64-apple-darwin", &["8", "16", "32", "64", "128", "ptr"], &[]), + ("x86_64-pc-windows-gnu", &["8", "16", "32", "64", "128", "ptr"], &[]), + ("x86_64-pc-windows-msvc", &["8", "16", "32", "64", "128", "ptr"], &[]), + ("x86_64-unknown-linux-gnu", &["8", "16", "32", "64", "ptr"], &["128"]), + ]; + + for (target, supported, not_supported) in atomics { + let config = cfg().target(target).build(); + for size in supported { + assert!(config.has_atomic(size)); + } + for size in not_supported { + assert!(!config.has_atomic(size)); + } + } +} + fn run_path(poisoned: &mut bool, path: &Path, buf: &[u8]) { let rdr = std::io::Cursor::new(&buf); iter_header(Mode::Ui, "ui", poisoned, path, rdr, &mut |_| {});