about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-06-24 22:42:26 +0000
committerbors <bors@rust-lang.org>2021-06-24 22:42:26 +0000
commitd4e7cb3254940e1db6d0670ecb15ccd89d6f69e3 (patch)
treeb3cb75cfcb21fb5986925b4b0b9e68890b0ff9df
parent7c3872e6bfd555d2ad753ac1f871db3bd7f2a547 (diff)
parentcfcb2b664d6f1419a6219f88b060dee420736407 (diff)
downloadrust-d4e7cb3254940e1db6d0670ecb15ccd89d6f69e3.tar.gz
rust-d4e7cb3254940e1db6d0670ecb15ccd89d6f69e3.zip
Auto merge of #86272 - nagisa:nagisa/tidy-llvm-components, r=Mark-Simulacrum
tidy: verify that test revisions with --target have associated needs-llvm-components directives

This ensures that people who tend to write `--target` `#[no_core]` tests don't miss specifying the `needs-llvm-components` directive. This is necessary for the test suite to pass when LLVM is compiled with a subset of components enabled.

While here I also took the opportunity to implement a more fine-grained handling of the ignore directives, so that they are evaluated for each revision, rather than for the entire test. With this even if people have `arm` component disabled, only the revision that depends on the arm component will not run.

Fixes https://github.com/rust-lang/rust/issues/82405
-rw-r--r--src/test/assembly/asm/mips-types.rs3
-rw-r--r--src/test/assembly/asm/powerpc-types.rs3
-rw-r--r--src/test/assembly/asm/riscv-types.rs3
-rw-r--r--src/test/assembly/asm/x86-modifiers.rs2
-rw-r--r--src/test/assembly/asm/x86-types.rs2
-rw-r--r--src/test/assembly/static-relocation-model.rs4
-rw-r--r--src/test/codegen/abi-efiapi.rs7
-rw-r--r--src/test/codegen/asm-sanitize-llvm.rs3
-rw-r--r--src/test/codegen/default-requires-uwtable.rs3
-rw-r--r--src/test/codegen/i686-macosx-deployment-target.rs1
-rw-r--r--src/test/codegen/i686-no-macosx-deployment-target.rs1
-rw-r--r--src/test/codegen/sparc-struct-abi.rs3
-rw-r--r--src/test/codegen/x86_64-macosx-deployment-target.rs1
-rw-r--r--src/test/codegen/x86_64-no-macosx-deployment-target.rs1
-rw-r--r--src/test/ui/asm/inline-syntax.arm.stderr12
-rw-r--r--src/test/ui/asm/inline-syntax.rs4
-rw-r--r--src/test/ui/asm/inline-syntax.x86_64.stderr14
-rw-r--r--src/test/ui/borrowck/two-phase-reservation-sharing-interference.rs4
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs16
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs18
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.rs8
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.stderr8
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.rs8
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.stderr4
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-registers.rs14
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-stack.rs16
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.rs13
-rw-r--r--src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.stderr4
-rw-r--r--src/test/ui/crate-loading/missing-std.rs1
-rw-r--r--src/test/ui/crate-loading/missing-std.stderr2
-rw-r--r--src/test/ui/sanitize/crt-static.rs1
-rw-r--r--src/test/ui/sanitize/incompatible.rs1
-rw-r--r--src/test/ui/sanitize/unsupported-target.rs1
-rw-r--r--src/tools/compiletest/src/header.rs462
-rw-r--r--src/tools/compiletest/src/header/tests.rs113
-rw-r--r--src/tools/compiletest/src/main.rs64
-rw-r--r--src/tools/tidy/src/lib.rs1
-rw-r--r--src/tools/tidy/src/main.rs2
-rw-r--r--src/tools/tidy/src/target_specific_tests.rs96
39 files changed, 517 insertions, 407 deletions
diff --git a/src/test/assembly/asm/mips-types.rs b/src/test/assembly/asm/mips-types.rs
index 60cfebdd992..9ec7ba83c42 100644
--- a/src/test/assembly/asm/mips-types.rs
+++ b/src/test/assembly/asm/mips-types.rs
@@ -2,8 +2,9 @@
 // revisions: mips32 mips64
 // assembly-output: emit-asm
 //[mips32] compile-flags: --target mips-unknown-linux-gnu
+//[mips32] needs-llvm-components: mips
 //[mips64] compile-flags: --target mips64-unknown-linux-gnuabi64
-// needs-llvm-components: mips
+//[mips64] needs-llvm-components: mips
 
 #![feature(no_core, lang_items, rustc_attrs, repr_simd)]
 #![crate_type = "rlib"]
diff --git a/src/test/assembly/asm/powerpc-types.rs b/src/test/assembly/asm/powerpc-types.rs
index 742e4ddaed6..1e263649e86 100644
--- a/src/test/assembly/asm/powerpc-types.rs
+++ b/src/test/assembly/asm/powerpc-types.rs
@@ -2,8 +2,9 @@
 // revisions: powerpc powerpc64
 // assembly-output: emit-asm
 //[powerpc] compile-flags: --target powerpc-unknown-linux-gnu
+//[powerpc] needs-llvm-components: powerpc
 //[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu
-// needs-llvm-components: powerpc
+//[powerpc64] needs-llvm-components: powerpc
 
 #![feature(no_core, lang_items, rustc_attrs, repr_simd)]
 #![crate_type = "rlib"]
diff --git a/src/test/assembly/asm/riscv-types.rs b/src/test/assembly/asm/riscv-types.rs
index 1ba73fcac9d..e62a6197b9a 100644
--- a/src/test/assembly/asm/riscv-types.rs
+++ b/src/test/assembly/asm/riscv-types.rs
@@ -2,9 +2,10 @@
 // revisions: riscv64 riscv32
 // assembly-output: emit-asm
 //[riscv64] compile-flags: --target riscv64imac-unknown-none-elf
+//[riscv64] needs-llvm-components: riscv
 //[riscv32] compile-flags: --target riscv32imac-unknown-none-elf
+//[riscv32] needs-llvm-components: riscv
 // compile-flags: -C target-feature=+d
-// needs-llvm-components: riscv
 // min-system-llvm-version: 12.0
 
 #![feature(no_core, lang_items, rustc_attrs)]
diff --git a/src/test/assembly/asm/x86-modifiers.rs b/src/test/assembly/asm/x86-modifiers.rs
index da2dc51d69e..c926fd7b3f5 100644
--- a/src/test/assembly/asm/x86-modifiers.rs
+++ b/src/test/assembly/asm/x86-modifiers.rs
@@ -3,7 +3,9 @@
 // assembly-output: emit-asm
 // compile-flags: -O
 //[x86_64] compile-flags: --target x86_64-unknown-linux-gnu
+//[x86_64] needs-llvm-components: x86
 //[i686] compile-flags: --target i686-unknown-linux-gnu
+//[i686] needs-llvm-components: x86
 // compile-flags: -C llvm-args=--x86-asm-syntax=intel
 // compile-flags: -C target-feature=+avx512bw
 
diff --git a/src/test/assembly/asm/x86-types.rs b/src/test/assembly/asm/x86-types.rs
index b65b727d225..d25f3a03777 100644
--- a/src/test/assembly/asm/x86-types.rs
+++ b/src/test/assembly/asm/x86-types.rs
@@ -2,7 +2,9 @@
 // revisions: x86_64 i686
 // assembly-output: emit-asm
 //[x86_64] compile-flags: --target x86_64-unknown-linux-gnu
+//[x86_64] needs-llvm-components: x86
 //[i686] compile-flags: --target i686-unknown-linux-gnu
+//[i686] needs-llvm-components: x86
 // compile-flags: -C llvm-args=--x86-asm-syntax=intel
 // compile-flags: -C target-feature=+avx512bw
 
diff --git a/src/test/assembly/static-relocation-model.rs b/src/test/assembly/static-relocation-model.rs
index 2cd74a01c84..b331d45668a 100644
--- a/src/test/assembly/static-relocation-model.rs
+++ b/src/test/assembly/static-relocation-model.rs
@@ -1,10 +1,12 @@
 // min-llvm-version: 12.0.0
-// needs-llvm-components: aarch64 x86 powerpc
 // revisions: x64 A64 ppc64le
 // assembly-output: emit-asm
 // [x64] compile-flags: --target x86_64-unknown-linux-gnu -Crelocation-model=static
+// [x64] needs-llvm-components: x86
 // [A64] compile-flags: --target aarch64-unknown-linux-gnu -Crelocation-model=static
+// [A64] needs-llvm-components: aarch64
 // [ppc64le] compile-flags: --target powerpc64le-unknown-linux-gnu -Crelocation-model=static
+// [ppc64le] needs-llvm-components: powerpc
 
 #![feature(no_core, lang_items)]
 #![no_core]
