Skip to main content

compiletest/directives/
needs.rs

1use crate::common::{
2    Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer, query_rustc_output,
3};
4use crate::directives::{DirectiveLine, IgnoreDecision};
5
6pub(super) fn handle_needs(
7    cache: &CachedNeedsConditions,
8    config: &Config,
9    ln: &DirectiveLine<'_>,
10) -> IgnoreDecision {
11    // Note that we intentionally still put the needs- prefix here to make the file show up when
12    // grepping for a directive name, even though we could technically strip that.
13    let needs = &[
14        Need {
15            name: "needs-asm-support",
16            condition: config.has_asm_support(),
17            ignore_reason: "ignored on targets without inline assembly support",
18        },
19        Need {
20            name: "needs-sanitizer-support",
21            condition: cache.sanitizer_support,
22            ignore_reason: "ignored on targets without sanitizers support",
23        },
24        Need {
25            name: "needs-sanitizer-address",
26            condition: cache.sanitizer_address,
27            ignore_reason: "ignored on targets without address sanitizer",
28        },
29        Need {
30            name: "needs-sanitizer-cfi",
31            condition: cache.sanitizer_cfi,
32            ignore_reason: "ignored on targets without CFI sanitizer",
33        },
34        Need {
35            name: "needs-sanitizer-dataflow",
36            condition: cache.sanitizer_dataflow,
37            ignore_reason: "ignored on targets without dataflow sanitizer",
38        },
39        Need {
40            name: "needs-sanitizer-kcfi",
41            condition: cache.sanitizer_kcfi,
42            ignore_reason: "ignored on targets without kernel CFI sanitizer",
43        },
44        Need {
45            name: "needs-sanitizer-kasan",
46            condition: cache.sanitizer_kasan,
47            ignore_reason: "ignored on targets without kernel address sanitizer",
48        },
49        Need {
50            name: "needs-sanitizer-khwasan",
51            condition: cache.sanitizer_khwasan,
52            ignore_reason: "ignored on targets without kernel hardware-assisted address sanitizer",
53        },
54        Need {
55            name: "needs-sanitizer-leak",
56            condition: cache.sanitizer_leak,
57            ignore_reason: "ignored on targets without leak sanitizer",
58        },
59        Need {
60            name: "needs-sanitizer-memory",
61            condition: cache.sanitizer_memory,
62            ignore_reason: "ignored on targets without memory sanitizer",
63        },
64        Need {
65            name: "needs-sanitizer-thread",
66            condition: cache.sanitizer_thread,
67            ignore_reason: "ignored on targets without thread sanitizer",
68        },
69        Need {
70            name: "needs-sanitizer-hwaddress",
71            condition: cache.sanitizer_hwaddress,
72            ignore_reason: "ignored on targets without hardware-assisted address sanitizer",
73        },
74        Need {
75            name: "needs-sanitizer-memtag",
76            condition: cache.sanitizer_memtag,
77            ignore_reason: "ignored on targets without memory tagging sanitizer",
78        },
79        Need {
80            name: "needs-sanitizer-realtime",
81            condition: cache.sanitizer_realtime,
82            ignore_reason: "ignored on targets without realtime sanitizer",
83        },
84        Need {
85            name: "needs-sanitizer-shadow-call-stack",
86            condition: cache.sanitizer_shadow_call_stack,
87            ignore_reason: "ignored on targets without shadow call stacks",
88        },
89        Need {
90            name: "needs-sanitizer-safestack",
91            condition: cache.sanitizer_safestack,
92            ignore_reason: "ignored on targets without SafeStack support",
93        },
94        Need {
95            name: "needs-enzyme",
96            condition: config.has_enzyme && config.default_codegen_backend.is_llvm(),
97            ignore_reason: "ignored when LLVM Enzyme is disabled or LLVM is not the default codegen backend",
98        },
99        Need {
100            name: "needs-offload",
101            condition: config.has_offload && config.default_codegen_backend.is_llvm(),
102            ignore_reason: "ignored when LLVM Offload is disabled or LLVM is not the default codegen backend",
103        },
104        Need {
105            name: "needs-run-enabled",
106            condition: config.run_enabled(),
107            ignore_reason: "ignored when running the resulting test binaries is disabled",
108        },
109        Need {
110            name: "needs-threads",
111            condition: config.has_threads(),
112            ignore_reason: "ignored on targets without threading support",
113        },
114        Need {
115            name: "needs-subprocess",
116            condition: config.has_subprocess_support(),
117            ignore_reason: "ignored on targets without subprocess support",
118        },
119        Need {
120            name: "needs-unwind",
121            condition: config.can_unwind(),
122            ignore_reason: "ignored on targets without unwinding support",
123        },
124        Need {
125            name: "needs-profiler-runtime",
126            condition: config.profiler_runtime,
127            ignore_reason: "ignored when the profiler runtime is not available",
128        },
129        Need {
130            name: "needs-force-clang-based-tests",
131            condition: config.run_clang_based_tests_with.is_some(),
132            ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set",
133        },
134        Need {
135            name: "needs-xray",
136            condition: cache.xray,
137            ignore_reason: "ignored on targets without xray tracing",
138        },
139        Need {
140            name: "needs-rust-lld",
141            condition: cache.rust_lld,
142            ignore_reason: "ignored on targets without Rust's LLD",
143        },
144        Need {
145            name: "needs-dlltool",
146            condition: cache.dlltool,
147            ignore_reason: "ignored when dlltool for the current architecture is not present",
148        },
149        Need {
150            name: "needs-git-hash",
151            condition: config.git_hash,
152            ignore_reason: "ignored when git hashes have been omitted for building",
153        },
154        Need {
155            name: "needs-dynamic-linking",
156            condition: config.target_cfg().dynamic_linking,
157            ignore_reason: "ignored on targets without dynamic linking",
158        },
159        Need {
160            name: "needs-relocation-model-pic",
161            condition: config.target_cfg().relocation_model == "pic",
162            ignore_reason: "ignored on targets without PIC relocation model",
163        },
164        Need {
165            name: "needs-deterministic-layouts",
166            condition: !config.rust_randomized_layout,
167            ignore_reason: "ignored when randomizing layouts",
168        },
169        Need {
170            name: "needs-wasmtime",
171            condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")),
172            ignore_reason: "ignored when wasmtime runner is not available",
173        },
174        Need {
175            name: "needs-symlink",
176            condition: cache.symlinks,
177            ignore_reason: "ignored if symlinks are unavailable",
178        },
179        Need {
180            name: "needs-llvm-zstd",
181            condition: cache.llvm_zstd && config.default_codegen_backend.is_llvm(),
182            ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression or LLVM is not the default codegen backend",
183        },
184        Need {
185            name: "needs-rustc-debug-assertions",
186            condition: config.with_rustc_debug_assertions,
187            ignore_reason: "ignored if rustc wasn't built with debug assertions",
188        },
189        Need {
190            name: "needs-std-debug-assertions",
191            condition: config.with_std_debug_assertions,
192            ignore_reason: "ignored if std wasn't built with debug assertions",
193        },
194        Need {
195            name: "needs-std-remap-debuginfo",
196            condition: config.with_std_remap_debuginfo,
197            ignore_reason: "ignored if std wasn't built with remapping of debuginfo",
198        },
199        Need {
200            name: "needs-target-std",
201            condition: build_helper::targets::target_supports_std(&config.target),
202            ignore_reason: "ignored if target does not support std",
203        },
204    ];
205
206    let &DirectiveLine { name, .. } = ln;
207
208    if name == "needs-target-has-atomic" {
209        let Some(rest) = ln.value_after_colon() else {
210            return IgnoreDecision::Error {
211                message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(),
212            };
213        };
214
215        // Expect directive value to be a list of comma-separated atomic widths.
216        let specified_widths = rest
217            .split(',')
218            .map(|width| width.trim())
219            .map(ToString::to_string)
220            .collect::<Vec<String>>();
221
222        for width in &specified_widths {
223            if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) {
224                return IgnoreDecision::Error {
225                    message: format!(
226                        "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \
227                        known `target_has_atomic_width`, known values are `{:?}`",
228                        KNOWN_TARGET_HAS_ATOMIC_WIDTHS
229                    ),
230                };
231            }
232        }
233
234        let satisfies_all_specified_widths = specified_widths
235            .iter()
236            .all(|specified| config.target_cfg().target_has_atomic.contains(specified));
237        if satisfies_all_specified_widths {
238            return IgnoreDecision::Continue;
239        } else {
240            return IgnoreDecision::Ignore {
241                reason: format!(
242                    "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`",
243                    specified_widths
244                ),
245            };
246        }
247    }
248
249    // FIXME(jieyouxu): share multi-value directive logic with `needs-target-has-atomic` above.
250    if name == "needs-crate-type" {
251        let Some(rest) = ln.value_after_colon() else {
252            return IgnoreDecision::Error {
253                message:
254                    "expected `needs-crate-type` to have a comma-separated list of crate types"
255                        .to_string(),
256            };
257        };
258
259        // Expect directive value to be a list of comma-separated crate-types.
260        let specified_crate_types = rest
261            .split(',')
262            .map(|crate_type| crate_type.trim())
263            .map(ToString::to_string)
264            .collect::<Vec<String>>();
265
266        for crate_type in &specified_crate_types {
267            if !KNOWN_CRATE_TYPES.contains(&crate_type.as_str()) {
268                return IgnoreDecision::Error {
269                    message: format!(
270                        "unknown crate type specified in `needs-crate-type`: `{crate_type}` is not \
271                        a known crate type, known values are `{:?}`",
272                        KNOWN_CRATE_TYPES
273                    ),
274                };
275            }
276        }
277
278        let satisfies_all_crate_types = specified_crate_types
279            .iter()
280            .all(|specified| config.supported_crate_types().contains(specified));
281        if satisfies_all_crate_types {
282            return IgnoreDecision::Continue;
283        } else {
284            return IgnoreDecision::Ignore {
285                reason: format!(
286                    "skipping test as target does not support all of the crate types `{:?}`",
287                    specified_crate_types
288                ),
289            };
290        }
291    }
292
293    if name == "needs-asm-mnemonic" {
294        let Some(rest) = ln.value_after_colon() else {
295            return IgnoreDecision::Error {
296                message: "expected `needs-asm-mnemonic` to have a mnemonic name after colon"
297                    .to_string(),
298            };
299        };
300
301        if !config.default_codegen_backend.is_llvm() {
302            return IgnoreDecision::Ignore {
303                reason: "skipping test as non-LLVM backend does not support mnemonic queries"
304                    .to_string(),
305            };
306        }
307
308        let mnemonic = rest.trim();
309        let has_mnemonic = match mnemonic {
310            "ret" => cache.has_ret_mnemonic,
311            "nop" => cache.has_nop_mnemonic,
312            _ => has_mnemonic(config, mnemonic),
313        };
314
315        if has_mnemonic {
316            return IgnoreDecision::Continue;
317        } else {
318            return IgnoreDecision::Ignore {
319                reason: format!("skipping test as target does not have `{mnemonic}` mnemonic"),
320            };
321        }
322    }
323
324    if !name.starts_with("needs-") {
325        return IgnoreDecision::Continue;
326    }
327
328    // Handled elsewhere.
329    if name == "needs-llvm-components" || name == "needs-backends" {
330        return IgnoreDecision::Continue;
331    }
332
333    let mut found_valid = false;
334    for need in needs {
335        if need.name == name {
336            if need.condition {
337                found_valid = true;
338                break;
339            } else {
340                return IgnoreDecision::Ignore {
341                    reason: if let Some(comment) = ln.remark_after_space() {
342                        format!("{} ({})", need.ignore_reason, comment.trim())
343                    } else {
344                        need.ignore_reason.into()
345                    },
346                };
347            }
348        }
349    }
350
351    if found_valid {
352        IgnoreDecision::Continue
353    } else {
354        IgnoreDecision::Error { message: format!("invalid needs directive: {name}") }
355    }
356}
357
358struct Need {
359    name: &'static str,
360    condition: bool,
361    ignore_reason: &'static str,
362}
363
364pub(super) struct CachedNeedsConditions {
365    sanitizer_support: bool,
366    sanitizer_address: bool,
367    sanitizer_cfi: bool,
368    sanitizer_dataflow: bool,
369    sanitizer_kcfi: bool,
370    sanitizer_kasan: bool,
371    sanitizer_khwasan: bool,
372    sanitizer_leak: bool,
373    sanitizer_memory: bool,
374    sanitizer_thread: bool,
375    sanitizer_hwaddress: bool,
376    sanitizer_memtag: bool,
377    sanitizer_realtime: bool,
378    sanitizer_shadow_call_stack: bool,
379    sanitizer_safestack: bool,
380    xray: bool,
381    rust_lld: bool,
382    dlltool: bool,
383    symlinks: bool,
384    /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive.
385    llvm_zstd: bool,
386    /// Might add particular other mnemonics heavily needed by tests here.
387    /// Otherwise call into llvm for every check
388    has_ret_mnemonic: bool,
389    has_nop_mnemonic: bool,
390}
391
392impl CachedNeedsConditions {
393    pub(super) fn load(config: &Config) -> Self {
394        let target = &&*config.target;
395        let sanitizers = &config.target_cfg().sanitizers;
396        Self {
397            sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
398            sanitizer_address: sanitizers.contains(&Sanitizer::Address),
399            sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi),
400            sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow),
401            sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi),
402            sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress),
403            sanitizer_khwasan: sanitizers.contains(&Sanitizer::KernelHwaddress),
404            sanitizer_leak: sanitizers.contains(&Sanitizer::Leak),
405            sanitizer_memory: sanitizers.contains(&Sanitizer::Memory),
406            sanitizer_thread: sanitizers.contains(&Sanitizer::Thread),
407            sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress),
408            sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag),
409            sanitizer_realtime: sanitizers.contains(&Sanitizer::Realtime),
410            sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack),
411            sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack),
412            xray: config.target_cfg().xray,
413
414            // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),
415            // we need to find whether `rust-lld` is present in the compiler under test.
416            //
417            // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
418            // example:
419            // - on linux, it can be <sysroot>/lib
420            // - on windows, it can be <sysroot>/bin
421            //
422            // However, `rust-lld` is only located under the lib path, so we look for it there.
423            rust_lld: config
424                .host_compile_lib_path
425                .parent()
426                .expect("couldn't traverse to the parent of the specified --compile-lib-path")
427                .join("lib")
428                .join("rustlib")
429                .join(target)
430                .join("bin")
431                .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
432                .exists(),
433
434            llvm_zstd: llvm_has_zstd(&config),
435            dlltool: find_dlltool(&config),
436            symlinks: has_symlinks(),
437            has_ret_mnemonic: has_mnemonic(config, "ret"),
438            has_nop_mnemonic: has_mnemonic(config, "nop"),
439        }
440    }
441}
442
443fn find_dlltool(config: &Config) -> bool {
444    let path = std::env::var_os("PATH").expect("missing PATH environment variable");
445    let path = std::env::split_paths(&path).collect::<Vec<_>>();
446
447    // dlltool is used ony by GNU based `*-*-windows-gnu`
448    if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
449        return false;
450    }
451
452    // On Windows, dlltool.exe is used for all architectures.
453    // For non-Windows, there are architecture specific dlltool binaries.
454    let dlltool_found = if cfg!(windows) {
455        path.iter().any(|dir| dir.join("dlltool.exe").is_file())
456    } else if config.matches_arch("i686") {
457        path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file())
458    } else if config.matches_arch("x86_64") {
459        path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file())
460    } else {
461        false
462    };
463    dlltool_found
464}
465
466// FIXME(#135928): this is actually not quite right because this detection is run on the **host**.
467// This however still helps the case of windows -> windows local development in case symlinks are
468// not available.
469#[cfg(windows)]
470fn has_symlinks() -> bool {
471    if std::env::var_os("CI").is_some() {
472        return true;
473    }
474    let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK");
475    if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() {
476        std::fs::remove_file(&link).unwrap();
477        true
478    } else {
479        false
480    }
481}
482
483#[cfg(not(windows))]
484fn has_symlinks() -> bool {
485    true
486}
487
488fn llvm_has_zstd(config: &Config) -> bool {
489    // FIXME(#149764): This actually queries the compiler's _default_ backend,
490    // which is usually LLVM, but can be another backend depending on the value
491    // of `rust.codegen-backends` in bootstrap.toml.
492
493    // The compiler already knows whether LLVM was built with zstd or not,
494    // so compiletest can just ask the compiler.
495    let output = query_rustc_output(
496        config,
497        &["-Zunstable-options", "--print=backend-has-zstd"],
498        Default::default(),
499    );
500    match output.trim() {
501        "true" => true,
502        "false" => false,
503        _ => panic!("unexpected output from `--print=backend-has-zstd`: {output:?}"),
504    }
505}
506
507fn has_mnemonic(config: &Config, mnemonic: &str) -> bool {
508    if !config.default_codegen_backend.is_llvm() {
509        return false;
510    }
511
512    let target_flag = format!("--target={}", config.target);
513    let output = query_rustc_output(
514        config,
515        &[
516            &target_flag,
517            "-Zunstable-options",
518            &format!("--print=backend-has-mnemonic:{}", mnemonic),
519        ],
520        Default::default(),
521    );
522
523    match output.trim() {
524        "true" => true,
525        "false" => false,
526        _ => panic!("unexpected output from `--print=backend-has-mnemonic:{mnemonic}`: {output:?}"),
527    }
528}