diff --git a/src/test/codegen/abi-efiapi.rs b/src/test/codegen/abi-efiapi.rs
index 613b0bf50e5..b4fda5f8c84 100644
--- a/src/test/codegen/abi-efiapi.rs
+++ b/src/test/codegen/abi-efiapi.rs
@@ -1,13 +1,16 @@
 // Checks if the correct annotation for the efiapi ABI is passed to llvm.
 
 // revisions:x86_64 i686 aarch64 arm riscv
-// needs-llvm-components: aarch64 arm riscv
-
 //[x86_64] compile-flags: --target x86_64-unknown-uefi
+//[x86_64] needs-llvm-components: aarch64 arm riscv
 //[i686] compile-flags: --target i686-unknown-linux-musl
+//[i686] needs-llvm-components: aarch64 arm riscv
 //[aarch64] compile-flags: --target aarch64-unknown-none
+//[aarch64] needs-llvm-components: aarch64 arm riscv
 //[arm] compile-flags: --target armv7r-none-eabi
+//[arm] needs-llvm-components: aarch64 arm riscv
 //[riscv] compile-flags: --target riscv64gc-unknown-none-elf
+//[riscv] needs-llvm-components: aarch64 arm riscv
 // compile-flags: -C no-prepopulate-passes
 
 #![crate_type = "lib"]
diff --git a/src/test/codegen/asm-sanitize-llvm.rs b/src/test/codegen/asm-sanitize-llvm.rs
index fe09caa6973..135177016bf 100644
--- a/src/test/codegen/asm-sanitize-llvm.rs
+++ b/src/test/codegen/asm-sanitize-llvm.rs
@@ -1,5 +1,6 @@
-// FIXME(nagisa): remove the flags here once all targets support `asm!`.
+// FIXME(nagisa): remove the flags below once all targets support `asm!`.
 // compile-flags: --target x86_64-unknown-linux-gnu
+// needs-llvm-components: x86
 
 // Verify we sanitize the special tokens for the LLVM inline-assembly, ensuring people won't
 // inadvertently rely on the LLVM-specific syntax and features.
diff --git a/src/test/codegen/default-requires-uwtable.rs b/src/test/codegen/default-requires-uwtable.rs
index d4c4200c5d2..5d77d3f14bb 100644
--- a/src/test/codegen/default-requires-uwtable.rs
+++ b/src/test/codegen/default-requires-uwtable.rs
@@ -1,8 +1,9 @@
 // revisions: WINDOWS ANDROID
-// needs-llvm-components: x86 arm
 // compile-flags: -C panic=abort
 // [WINDOWS] compile-flags: --target=x86_64-pc-windows-msvc
+// [WINDOWS] needs-llvm-components: x86
 // [ANDROID] compile-flags: --target=armv7-linux-androideabi
+// [ANDROID] needs-llvm-components: arm
 
 #![feature(no_core, lang_items)]
 #![crate_type = "lib"]
diff --git a/src/test/codegen/i686-macosx-deployment-target.rs b/src/test/codegen/i686-macosx-deployment-target.rs
index dad376d6677..17258a264a5 100644
--- a/src/test/codegen/i686-macosx-deployment-target.rs
+++ b/src/test/codegen/i686-macosx-deployment-target.rs
@@ -3,6 +3,7 @@
 // See issue #60235.
 
 // compile-flags: -O --target=i686-apple-darwin --crate-type=rlib
+// needs-llvm-components: x86
 // rustc-env:MACOSX_DEPLOYMENT_TARGET=10.9
 #![feature(no_core, lang_items)]
 #![no_core]
diff --git a/src/test/codegen/i686-no-macosx-deployment-target.rs b/src/test/codegen/i686-no-macosx-deployment-target.rs
index 1cebc49236f..043040a95e3 100644
--- a/src/test/codegen/i686-no-macosx-deployment-target.rs
+++ b/src/test/codegen/i686-no-macosx-deployment-target.rs
@@ -3,6 +3,7 @@
 // See issue #60235.
 
 // compile-flags: -O --target=i686-apple-darwin --crate-type=rlib
+// needs-llvm-components: x86
 // unset-rustc-env:MACOSX_DEPLOYMENT_TARGET
 #![feature(no_core, lang_items)]
 #![no_core]
diff --git a/src/test/codegen/sparc-struct-abi.rs b/src/test/codegen/sparc-struct-abi.rs
index 78e5b14a212..f228d7c5500 100644
--- a/src/test/codegen/sparc-struct-abi.rs
+++ b/src/test/codegen/sparc-struct-abi.rs
@@ -1,9 +1,8 @@
-//
 // Checks that we correctly codegen extern "C" functions returning structs.
 // See issue #52638.
 
-// only-sparc64
 // compile-flags: -O --target=sparc64-unknown-linux-gnu --crate-type=rlib
+// needs-llvm-components: sparc
 #![feature(no_core, lang_items)]
 #![no_core]
 
diff --git a/src/test/codegen/x86_64-macosx-deployment-target.rs b/src/test/codegen/x86_64-macosx-deployment-target.rs
index 8e291b7b298..8e673d11d98 100644
--- a/src/test/codegen/x86_64-macosx-deployment-target.rs
+++ b/src/test/codegen/x86_64-macosx-deployment-target.rs
@@ -3,6 +3,7 @@
 // See issue #60235.
 
 // compile-flags: -O --target=x86_64-apple-darwin --crate-type=rlib
+// needs-llvm-components: x86
 // rustc-env:MACOSX_DEPLOYMENT_TARGET=10.9
 #![feature(no_core, lang_items)]
 #![no_core]
diff --git a/src/test/codegen/x86_64-no-macosx-deployment-target.rs b/src/test/codegen/x86_64-no-macosx-deployment-target.rs
index c5ac73b54e1..25ae6924de0 100644
--- a/src/test/codegen/x86_64-no-macosx-deployment-target.rs
+++ b/src/test/codegen/x86_64-no-macosx-deployment-target.rs
@@ -3,6 +3,7 @@
 // See issue #60235.
 
 // compile-flags: -O --target=x86_64-apple-darwin --crate-type=rlib
+// needs-llvm-components: x86
 // unset-rustc-env:MACOSX_DEPLOYMENT_TARGET
 #![feature(no_core, lang_items)]
 #![no_core]
diff --git a/src/test/ui/asm/inline-syntax.arm.stderr b/src/test/ui/asm/inline-syntax.arm.stderr
index b1685bd4e02..bf6ea6b67f9 100644
--- a/src/test/ui/asm/inline-syntax.arm.stderr
+++ b/src/test/ui/asm/inline-syntax.arm.stderr
@@ -13,7 +13,7 @@ LL | .intel_syntax noprefix
    | ^
 
 error: unknown directive
-  --> $DIR/inline-syntax.rs:29:15
+  --> $DIR/inline-syntax.rs:31:15
    |
 LL |         asm!(".intel_syntax noprefix", "nop");
    |               ^
@@ -25,7 +25,7 @@ LL |     .intel_syntax noprefix
    |     ^
 
 error: unknown directive
-  --> $DIR/inline-syntax.rs:32:15
+  --> $DIR/inline-syntax.rs:34:15
    |
 LL |         asm!(".intel_syntax aaa noprefix", "nop");
    |               ^
@@ -37,7 +37,7 @@ LL |     .intel_syntax aaa noprefix
    |     ^
 
 error: unknown directive
-  --> $DIR/inline-syntax.rs:35:15
+  --> $DIR/inline-syntax.rs:37:15
    |
 LL |         asm!(".att_syntax noprefix", "nop");
    |               ^
@@ -49,7 +49,7 @@ LL |     .att_syntax noprefix
    |     ^
 
 error: unknown directive
-  --> $DIR/inline-syntax.rs:38:15
+  --> $DIR/inline-syntax.rs:40:15
    |
 LL |         asm!(".att_syntax bbb noprefix", "nop");
    |               ^
@@ -61,7 +61,7 @@ LL |     .att_syntax bbb noprefix
    |     ^
 
 error: unknown directive
-  --> $DIR/inline-syntax.rs:41:15
+  --> $DIR/inline-syntax.rs:43:15
    |
 LL |         asm!(".intel_syntax noprefix; nop");
    |               ^
@@ -73,7 +73,7 @@ LL |     .intel_syntax noprefix; nop
    |     ^
 
 error: unknown directive
-  --> $DIR/inline-syntax.rs:47:13
+  --> $DIR/inline-syntax.rs:49:13
    |
 LL |             .intel_syntax noprefix
    |             ^
diff --git a/src/test/ui/asm/inline-syntax.rs b/src/test/ui/asm/inline-syntax.rs
index 2d54ef7bd6b..481990398f4 100644
--- a/src/test/ui/asm/inline-syntax.rs
+++ b/src/test/ui/asm/inline-syntax.rs
@@ -1,11 +1,13 @@
-// needs-llvm-components: arm
 // revisions: x86_64 arm
 //[x86_64] compile-flags: --target x86_64-unknown-linux-gnu
 //[x86_64] check-pass
+//[x86_64] needs-llvm-components: x86
 //[x86_64_allowed] compile-flags: --target x86_64-unknown-linux-gnu
 //[x86_64_allowed] check-pass
+//[x86_64_allowed] needs-llvm-components: x86
 //[arm] compile-flags: --target armv7-unknown-linux-gnueabihf
 //[arm] build-fail
+//[arm] needs-llvm-components: arm
 
 #![feature(no_core, lang_items, rustc_attrs)]
 #![crate_type = "rlib"]
diff --git a/src/test/ui/asm/inline-syntax.x86_64.stderr b/src/test/ui/asm/inline-syntax.x86_64.stderr
index 59c95194322..dcbc17bb260 100644
--- a/src/test/ui/asm/inline-syntax.x86_64.stderr
+++ b/src/test/ui/asm/inline-syntax.x86_64.stderr
@@ -1,5 +1,5 @@
 warning: avoid using `.intel_syntax`, Intel syntax is the default
-  --> $DIR/inline-syntax.rs:55:14
+  --> $DIR/inline-syntax.rs:57:14
    |
 LL | global_asm!(".intel_syntax noprefix", "nop");
    |              ^^^^^^^^^^^^^^^^^^^^^^
@@ -7,37 +7,37 @@ LL | global_asm!(".intel_syntax noprefix", "nop");
    = note: `#[warn(bad_asm_style)]` on by default
 
 warning: avoid using `.intel_syntax`, Intel syntax is the default
-  --> $DIR/inline-syntax.rs:29:15
+  --> $DIR/inline-syntax.rs:31:15
    |
 LL |         asm!(".intel_syntax noprefix", "nop");
    |               ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: avoid using `.intel_syntax`, Intel syntax is the default
-  --> $DIR/inline-syntax.rs:32:15
+  --> $DIR/inline-syntax.rs:34:15
    |
 LL |         asm!(".intel_syntax aaa noprefix", "nop");
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: avoid using `.att_syntax`, prefer using `options(att_syntax)` instead
-  --> $DIR/inline-syntax.rs:35:15
+  --> $DIR/inline-syntax.rs:37:15
    |
 LL |         asm!(".att_syntax noprefix", "nop");
    |               ^^^^^^^^^^^^^^^^^^^^
 
 warning: avoid using `.att_syntax`, prefer using `options(att_syntax)` instead
-  --> $DIR/inline-syntax.rs:38:15
+  --> $DIR/inline-syntax.rs:40:15
    |
 LL |         asm!(".att_syntax bbb noprefix", "nop");
    |               ^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: avoid using `.intel_syntax`, Intel syntax is the default
-  --> $DIR/inline-syntax.rs:41:15
+  --> $DIR/inline-syntax.rs:43:15
    |
 LL |         asm!(".intel_syntax noprefix; nop");
    |               ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: avoid using `.intel_syntax`, Intel syntax is the default
-  --> $DIR/inline-syntax.rs:47:13
+  --> $DIR/inline-syntax.rs:49:13
    |
 LL |             .intel_syntax noprefix
    |             ^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/test/ui/borrowck/two-phase-reservation-sharing-interference.rs b/src/test/ui/borrowck/two-phase-reservation-sharing-interference.rs
index f7392bfeaab..50248a55838 100644
--- a/src/test/ui/borrowck/two-phase-reservation-sharing-interference.rs
+++ b/src/test/ui/borrowck/two-phase-reservation-sharing-interference.rs
@@ -1,8 +1,8 @@
 // revisions: nll_target
 
-// The following revisions are disabled due to missing support from two-phase beyond autorefs
+// The nll_beyond revision is disabled due to missing support from two-phase beyond autorefs
 //[nll_beyond]compile-flags: -Z borrowck=mir -Z two-phase-beyond-autoref
-//[nll_beyond] should-fail
+//[nll_beyond]should-fail
 
 //[nll_target]compile-flags: -Z borrowck=mir
 
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs
index b09ea06c8f3..9e4521df8c3 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs
@@ -1,13 +1,21 @@
 // build-pass
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(abi_c_cmse_nonsecure_call)]
-#![no_std]
+// needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
+#![no_core]
+#[lang="sized"]
+pub trait Sized { }
+#[lang="copy"]
+pub trait Copy { }
+
+extern "rust-intrinsic" {
+    pub fn transmute<T, U>(e: T) -> U;
+}
 
 #[no_mangle]
 pub fn test(a: u32, b: u32, c: u32, d: u32) -> u32 {
     let non_secure_function = unsafe {
-        core::mem::transmute::<usize, extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32) -> u32>(
+        transmute::<usize, extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32) -> u32>(
             0x10000004,
         )
     };
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs
index cfdce4f4e92..9697146d5c3 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs
@@ -1,12 +1,22 @@
+// build-fail
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(abi_c_cmse_nonsecure_call)]
-#![no_std]
+// needs-llvm-components: arm
+// min-llvm-version: 11.0
+#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
+#![no_core]
+#[lang="sized"]
+pub trait Sized { }
+#[lang="copy"]
+pub trait Copy { }
+
+extern "rust-intrinsic" {
+    pub fn transmute<T, U>(e: T) -> U;
+}
 
 #[no_mangle]
 pub fn test(a: u32, b: u32, c: u32, d: u32, e: u32) -> u32 {
     let non_secure_function = unsafe {
-        core::mem::transmute::<
+        transmute::<
             usize,
             extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32>
         (
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.rs
index 17117301fef..f32b3709002 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.rs
@@ -1,6 +1,8 @@
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(abi_c_cmse_nonsecure_call)]
-#![no_std]
+// needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, lang_items, no_core)]
+#![no_core]
+#[lang="sized"]
+trait Sized { }
 
 pub extern "C-cmse-nonsecure-call" fn test() {} //~ ERROR [E0781]
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.stderr b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.stderr
index 78490bf8f68..3564ab4b6cd 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.stderr
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-1.stderr
@@ -1,8 +1,8 @@
-error[E0781]: the `"cmse-nonsecure-call"` ABI is only allowed on function pointers.
-  --> $DIR/wrong-abi-location-1.rs:6:1
+error[E0781]: the `"C-cmse-nonsecure-call"` ABI is only allowed on function pointers.
+  --> $DIR/wrong-abi-location-1.rs:8:1
    |
-LL | pub extern "C-cmse-nonsecure-call" fn test() {} //~ ERROR [E0781]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | pub extern "C-cmse-nonsecure-call" fn test() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.rs
index 78f553d747c..6f8bb24aa69 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.rs
@@ -1,7 +1,9 @@
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(abi_c_cmse_nonsecure_call)]
-#![no_std]
+// needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, lang_items, no_core)]
+#![no_core]
+#[lang="sized"]
+trait Sized { }
 
 extern "C-cmse-nonsecure-call" { //~ ERROR [E0781]
     fn test();
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.stderr b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.stderr
index 5c148e2cd6f..76073f54884 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.stderr
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-call/wrong-abi-location-2.stderr
@@ -1,8 +1,8 @@
 error[E0781]: the `"C-cmse-nonsecure-call"` ABI is only allowed on function pointers.
-  --> $DIR/wrong-abi-location-2.rs:6:1
+  --> $DIR/wrong-abi-location-2.rs:8:1
    |
 LL | / extern "C-cmse-nonsecure-call" {
-LL | |     fn test(); //~ ERROR [E0781]
+LL | |     fn test();
 LL | | }
    | |_^
 
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-registers.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-registers.rs
index a723eb73473..8cde9ba58b9 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-registers.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-registers.rs
@@ -1,11 +1,15 @@
 // build-pass
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(cmse_nonsecure_entry)]
-#![no_std]
+// needs-llvm-components: arm
+#![feature(cmse_nonsecure_entry, no_core, lang_items)]
+#![no_core]
+#[lang="sized"]
+trait Sized { }
+#[lang="copy"]
+trait Copy { }
 
 #[no_mangle]
 #[cmse_nonsecure_entry]
-pub extern "C" fn entry_function(a: u32, b: u32, c: u32, d: u32) -> u32 {
-    a + b + c + d
+pub extern "C" fn entry_function(_: u32, _: u32, _: u32, d: u32) -> u32 {
+    d
 }
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-stack.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-stack.rs
index 553d3a8cb0b..74321fdfdde 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-stack.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/params-on-stack.rs
@@ -1,10 +1,16 @@
+// build-fail
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(cmse_nonsecure_entry)]
-#![no_std]
+// needs-llvm-components: arm
+// min-llvm-version: 11.0
+#![feature(cmse_nonsecure_entry, no_core, lang_items)]
+#![no_core]
+#[lang="sized"]
+trait Sized { }
+#[lang="copy"]
+trait Copy { }
 
 #[no_mangle]
 #[cmse_nonsecure_entry]
-pub extern "C" fn entry_function(a: u32, b: u32, c: u32, d: u32, e: u32) -> u32 { //~ ERROR
-    a + b + c + d + e
+pub extern "C" fn entry_function(_: u32, _: u32, _: u32, _: u32, e: u32) -> u32 {
+    e
 }
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.rs b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.rs
index 611c8643dcb..6320d296373 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.rs
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.rs
@@ -1,10 +1,13 @@
 // compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-// only-thumbv8m.main-none-eabi
-#![feature(cmse_nonsecure_entry)]
-#![no_std]
+// needs-llvm-components: arm
+#![feature(cmse_nonsecure_entry, no_core, lang_items)]
+#![no_core]
+#[lang="sized"]
+trait Sized { }
 
 #[no_mangle]
 #[cmse_nonsecure_entry]
-pub fn entry_function(a: u32, b: u32, c: u32, d: u32) -> u32 { //~ ERROR [E0776]
-    a + b + c + d
+//~^ ERROR `#[cmse_nonsecure_entry]` requires C ABI [E0776]
+pub fn entry_function(_: u32, _: u32, _: u32, d: u32) -> u32 {
+    d
 }
diff --git a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.stderr b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.stderr
index d6967a11e6b..36d76c9674a 100644
--- a/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.stderr
+++ b/src/test/ui/cmse-nonsecure/cmse-nonsecure-entry/wrong-abi.stderr
@@ -1,5 +1,5 @@
-error[E0776]: `#[cmse_nonsecure_entry]` functions require C ABI
-  --> $DIR/wrong-abi.rs:7:1
+error[E0776]: `#[cmse_nonsecure_entry]` requires C ABI
+  --> $DIR/wrong-abi.rs:9:1
    |
 LL | #[cmse_nonsecure_entry]
    | ^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/test/ui/crate-loading/missing-std.rs b/src/test/ui/crate-loading/missing-std.rs
index 442a7c01e5a..de4ccc18560 100644
--- a/src/test/ui/crate-loading/missing-std.rs
+++ b/src/test/ui/crate-loading/missing-std.rs
@@ -1,4 +1,5 @@
 // compile-flags: --target x86_64-unknown-uefi
+// needs-llvm-components: x86
 // rustc-env:CARGO=/usr/bin/cargo
 // rustc-env:RUSTUP_HOME=/home/bors/.rustup
 #![no_core]
diff --git a/src/test/ui/crate-loading/missing-std.stderr b/src/test/ui/crate-loading/missing-std.stderr
index 25808efdfa6..e61486fdc6f 100644
--- a/src/test/ui/crate-loading/missing-std.stderr
+++ b/src/test/ui/crate-loading/missing-std.stderr
@@ -1,5 +1,5 @@
 error[E0463]: can't find crate for `core`
-  --> $DIR/missing-std.rs:5:1
+  --> $DIR/missing-std.rs:6:1
    |
 LL | extern crate core;
    | ^^^^^^^^^^^^^^^^^^ can't find crate
diff --git a/src/test/ui/sanitize/crt-static.rs b/src/test/ui/sanitize/crt-static.rs
index f5dd2a40cc4..7a6b9eda3fa 100644
--- a/src/test/ui/sanitize/crt-static.rs
+++ b/src/test/ui/sanitize/crt-static.rs
@@ -1,4 +1,5 @@
 // compile-flags: -Z sanitizer=address -C target-feature=+crt-static --target x86_64-unknown-linux-gnu
+// needs-llvm-components: x86
 
 #![feature(no_core)]
 #![no_core]
diff --git a/src/test/ui/sanitize/incompatible.rs b/src/test/ui/sanitize/incompatible.rs
index 4947f3b3d8b..bcafc2891fd 100644
--- a/src/test/ui/sanitize/incompatible.rs
+++ b/src/test/ui/sanitize/incompatible.rs
@@ -1,4 +1,5 @@
 // compile-flags: -Z sanitizer=address -Z sanitizer=memory --target x86_64-unknown-linux-gnu
+// needs-llvm-components: x86
 // error-pattern: error: `-Zsanitizer=address` is incompatible with `-Zsanitizer=memory`
 
 #![feature(no_core)]
diff --git a/src/test/ui/sanitize/unsupported-target.rs b/src/test/ui/sanitize/unsupported-target.rs
index 3fb749815f7..9f29c76353b 100644
--- a/src/test/ui/sanitize/unsupported-target.rs
+++ b/src/test/ui/sanitize/unsupported-target.rs
@@ -1,4 +1,5 @@
 // compile-flags: -Z sanitizer=leak --target i686-unknown-linux-gnu
+// needs-llvm-components: x86
 // error-pattern: error: leak sanitizer is not supported for this target
 #![feature(no_core)]
 #![no_core]
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 26c1710be74..764f2055277 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -27,8 +27,6 @@ enum ParsedNameDirective {
 /// the test.
 #[derive(Default)]
 pub struct EarlyProps {
-    pub ignore: bool,
-    pub should_fail: bool,
     pub aux: Vec<String>,
     pub aux_crate: Vec<(String, String)>,
     pub revisions: Vec<String>,
@@ -36,251 +34,22 @@ pub struct EarlyProps {
 
 impl EarlyProps {
     pub fn from_file(config: &Config, testfile: &Path) -> Self {
-        let file = File::open(testfile).unwrap();
+        let file = File::open(testfile).expect("open test file to parse earlyprops");
         Self::from_reader(config, testfile, file)
     }
 
     pub fn from_reader<R: Read>(config: &Config, testfile: &Path, rdr: R) -> Self {
         let mut props = EarlyProps::default();
-        let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some();
-        let rustc_has_sanitizer_support = env::var_os("RUSTC_SANITIZER_SUPPORT").is_some();
-        let has_asm_support = util::has_asm_support(&config.target);
-        let has_asan = util::ASAN_SUPPORTED_TARGETS.contains(&&*config.target);
-        let has_lsan = util::LSAN_SUPPORTED_TARGETS.contains(&&*config.target);
-        let has_msan = util::MSAN_SUPPORTED_TARGETS.contains(&&*config.target);
-        let has_tsan = util::TSAN_SUPPORTED_TARGETS.contains(&&*config.target);
-        let has_hwasan = util::HWASAN_SUPPORTED_TARGETS.contains(&&*config.target);
-        // for `-Z gcc-ld=lld`
-        let has_rust_lld = config
-            .compile_lib_path
-            .join("rustlib")
-            .join(&config.target)
-            .join("bin")
-            .join("gcc-ld")
-            .join(if config.host.contains("windows") { "ld.exe" } else { "ld" })
-            .exists();
-
-        iter_header(testfile, None, rdr, &mut |ln| {
-            // we should check if any only-<platform> exists and if it exists
-            // and does not matches the current platform, skip the test
-            if !props.ignore {
-                props.ignore = match config.parse_cfg_name_directive(ln, "ignore") {
-                    ParsedNameDirective::Match => true,
-                    ParsedNameDirective::NoMatch => props.ignore,
-                };
-
-                if config.has_cfg_prefix(ln, "only") {
-                    props.ignore = match config.parse_cfg_name_directive(ln, "only") {
-                        ParsedNameDirective::Match => props.ignore,
-                        ParsedNameDirective::NoMatch => true,
-                    };
-                }
-
-                if ignore_llvm(config, ln) {
-                    props.ignore = true;
-                }
-
-                if config.run_clang_based_tests_with.is_none()
-                    && config.parse_needs_matching_clang(ln)
-                {
-                    props.ignore = true;
-                }
-
-                if !has_asm_support && config.parse_name_directive(ln, "needs-asm-support") {
-                    props.ignore = true;
-                }
-
-                if !rustc_has_profiler_support && config.parse_needs_profiler_support(ln) {
-                    props.ignore = true;
-                }
-
-                if !config.run_enabled() && config.parse_name_directive(ln, "needs-run-enabled") {
-                    props.ignore = true;
-                }
-
-                if !rustc_has_sanitizer_support
-                    && config.parse_name_directive(ln, "needs-sanitizer-support")
-                {
-                    props.ignore = true;
-                }
-
-                if !has_asan && config.parse_name_directive(ln, "needs-sanitizer-address") {
-                    props.ignore = true;
-                }
-
-                if !has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak") {
-                    props.ignore = true;
-                }
-
-                if !has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory") {
-                    props.ignore = true;
-                }
-
-                if !has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread") {
-                    props.ignore = true;
-                }
-
-                if !has_hwasan && config.parse_name_directive(ln, "needs-sanitizer-hwaddress") {
-                    props.ignore = true;
-                }
-
-                if config.target_panic == PanicStrategy::Abort
-                    && config.parse_name_directive(ln, "needs-unwind")
-                {
-                    props.ignore = true;
-                }
-
-                if config.target == "wasm32-unknown-unknown" && config.parse_check_run_results(ln) {
-                    props.ignore = true;
-                }
-
-                if config.debugger == Some(Debugger::Cdb) && ignore_cdb(config, ln) {
-                    props.ignore = true;
-                }
-
-                if config.debugger == Some(Debugger::Gdb) && ignore_gdb(config, ln) {
-                    props.ignore = true;
-                }
-
-                if config.debugger == Some(Debugger::Lldb) && ignore_lldb(config, ln) {
-                    props.ignore = true;
-                }
-
-                if !has_rust_lld && config.parse_name_directive(ln, "needs-rust-lld") {
-                    props.ignore = true;
-                }
-            }
-
+        iter_header(testfile, rdr, &mut |_, ln| {
             if let Some(s) = config.parse_aux_build(ln) {
                 props.aux.push(s);
             }
-
             if let Some(ac) = config.parse_aux_crate(ln) {
                 props.aux_crate.push(ac);
             }
-
             config.parse_and_update_revisions(ln, &mut props.revisions);
-
-            props.should_fail = props.should_fail || config.parse_name_directive(ln, "should-fail");
         });
-
         return props;
-
-        fn ignore_cdb(config: &Config, line: &str) -> bool {
-            if let Some(actual_version) = config.cdb_version {
-                if let Some(min_version) = line.strip_prefix("min-cdb-version:").map(str::trim) {
-                    let min_version = extract_cdb_version(min_version).unwrap_or_else(|| {
-                        panic!("couldn't parse version range: {:?}", min_version);
-                    });
-
-                    // Ignore if actual version is smaller than the minimum
-                    // required version
-                    return actual_version < min_version;
-                }
-            }
-            false
-        }
-
-        fn ignore_gdb(config: &Config, line: &str) -> bool {
-            if let Some(actual_version) = config.gdb_version {
-                if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
-                    let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
-                        .unwrap_or_else(|| {
-                            panic!("couldn't parse version range: {:?}", rest);
-                        });
-
-                    if start_ver != end_ver {
-                        panic!("Expected single GDB version")
-                    }
-                    // Ignore if actual version is smaller than the minimum
-                    // required version
-                    return actual_version < start_ver;
-                } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
-                    let (min_version, max_version) =
-                        extract_version_range(rest, extract_gdb_version).unwrap_or_else(|| {
-                            panic!("couldn't parse version range: {:?}", rest);
-                        });
-
-                    if max_version < min_version {
-                        panic!("Malformed GDB version range: max < min")
-                    }
-
-                    return actual_version >= min_version && actual_version <= max_version;
-                }
-            }
-            false
-        }
-
-        fn ignore_lldb(config: &Config, line: &str) -> bool {
-            if let Some(actual_version) = config.lldb_version {
-                if let Some(min_version) = line.strip_prefix("min-lldb-version:").map(str::trim) {
-                    let min_version = min_version.parse().unwrap_or_else(|e| {
-                        panic!(
-                            "Unexpected format of LLDB version string: {}\n{:?}",
-                            min_version, e
-                        );
-                    });
-                    // Ignore if actual version is smaller the minimum required
-                    // version
-                    actual_version < min_version
-                } else {
-                    line.starts_with("rust-lldb") && !config.lldb_native_rust
-                }
-            } else {
-                false
-            }
-        }
-
-        fn ignore_llvm(config: &Config, line: &str) -> bool {
-            if config.system_llvm && line.starts_with("no-system-llvm") {
-                return true;
-            }
-            if let Some(needed_components) =
-                config.parse_name_value_directive(line, "needs-llvm-components")
-            {
-                let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
-                if let Some(missing_component) = needed_components
-                    .split_whitespace()
-                    .find(|needed_component| !components.contains(needed_component))
-                {
-                    if env::var_os("COMPILETEST_NEEDS_ALL_LLVM_COMPONENTS").is_some() {
-                        panic!("missing LLVM component: {}", missing_component);
-                    }
-                    return true;
-                }
-            }
-            if let Some(actual_version) = config.llvm_version {
-                if let Some(rest) = line.strip_prefix("min-llvm-version:").map(str::trim) {
-                    let min_version = extract_llvm_version(rest).unwrap();
-                    // Ignore if actual version is smaller the minimum required
-                    // version
-                    actual_version < min_version
-                } else if let Some(rest) =
-                    line.strip_prefix("min-system-llvm-version:").map(str::trim)
-                {
-                    let min_version = extract_llvm_version(rest).unwrap();
-                    // Ignore if using system LLVM and actual version
-                    // is smaller the minimum required version
-                    config.system_llvm && actual_version < min_version
-                } else if let Some(rest) = line.strip_prefix("ignore-llvm-version:").map(str::trim)
-                {
-                    // Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
-                    let (v_min, v_max) = extract_version_range(rest, extract_llvm_version)
-                        .unwrap_or_else(|| {
-                            panic!("couldn't parse version range: {:?}", rest);
-                        });
-                    if v_max < v_min {
-                        panic!("Malformed LLVM version range: max < min")
-                    }
-                    // Ignore if version lies inside of range.
-                    actual_version >= v_min && actual_version <= v_max
-                } else {
-                    false
-                }
-            } else {
-                false
-            }
-        }
     }
 }
 
@@ -440,7 +209,11 @@ impl TestProps {
         if !testfile.is_dir() {
             let file = File::open(testfile).unwrap();
 
-            iter_header(testfile, cfg, file, &mut |ln| {
+            iter_header(testfile, file, &mut |revision, ln| {
+                if revision.is_some() && revision != cfg {
+                    return;
+                }
+
                 if let Some(ep) = config.parse_error_pattern(ln) {
                     self.error_patterns.push(ep);
                 }
@@ -672,12 +445,12 @@ impl TestProps {
     }
 }
 
-fn iter_header<R: Read>(testfile: &Path, cfg: Option<&str>, rdr: R, it: &mut dyn FnMut(&str)) {
+fn iter_header<R: Read>(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str)) {
     if testfile.is_dir() {
         return;
     }
 
-    let comment = if testfile.to_string_lossy().ends_with(".rs") { "//" } else { "#" };
+    let comment = if testfile.extension().map(|e| e == "rs") == Some(true) { "//" } else { "#" };
 
     let mut rdr = BufReader::new(rdr);
     let mut ln = String::new();
@@ -699,18 +472,12 @@ fn iter_header<R: Read>(testfile: &Path, cfg: Option<&str>, rdr: R, it: &mut dyn
             if let Some(close_brace) = ln.find(']') {
                 let open_brace = ln.find('[').unwrap();
                 let lncfg = &ln[open_brace + 1..close_brace];
-                let matches = match cfg {
-                    Some(s) => s == &lncfg[..],
-                    None => false,
-                };
-                if matches {
-                    it(ln[(close_brace + 1)..].trim_start());
-                }
+                it(Some(lncfg), ln[(close_brace + 1)..].trim_start());
             } else {
                 panic!("malformed condition directive: expected `{}[foo]`, found `{}`", comment, ln)
             }
         } else if ln.starts_with(comment) {
-            it(ln[comment.len()..].trim_start());
+            it(None, ln[comment.len()..].trim_start());
         }
     }
 }
@@ -1026,11 +793,12 @@ pub fn extract_llvm_version(version: &str) -> Option<u32> {
     Some(version)
 }
 
-// Takes a directive of the form "<version1> [- <version2>]",
-// returns the numeric representation of <version1> and <version2> as
-// tuple: (<version1> as u32, <version2> as u32)
-// If the <version2> part is omitted, the second component of the tuple
-// is the same as <version1>.
+/// Takes a directive of the form "<version1> [- <version2>]",
+/// returns the numeric representation of <version1> and <version2> as
+/// tuple: (<version1> as u32, <version2> as u32)
+///
+/// If the <version2> part is omitted, the second component of the tuple
+/// is the same as <version1>.
 fn extract_version_range<F>(line: &str, parse: F) -> Option<(u32, u32)>
 where
     F: Fn(&str) -> Option<u32>,
@@ -1056,3 +824,199 @@ where
 
     Some((min, max))
 }
+
+pub fn make_test_description<R: Read>(
+    config: &Config,
+    name: test::TestName,
+    path: &Path,
+    src: R,
+    cfg: Option<&str>,
+) -> test::TestDesc {
+    let mut ignore = false;
+    let mut should_fail = false;
+
+    let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some();
+    let rustc_has_sanitizer_support = env::var_os("RUSTC_SANITIZER_SUPPORT").is_some();
+    let has_asm_support = util::has_asm_support(&config.target);
+    let has_asan = util::ASAN_SUPPORTED_TARGETS.contains(&&*config.target);
+    let has_lsan = util::LSAN_SUPPORTED_TARGETS.contains(&&*config.target);
+    let has_msan = util::MSAN_SUPPORTED_TARGETS.contains(&&*config.target);
+    let has_tsan = util::TSAN_SUPPORTED_TARGETS.contains(&&*config.target);
+    let has_hwasan = util::HWASAN_SUPPORTED_TARGETS.contains(&&*config.target);
+    // for `-Z gcc-ld=lld`
+    let has_rust_lld = config
+        .compile_lib_path
+        .join("rustlib")
+        .join(&config.target)
+        .join("bin")
+        .join("gcc-ld")
+        .join(if config.host.contains("windows") { "ld.exe" } else { "ld" })
+        .exists();
+    iter_header(path, src, &mut |revision, ln| {
+        if revision.is_some() && revision != cfg {
+            return;
+        }
+        ignore = match config.parse_cfg_name_directive(ln, "ignore") {
+            ParsedNameDirective::Match => true,
+            ParsedNameDirective::NoMatch => ignore,
+        };
+        if config.has_cfg_prefix(ln, "only") {
+            ignore = match config.parse_cfg_name_directive(ln, "only") {
+                ParsedNameDirective::Match => ignore,
+                ParsedNameDirective::NoMatch => true,
+            };
+        }
+        ignore |= ignore_llvm(config, ln);
+        ignore |=
+            config.run_clang_based_tests_with.is_none() && config.parse_needs_matching_clang(ln);
+        ignore |= !has_asm_support && config.parse_name_directive(ln, "needs-asm-support");
+        ignore |= !rustc_has_profiler_support && config.parse_needs_profiler_support(ln);
+        ignore |= !config.run_enabled() && config.parse_name_directive(ln, "needs-run-enabled");
+        ignore |= !rustc_has_sanitizer_support
+            && config.parse_name_directive(ln, "needs-sanitizer-support");
+        ignore |= !has_asan && config.parse_name_directive(ln, "needs-sanitizer-address");
+        ignore |= !has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak");
+        ignore |= !has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory");
+        ignore |= !has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread");
+        ignore |= !has_hwasan && config.parse_name_directive(ln, "needs-sanitizer-hwaddress");
+        ignore |= config.target_panic == PanicStrategy::Abort
+            && config.parse_name_directive(ln, "needs-unwind");
+        ignore |= config.target == "wasm32-unknown-unknown" && config.parse_check_run_results(ln);
+        ignore |= config.debugger == Some(Debugger::Cdb) && ignore_cdb(config, ln);
+        ignore |= config.debugger == Some(Debugger::Gdb) && ignore_gdb(config, ln);
+        ignore |= config.debugger == Some(Debugger::Lldb) && ignore_lldb(config, ln);
+        ignore |= !has_rust_lld && config.parse_name_directive(ln, "needs-rust-lld");
+        should_fail |= config.parse_name_directive(ln, "should-fail");
+    });
+
+    // The `should-fail` annotation doesn't apply to pretty tests,
+    // since we run the pretty printer across all tests by default.
+    // If desired, we could add a `should-fail-pretty` annotation.
+    let should_panic = match config.mode {
+        crate::common::Pretty => test::ShouldPanic::No,
+        _ if should_fail => test::ShouldPanic::Yes,
+        _ => test::ShouldPanic::No,
+    };
+
+    test::TestDesc {
+        name,
+        ignore,
+        should_panic,
+        allow_fail: false,
+        #[cfg(not(bootstrap))]
+        compile_fail: false,
+        #[cfg(not(bootstrap))]
+        no_run: false,
+        test_type: test::TestType::Unknown,
+    }
+}
+
+fn ignore_cdb(config: &Config, line: &str) -> bool {
+    if let Some(actual_version) = config.cdb_version {
+        if let Some(min_version) = line.strip_prefix("min-cdb-version:").map(str::trim) {
+            let min_version = extract_cdb_version(min_version).unwrap_or_else(|| {
+                panic!("couldn't parse version range: {:?}", min_version);
+            });
+
+            // Ignore if actual version is smaller than the minimum
+            // required version
+            return actual_version < min_version;
+        }
+    }
+    false
+}
+
+fn ignore_gdb(config: &Config, line: &str) -> bool {
+    if let Some(actual_version) = config.gdb_version {
+        if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
+            let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
+                .unwrap_or_else(|| {
+                    panic!("couldn't parse version range: {:?}", rest);
+                });
+
+            if start_ver != end_ver {
+                panic!("Expected single GDB version")
+            }
+            // Ignore if actual version is smaller than the minimum
+            // required version
+            return actual_version < start_ver;
+        } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
+            let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
+                .unwrap_or_else(|| {
+                    panic!("couldn't parse version range: {:?}", rest);
+                });
+
+            if max_version < min_version {
+                panic!("Malformed GDB version range: max < min")
+            }
+
+            return actual_version >= min_version && actual_version <= max_version;
+        }
+    }
+    false
+}
+
+fn ignore_lldb(config: &Config, line: &str) -> bool {
+    if let Some(actual_version) = config.lldb_version {
+        if let Some(min_version) = line.strip_prefix("min-lldb-version:").map(str::trim) {
+            let min_version = min_version.parse().unwrap_or_else(|e| {
+                panic!("Unexpected format of LLDB version string: {}\n{:?}", min_version, e);
+            });
+            // Ignore if actual version is smaller the minimum required
+            // version
+            actual_version < min_version
+        } else {
+            line.starts_with("rust-lldb") && !config.lldb_native_rust
+        }
+    } else {
+        false
+    }
+}
+
+fn ignore_llvm(config: &Config, line: &str) -> bool {
+    if config.system_llvm && line.starts_with("no-system-llvm") {
+        return true;
+    }
+    if let Some(needed_components) =
+        config.parse_name_value_directive(line, "needs-llvm-components")
+    {
+        let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
+        if let Some(missing_component) = needed_components
+            .split_whitespace()
+            .find(|needed_component| !components.contains(needed_component))
+        {
+            if env::var_os("COMPILETEST_NEEDS_ALL_LLVM_COMPONENTS").is_some() {
+                panic!("missing LLVM component: {}", missing_component);
+            }
+            return true;
+        }
+    }
+    if let Some(actual_version) = config.llvm_version {
+        if let Some(rest) = line.strip_prefix("min-llvm-version:").map(str::trim) {
+            let min_version = extract_llvm_version(rest).unwrap();
+            // Ignore if actual version is smaller the minimum required
+            // version
+            actual_version < min_version
+        } else if let Some(rest) = line.strip_prefix("min-system-llvm-version:").map(str::trim) {
+            let min_version = extract_llvm_version(rest).unwrap();
+            // Ignore if using system LLVM and actual version
+            // is smaller the minimum required version
+            config.system_llvm && actual_version < min_version
+        } else if let Some(rest) = line.strip_prefix("ignore-llvm-version:").map(str::trim) {
+            // Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
+            let (v_min, v_max) =
+                extract_version_range(rest, extract_llvm_version).unwrap_or_else(|| {
+                    panic!("couldn't parse version range: {:?}", rest);
+                });
+            if v_max < v_min {
+                panic!("Malformed LLVM version range: max < min")
+            }
+            // Ignore if version lies inside of range.
+            actual_version >= v_min && actual_version <= v_max
+        } else {
+            false
+        }
+    } else {
+        false
+    }
+}
diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs
index 2c607b6a50e..2485dbadab5 100644
--- a/src/tools/compiletest/src/header/tests.rs
+++ b/src/tools/compiletest/src/header/tests.rs
@@ -1,7 +1,7 @@
 use std::path::Path;
 
 use crate::common::{Config, Debugger};
-use crate::header::{parse_normalization_string, EarlyProps};
+use crate::header::{make_test_description, parse_normalization_string, EarlyProps};
 
 #[test]
 fn test_parse_normalization_string() {
@@ -66,6 +66,13 @@ fn parse_rs(config: &Config, contents: &str) -> EarlyProps {
     EarlyProps::from_reader(config, Path::new("a.rs"), bytes)
 }
 
+fn check_ignore(config: &Config, contents: &str) -> bool {
+    let tn = test::DynTestName(String::new());
+    let p = Path::new("a.rs");
+    let d = make_test_description(&config, tn, p, std::io::Cursor::new(contents), None);
+    d.ignore
+}
+
 fn parse_makefile(config: &Config, contents: &str) -> EarlyProps {
     let bytes = contents.as_bytes();
     EarlyProps::from_reader(config, Path::new("Makefile"), bytes)
@@ -74,9 +81,13 @@ fn parse_makefile(config: &Config, contents: &str) -> EarlyProps {
 #[test]
 fn should_fail() {
     let config = config();
+    let tn = test::DynTestName(String::new());
+    let p = Path::new("a.rs");
 
-    assert!(!parse_rs(&config, "").should_fail);
-    assert!(parse_rs(&config, "// should-fail").should_fail);
+    let d = make_test_description(&config, tn.clone(), p, std::io::Cursor::new(""), None);
+    assert_eq!(d.should_panic, test::ShouldPanic::No);
+    let d = make_test_description(&config, tn, p, std::io::Cursor::new("// should-fail"), None);
+    assert_eq!(d.should_panic, test::ShouldPanic::Yes);
 }
 
 #[test]
@@ -112,10 +123,10 @@ fn no_system_llvm() {
     let mut config = config();
 
     config.system_llvm = false;
-    assert!(!parse_rs(&config, "// no-system-llvm").ignore);
+    assert!(!check_ignore(&config, "// no-system-llvm"));
 
     config.system_llvm = true;
-    assert!(parse_rs(&config, "// no-system-llvm").ignore);
+    assert!(check_ignore(&config, "// no-system-llvm"));
 }
 
 #[test]
@@ -123,16 +134,16 @@ fn llvm_version() {
     let mut config = config();
 
     config.llvm_version = Some(80102);
-    assert!(parse_rs(&config, "// min-llvm-version: 9.0").ignore);
+    assert!(check_ignore(&config, "// min-llvm-version: 9.0"));
 
     config.llvm_version = Some(90001);
-    assert!(parse_rs(&config, "// min-llvm-version: 9.2").ignore);
+    assert!(check_ignore(&config, "// min-llvm-version: 9.2"));
 
     config.llvm_version = Some(90301);
-    assert!(!parse_rs(&config, "// min-llvm-version: 9.2").ignore);
+    assert!(!check_ignore(&config, "// min-llvm-version: 9.2"));
 
     config.llvm_version = Some(100000);
-    assert!(!parse_rs(&config, "// min-llvm-version: 9.0").ignore);
+    assert!(!check_ignore(&config, "// min-llvm-version: 9.0"));
 }
 
 #[test]
@@ -140,16 +151,16 @@ fn ignore_target() {
     let mut config = config();
     config.target = "x86_64-unknown-linux-gnu".to_owned();
 
-    assert!(parse_rs(&config, "// ignore-x86_64-unknown-linux-gnu").ignore);
-    assert!(parse_rs(&config, "// ignore-x86_64").ignore);
-    assert!(parse_rs(&config, "// ignore-linux").ignore);
-    assert!(parse_rs(&config, "// ignore-gnu").ignore);
-    assert!(parse_rs(&config, "// ignore-64bit").ignore);
+    assert!(check_ignore(&config, "// ignore-x86_64-unknown-linux-gnu"));
+    assert!(check_ignore(&config, "// ignore-x86_64"));
+    assert!(check_ignore(&config, "// ignore-linux"));
+    assert!(check_ignore(&config, "// ignore-gnu"));
+    assert!(check_ignore(&config, "// ignore-64bit"));
 
-    assert!(!parse_rs(&config, "// ignore-i686").ignore);
-    assert!(!parse_rs(&config, "// ignore-windows").ignore);
-    assert!(!parse_rs(&config, "// ignore-msvc").ignore);
-    assert!(!parse_rs(&config, "// ignore-32bit").ignore);
+    assert!(!check_ignore(&config, "// ignore-i686"));
+    assert!(!check_ignore(&config, "// ignore-windows"));
+    assert!(!check_ignore(&config, "// ignore-msvc"));
+    assert!(!check_ignore(&config, "// ignore-32bit"));
 }
 
 #[test]
@@ -157,16 +168,16 @@ fn only_target() {
     let mut config = config();
     config.target = "x86_64-pc-windows-gnu".to_owned();
 
-    assert!(parse_rs(&config, "// only-i686").ignore);
-    assert!(parse_rs(&config, "// only-linux").ignore);
-    assert!(parse_rs(&config, "// only-msvc").ignore);
-    assert!(parse_rs(&config, "// only-32bit").ignore);
+    assert!(check_ignore(&config, "// only-i686"));
+    assert!(check_ignore(&config, "// only-linux"));
+    assert!(check_ignore(&config, "// only-msvc"));
+    assert!(check_ignore(&config, "// only-32bit"));
 
-    assert!(!parse_rs(&config, "// only-x86_64-pc-windows-gnu").ignore);
-    assert!(!parse_rs(&config, "// only-x86_64").ignore);
-    assert!(!parse_rs(&config, "// only-windows").ignore);
-    assert!(!parse_rs(&config, "// only-gnu").ignore);
-    assert!(!parse_rs(&config, "// only-64bit").ignore);
+    assert!(!check_ignore(&config, "// only-x86_64-pc-windows-gnu"));
+    assert!(!check_ignore(&config, "// only-x86_64"));
+    assert!(!check_ignore(&config, "// only-windows"));
+    assert!(!check_ignore(&config, "// only-gnu"));
+    assert!(!check_ignore(&config, "// only-64bit"));
 }
 
 #[test]
@@ -174,8 +185,8 @@ fn stage() {
     let mut config = config();
     config.stage_id = "stage1".to_owned();
 
-    assert!(parse_rs(&config, "// ignore-stage1").ignore);
-    assert!(!parse_rs(&config, "// ignore-stage2").ignore);
+    assert!(check_ignore(&config, "// ignore-stage1"));
+    assert!(!check_ignore(&config, "// ignore-stage2"));
 }
 
 #[test]
@@ -183,26 +194,26 @@ fn cross_compile() {
     let mut config = config();
     config.host = "x86_64-apple-darwin".to_owned();
     config.target = "wasm32-unknown-unknown".to_owned();
-    assert!(parse_rs(&config, "// ignore-cross-compile").ignore);
+    assert!(check_ignore(&config, "// ignore-cross-compile"));
 
     config.target = config.host.clone();
-    assert!(!parse_rs(&config, "// ignore-cross-compile").ignore);
+    assert!(!check_ignore(&config, "// ignore-cross-compile"));
 }
 
 #[test]
 fn debugger() {
     let mut config = config();
     config.debugger = None;
-    assert!(!parse_rs(&config, "// ignore-cdb").ignore);
+    assert!(!check_ignore(&config, "// ignore-cdb"));
 
     config.debugger = Some(Debugger::Cdb);
-    assert!(parse_rs(&config, "// ignore-cdb").ignore);
+    assert!(check_ignore(&config, "// ignore-cdb"));
 
     config.debugger = Some(Debugger::Gdb);
-    assert!(parse_rs(&config, "// ignore-gdb").ignore);
+    assert!(check_ignore(&config, "// ignore-gdb"));
 
     config.debugger = Some(Debugger::Lldb);
-    assert!(parse_rs(&config, "// ignore-lldb").ignore);
+    assert!(check_ignore(&config, "// ignore-lldb"));
 }
 
 #[test]
@@ -211,17 +222,17 @@ fn sanitizers() {
 
     // Target that supports all sanitizers:
     config.target = "x86_64-unknown-linux-gnu".to_owned();
-    assert!(!parse_rs(&config, "// needs-sanitizer-address").ignore);
-    assert!(!parse_rs(&config, "// needs-sanitizer-leak").ignore);
-    assert!(!parse_rs(&config, "// needs-sanitizer-memory").ignore);
-    assert!(!parse_rs(&config, "// needs-sanitizer-thread").ignore);
+    assert!(!check_ignore(&config, "// needs-sanitizer-address"));
+    assert!(!check_ignore(&config, "// needs-sanitizer-leak"));
+    assert!(!check_ignore(&config, "// needs-sanitizer-memory"));
+    assert!(!check_ignore(&config, "// needs-sanitizer-thread"));
 
     // Target that doesn't support sanitizers:
     config.target = "wasm32-unknown-emscripten".to_owned();
-    assert!(parse_rs(&config, "// needs-sanitizer-address").ignore);
-    assert!(parse_rs(&config, "// needs-sanitizer-leak").ignore);
-    assert!(parse_rs(&config, "// needs-sanitizer-memory").ignore);
-    assert!(parse_rs(&config, "// needs-sanitizer-thread").ignore);
+    assert!(check_ignore(&config, "// needs-sanitizer-address"));
+    assert!(check_ignore(&config, "// needs-sanitizer-leak"));
+    assert!(check_ignore(&config, "// needs-sanitizer-memory"));
+    assert!(check_ignore(&config, "// needs-sanitizer-thread"));
 }
 
 #[test]
@@ -229,10 +240,10 @@ fn asm_support() {
     let mut config = config();
 
     config.target = "avr-unknown-gnu-atmega328".to_owned();
-    assert!(parse_rs(&config, "// needs-asm-support").ignore);
+    assert!(check_ignore(&config, "// needs-asm-support"));
 
     config.target = "i686-unknown-netbsd".to_owned();
-    assert!(!parse_rs(&config, "// needs-asm-support").ignore);
+    assert!(!check_ignore(&config, "// needs-asm-support"));
 }
 
 #[test]
@@ -240,13 +251,13 @@ fn channel() {
     let mut config = config();
     config.channel = "beta".into();
 
-    assert!(parse_rs(&config, "// ignore-beta").ignore);
-    assert!(parse_rs(&config, "// only-nightly").ignore);
-    assert!(parse_rs(&config, "// only-stable").ignore);
+    assert!(check_ignore(&config, "// ignore-beta"));
+    assert!(check_ignore(&config, "// only-nightly"));
+    assert!(check_ignore(&config, "// only-stable"));
 
-    assert!(!parse_rs(&config, "// only-beta").ignore);
-    assert!(!parse_rs(&config, "// ignore-nightly").ignore);
-    assert!(!parse_rs(&config, "// ignore-stable").ignore);
+    assert!(!check_ignore(&config, "// only-beta"));
+    assert!(!check_ignore(&config, "// ignore-nightly"));
+    assert!(!check_ignore(&config, "// ignore-stable"));
 }
 
 #[test]
diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs
index c854663706a..46432d5e4f5 100644
--- a/src/tools/compiletest/src/main.rs
+++ b/src/tools/compiletest/src/main.rs
@@ -8,7 +8,7 @@ extern crate test;
 use crate::common::{
     expected_output_path, output_base_dir, output_relative_path, PanicStrategy, UI_EXTENSIONS,
 };
-use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, Pretty, TestPaths};
+use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths};
 use crate::util::logv;
 use getopts::Options;
 use std::env;
@@ -22,7 +22,7 @@ use test::ColorConfig;
 use tracing::*;
 use walkdir::WalkDir;
 
-use self::header::EarlyProps;
+use self::header::{make_test_description, EarlyProps};
 
 #[cfg(test)]
 mod tests;
@@ -620,26 +620,13 @@ pub fn is_test(file_name: &OsString) -> bool {
 }
 
 fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test::TestDescAndFn> {
-    let early_props = if config.mode == Mode::RunMake {
-        // Allow `ignore` directives to be in the Makefile.
-        EarlyProps::from_file(config, &testpaths.file.join("Makefile"))
+    let test_path = if config.mode == Mode::RunMake {
+        // Parse directives in the Makefile
+        testpaths.file.join("Makefile")
     } else {
-        EarlyProps::from_file(config, &testpaths.file)
-    };
-
-    // The `should-fail` annotation doesn't apply to pretty tests,
-    // since we run the pretty printer across all tests by default.
-    // If desired, we could add a `should-fail-pretty` annotation.
-    let should_panic = match config.mode {
-        Pretty => test::ShouldPanic::No,
-        _ => {
-            if early_props.should_fail {
-                test::ShouldPanic::Yes
-            } else {
-                test::ShouldPanic::No
-            }
-        }
+        PathBuf::from(&testpaths.file)
     };
+    let early_props = EarlyProps::from_file(config, &test_path);
 
     // Incremental tests are special, they inherently cannot be run in parallel.
     // `runtest::run` will be responsible for iterating over revisions.
@@ -651,29 +638,20 @@ fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> Vec<test
     revisions
         .into_iter()
         .map(|revision| {
-            let ignore = early_props.ignore
-                // Ignore tests that already run and are up to date with respect to inputs.
-                || is_up_to_date(
-                    config,
-                    testpaths,
-                    &early_props,
-                    revision.map(|s| s.as_str()),
-                    inputs,
-                );
-            test::TestDescAndFn {
-                desc: test::TestDesc {
-                    name: make_test_name(config, testpaths, revision),
-                    ignore,
-                    should_panic,
-                    allow_fail: false,
-                    #[cfg(not(bootstrap))]
-                    compile_fail: false,
-                    #[cfg(not(bootstrap))]
-                    no_run: false,
-                    test_type: test::TestType::Unknown,
-                },
-                testfn: make_test_closure(config, testpaths, revision),
-            }
+            let src_file =
+                std::fs::File::open(&test_path).expect("open test file to parse ignores");
+            let cfg = revision.map(|v| &**v);
+            let test_name = crate::make_test_name(config, testpaths, revision);
+            let mut desc = make_test_description(config, test_name, &test_path, src_file, cfg);
+            // Ignore tests that already run and are up to date with respect to inputs.
+            desc.ignore |= is_up_to_date(
+                config,
+                testpaths,
+                &early_props,
+                revision.map(|s| s.as_str()),
+                inputs,
+            );
+            test::TestDescAndFn { desc, testfn: make_test_closure(config, testpaths, revision) }
         })
         .collect()
 }
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
index fcb27dae9ea..a1c41eb9981 100644
--- a/src/tools/tidy/src/lib.rs
+++ b/src/tools/tidy/src/lib.rs
@@ -47,6 +47,7 @@ pub mod extdeps;
 pub mod features;
 pub mod pal;
 pub mod style;
+pub mod target_specific_tests;
 pub mod ui_tests;
 pub mod unit_tests;
 pub mod unstable_book;
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 10356a2fdc5..440c352ea53 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -55,6 +55,8 @@ fn main() {
             }
         }
 
+        check!(target_specific_tests, &src_path);
+
         // Checks that are done on the cargo workspace.
         check!(deps, &root_path, &cargo);
         check!(extdeps, &root_path);
diff --git a/src/tools/tidy/src/target_specific_tests.rs b/src/tools/tidy/src/target_specific_tests.rs
new file mode 100644
index 00000000000..8e1749196d2
--- /dev/null
+++ b/src/tools/tidy/src/target_specific_tests.rs
@@ -0,0 +1,96 @@
+//! Tidy check to ensure that all target specific tests (those that require a `--target` flag)
+//! also require the pre-requisite LLVM components to run.
+
+use std::collections::BTreeMap;
+use std::path::Path;
+
+const COMMENT: &str = "//";
+const LLVM_COMPONENTS_HEADER: &str = "needs-llvm-components:";
+const COMPILE_FLAGS_HEADER: &str = "compile-flags:";
+
+/// Iterate through compiletest headers in a test contents.
+///
+/// Adjusted from compiletest/src/header.rs.
+fn iter_header<'a>(contents: &'a str, it: &mut dyn FnMut(Option<&'a str>, &'a str)) {
+    for ln in contents.lines() {
+        let ln = ln.trim();
+        if ln.starts_with(COMMENT) && ln[COMMENT.len()..].trim_start().starts_with('[') {
+            if let Some(close_brace) = ln.find(']') {
+                let open_brace = ln.find('[').unwrap();
+                let lncfg = &ln[open_brace + 1..close_brace];
+                it(Some(lncfg), ln[(close_brace + 1)..].trim_start());
+            } else {
+                panic!("malformed condition directive: expected `//[foo]`, found `{}`", ln)
+            }
+        } else if ln.starts_with(COMMENT) {
+            it(None, ln[COMMENT.len()..].trim_start());
+        }
+    }
+}
+
+#[derive(Default, Debug)]
+struct RevisionInfo<'a> {
+    target_arch: Option<&'a str>,
+    llvm_components: Option<Vec<&'a str>>,
+}
+
+pub fn check(path: &Path, bad: &mut bool) {
+    let tests = path.join("test");
+    super::walk(
+        &tests,
+        &mut |path| path.extension().map(|p| p == "rs") == Some(false),
+        &mut |entry, content| {
+            let file = entry.path().display();
+            let mut header_map = BTreeMap::new();
+            iter_header(content, &mut |cfg, directive| {
+                if let Some(value) = directive.strip_prefix(LLVM_COMPONENTS_HEADER) {
+                    let info = header_map.entry(cfg).or_insert(RevisionInfo::default());
+                    let comp_vec = info.llvm_components.get_or_insert(Vec::new());
+                    for component in value.split(' ') {
+                        let component = component.trim();
+                        if !component.is_empty() {
+                            comp_vec.push(component);
+                        }
+                    }
+                } else if directive.starts_with(COMPILE_FLAGS_HEADER) {
+                    let compile_flags = &directive[COMPILE_FLAGS_HEADER.len()..];
+                    if let Some((_, v)) = compile_flags.split_once("--target") {
+                        if let Some((arch, _)) =
+                            v.trim_start_matches(|c| c == ' ' || c == '=').split_once("-")
+                        {
+                            let info = header_map.entry(cfg).or_insert(RevisionInfo::default());
+                            info.target_arch.replace(arch);
+                        } else {
+                            eprintln!("{}: seems to have a malformed --target value", file);
+                            *bad = true;
+                        }
+                    }
+                }
+            });
+            for (rev, RevisionInfo { target_arch, llvm_components }) in &header_map {
+                let rev = rev.unwrap_or("[unspecified]");
+                match (target_arch, llvm_components) {
+                    (None, None) => {}
+                    (Some(_), None) => {
+                        eprintln!(
+                            "{}: revision {} should specify `{}` as it has `--target` set",
+                            file, rev, LLVM_COMPONENTS_HEADER
+                        );
+                        *bad = true;
+                    }
+                    (None, Some(_)) => {
+                        eprintln!(
+                            "{}: revision {} should not specify `{}` as it doesn't need `--target`",
+                            file, rev, LLVM_COMPONENTS_HEADER
+                        );
+                        *bad = true;
+                    }
+                    (Some(_), Some(_)) => {
+                        // FIXME: check specified components against the target architectures we
+                        // gathered.
+                    }
+                }
+            }
+        },
+    );
+}