Rollup merge of #120656 - Zalathar:filecheck-flags, r=wesleywiser

Allow tests to specify a `//@ filecheck-flags:` header

This allows individual codegen/assembly/mir-opt tests to pass extra flags to the LLVM `filecheck` tool as needed.

---

The original motivation was noticing that `tests/run-make/instrument-coverage` was very close to being an ordinary codegen test, except that it needs some extra logic to set up platform-specific variables to be passed into filecheck.

I then saw the comment in `verify_with_filecheck` indicating that a `filecheck-flags` header might be useful for other purposes as well.
This commit is contained in:
Guillaume Gomez 2024-02-26 10:27:41 +01:00 committed by GitHub
commit 0e08be5360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 190 additions and 168 deletions

View file

@ -197,6 +197,8 @@ pub struct TestProps {
/// Extra flags to pass to `llvm-cov` when producing coverage reports.
/// Only used by the "coverage-run" test mode.
pub llvm_cov_flags: Vec<String>,
/// Extra flags to pass to LLVM's `filecheck` tool, in tests that use it.
pub filecheck_flags: Vec<String>,
}
mod directives {
@ -236,6 +238,7 @@ mod directives {
pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
pub const COMPARE_OUTPUT_LINES_BY_SUBSET: &'static str = "compare-output-lines-by-subset";
pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags";
pub const FILECHECK_FLAGS: &'static str = "filecheck-flags";
// This isn't a real directive, just one that is probably mistyped often
pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags";
}
@ -286,6 +289,7 @@ impl TestProps {
mir_unit_test: None,
remap_src_base: false,
llvm_cov_flags: vec![],
filecheck_flags: vec![],
}
}
@ -542,6 +546,10 @@ impl TestProps {
if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) {
self.llvm_cov_flags.extend(split_flags(&flags));
}
if let Some(flags) = config.parse_name_value_directive(ln, FILECHECK_FLAGS) {
self.filecheck_flags.extend(split_flags(&flags));
}
},
);

View file

@ -2909,25 +2909,32 @@ impl<'test> TestCx<'test> {
fn verify_with_filecheck(&self, output: &Path) -> ProcRes {
let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
// It would be more appropriate to make most of the arguments configurable through
// a comment-attribute similar to `compile-flags`. For example, --check-prefixes is a very
// useful flag.
//
// For now, though…
let prefix_for_target =
if self.config.target.contains("msvc") { "MSVC" } else { "NONMSVC" };
let prefixes = if let Some(rev) = self.revision {
format!("CHECK,{},{}", prefix_for_target, rev)
} else {
format!("CHECK,{}", prefix_for_target)
};
if self.config.llvm_version.unwrap_or(0) >= 130000 {
filecheck.args(&["--allow-unused-prefixes", "--check-prefixes", &prefixes]);
} else {
filecheck.args(&["--check-prefixes", &prefixes]);
// FIXME: Consider making some of these prefix flags opt-in per test,
// via `filecheck-flags` or by adding new header directives.
// Because we use custom prefixes, we also have to register the default prefix.
filecheck.arg("--check-prefix=CHECK");
// Some tests use the current revision name as a check prefix.
if let Some(rev) = self.revision {
filecheck.arg("--check-prefix").arg(rev);
}
// Some tests also expect either the MSVC or NONMSVC prefix to be defined.
let msvc_or_not = if self.config.target.contains("msvc") { "MSVC" } else { "NONMSVC" };
filecheck.arg("--check-prefix").arg(msvc_or_not);
// The filecheck tool normally fails if a prefix is defined but not used.
// However, we define several prefixes globally for all tests.
filecheck.arg("--allow-unused-prefixes");
// Provide more context on failures.
filecheck.args(&["--dump-input-context", "100"]);
// Add custom flags supplied by the `filecheck-flags:` test header.
filecheck.args(&self.props.filecheck_flags);
self.compose_and_run(filecheck, "", None, None)
}

View file

@ -0,0 +1,120 @@
//@ edition: 2021
//@ needs-profiler-support
//@ compile-flags: -Cinstrument-coverage -Copt-level=0
//@ revisions: LINUX DARWIN WINDOWS
//@ [LINUX] only-linux
//@ [LINUX] filecheck-flags: -DINSTR_PROF_DATA=__llvm_prf_data
//@ [LINUX] filecheck-flags: -DINSTR_PROF_NAME=__llvm_prf_names
//@ [LINUX] filecheck-flags: -DINSTR_PROF_CNTS=__llvm_prf_cnts
//@ [LINUX] filecheck-flags: -DINSTR_PROF_COVMAP=__llvm_covmap
//@ [LINUX] filecheck-flags: -DINSTR_PROF_COVFUN=__llvm_covfun
//@ [LINUX] filecheck-flags: '-DCOMDAT_IF_SUPPORTED=, comdat'
//@ [DARWIN] only-macos
//@ [DARWIN] filecheck-flags: -DINSTR_PROF_DATA=__DATA,__llvm_prf_data,regular,live_support
//@ [DARWIN] filecheck-flags: -DINSTR_PROF_NAME=__DATA,__llvm_prf_names
//@ [DARWIN] filecheck-flags: -DINSTR_PROF_CNTS=__DATA,__llvm_prf_cnts
//@ [DARWIN] filecheck-flags: -DINSTR_PROF_COVMAP=__LLVM_COV,__llvm_covmap
//@ [DARWIN] filecheck-flags: -DINSTR_PROF_COVFUN=__LLVM_COV,__llvm_covfun
//@ [DARWIN] filecheck-flags: -DCOMDAT_IF_SUPPORTED=
//@ [WINDOWS] only-windows
//@ [WINDOWS] filecheck-flags: -DINSTR_PROF_DATA=.lprfd$M
//@ [WINDOWS] filecheck-flags: -DINSTR_PROF_NAME=.lprfn$M
//@ [WINDOWS] filecheck-flags: -DINSTR_PROF_CNTS=.lprfc$M
//@ [WINDOWS] filecheck-flags: -DINSTR_PROF_COVMAP=.lcovmap$M
//@ [WINDOWS] filecheck-flags: -DINSTR_PROF_COVFUN=.lcovfun$M
//@ [WINDOWS] filecheck-flags: '-DCOMDAT_IF_SUPPORTED=, comdat'
// ignore-tidy-linelength
pub fn will_be_called() -> &'static str {
let val = "called";
println!("{}", val);
val
}
pub fn will_not_be_called() -> bool {
println!("should not have been called");
false
}
pub fn print<T>(left: &str, value: T, right: &str)
where
T: std::fmt::Display,
{
println!("{}{}{}", left, value, right);
}
pub fn wrap_with<F, T>(inner: T, should_wrap: bool, wrapper: F)
where
F: FnOnce(&T)
{
if should_wrap {
wrapper(&inner)
}
}
fn main() {
let less = 1;
let more = 100;
if less < more {
wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** "));
wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** "));
} else {
wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, ""));
}
}
// Check for metadata, variables, declarations, and function definitions injected
// into LLVM IR when compiling with -Cinstrument-coverage.
// WINDOWS: $__llvm_profile_runtime_user = comdat any
// CHECK: @__llvm_coverage_mapping = private constant
// CHECK-SAME: section "[[INSTR_PROF_COVMAP]]", align 8
// CHECK: @__covrec_{{[A-F0-9]+}}u = linkonce_odr hidden constant
// CHECK-SAME: section "[[INSTR_PROF_COVFUN]]"[[COMDAT_IF_SUPPORTED]], align 8
// WINDOWS: @__llvm_profile_runtime = external{{.*}}global i32
// CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = {{private|internal}} global
// CHECK-SAME: section "[[INSTR_PROF_CNTS]]"{{.*}}, align 8
// CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = {{private|internal}} global
// CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called
// CHECK-SAME: section "[[INSTR_PROF_DATA]]"{{.*}}, align 8
// CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main = {{private|internal}} global
// CHECK-SAME: section "[[INSTR_PROF_CNTS]]"{{.*}}, align 8
// CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog4main = {{private|internal}} global
// CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main
// CHECK-SAME: section "[[INSTR_PROF_DATA]]"{{.*}}, align 8
// CHECK: @__llvm_prf_nm = private constant
// CHECK-SAME: section "[[INSTR_PROF_NAME]]", align 1
// CHECK: @llvm.used = appending global
// CHECK-SAME: @__llvm_coverage_mapping
// CHECK-SAME: @__llvm_prf_nm
// CHECK-SAME: section "llvm.metadata"
// CHECK: define internal { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} {
// CHECK-NEXT: start:
// CHECK-NOT: define internal
// CHECK: atomicrmw add ptr
// CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called,
// CHECK: declare void @llvm.instrprof.increment(ptr, i64, i32, i32) #[[LLVM_INSTRPROF_INCREMENT_ATTR:[0-9]+]]
// WINDOWS: define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #[[LLVM_PROFILE_RUNTIME_USER_ATTR:[0-9]+]] comdat {
// WINDOWS-NEXT: %1 = load i32, ptr @__llvm_profile_runtime
// WINDOWS-NEXT: ret i32 %1
// WINDOWS-NEXT: }
// CHECK: attributes #[[LLVM_INSTRPROF_INCREMENT_ATTR]] = { nounwind }
// WINDOWS: attributes #[[LLVM_PROFILE_RUNTIME_USER_ATTR]] = { noinline }

View file

@ -0,0 +1,4 @@
// Simple test that uses the default CHECK prefix and should always succeed.
// CHECK: main
fn main() {}

View file

@ -0,0 +1,8 @@
// Arguments provided via `filecheck-flags` should be passed to `filecheck`.
//@ revisions: good bad
//@ [good] filecheck-flags: --check-prefix=CUSTOM
//@ [bad] should-fail
// CUSTOM: main
fn main() {}

View file

@ -0,0 +1,7 @@
// This is exactly like `msvc-prefix-good.rs`, except that it should always fail.
//@ should-fail
// MSVC: text that should not match
// NONMSVC: text that should not match
fn main() {}

View file

@ -0,0 +1,7 @@
// One of MSVC or NONMSVC should always be defined, so this test should pass.
// (one of these should always be present)
// MSVC: main
// NONMSVC: main
fn main() {}

View file

@ -0,0 +1,5 @@
// A test that doesn't include any filecheck directives should fail.
//@ should-fail
fn main() {}

View file

@ -0,0 +1,8 @@
// The current revision name is registered as a filecheck prefix.
//@ revisions: GOOD BAD
//@ [BAD] should-fail
// GOOD: main
// BAD: text that should not match
fn main() {}

View file

@ -1,64 +0,0 @@
# needs-profiler-support
# Rust coverage maps support LLVM Coverage Mapping Format versions 5 and 6,
# corresponding with LLVM versions 12 and 13, respectively.
# When upgrading LLVM versions, consider whether to enforce a minimum LLVM
# version during testing, with an additional directive at the top of this file
# that sets, for example: `min-llvm-version: 12.0`
include ../tools.mk
BASEDIR=../coverage-llvmir
ifeq ($(UNAME),Darwin)
INSTR_PROF_DATA_SUFFIX=,regular,live_support
DATA_SECTION_PREFIX=__DATA,
LLVM_COV_SECTION_PREFIX=__LLVM_COV,
COMDAT_IF_SUPPORTED=
else
INSTR_PROF_DATA_SUFFIX=
DATA_SECTION_PREFIX=
LLVM_COV_SECTION_PREFIX=
COMDAT_IF_SUPPORTED=, comdat
endif
DEFINE_INTERNAL=define internal
ifdef IS_WINDOWS
LLVM_FILECHECK_OPTIONS=\
-check-prefixes=CHECK,WINDOWS \
-DDEFINE_INTERNAL='$(DEFINE_INTERNAL)' \
-DCOMDAT_IF_SUPPORTED='$(COMDAT_IF_SUPPORTED)' \
-DINSTR_PROF_DATA='.lprfd$$M' \
-DINSTR_PROF_NAME='.lprfn$$M' \
-DINSTR_PROF_CNTS='.lprfc$$M' \
-DINSTR_PROF_VALS='.lprfv$$M' \
-DINSTR_PROF_VNODES='.lprfnd$$M' \
-DINSTR_PROF_COVMAP='.lcovmap$$M' \
-DINSTR_PROF_COVFUN='.lcovfun$$M' \
-DINSTR_PROF_ORDERFILE='.lorderfile$$M'
else
LLVM_FILECHECK_OPTIONS=\
-check-prefixes=CHECK \
-DDEFINE_INTERNAL='$(DEFINE_INTERNAL)' \
-DCOMDAT_IF_SUPPORTED='$(COMDAT_IF_SUPPORTED)' \
-DINSTR_PROF_DATA='$(DATA_SECTION_PREFIX)__llvm_prf_data$(INSTR_PROF_DATA_SUFFIX)' \
-DINSTR_PROF_NAME='$(DATA_SECTION_PREFIX)__llvm_prf_names' \
-DINSTR_PROF_CNTS='$(DATA_SECTION_PREFIX)__llvm_prf_cnts' \
-DINSTR_PROF_VALS='$(DATA_SECTION_PREFIX)__llvm_prf_vals' \
-DINSTR_PROF_VNODES='$(DATA_SECTION_PREFIX)__llvm_prf_vnds' \
-DINSTR_PROF_COVMAP='$(LLVM_COV_SECTION_PREFIX)__llvm_covmap' \
-DINSTR_PROF_COVFUN='$(LLVM_COV_SECTION_PREFIX)__llvm_covfun' \
-DINSTR_PROF_ORDERFILE='$(DATA_SECTION_PREFIX)__llvm_orderfile'
endif
all: test_llvm_ir
test_llvm_ir:
# Compile the test program with non-experimental coverage instrumentation, and generate LLVM IR
$(RUSTC) $(BASEDIR)/testprog.rs \
-Cinstrument-coverage \
--emit=llvm-ir
cat "$(TMPDIR)"/testprog.ll | \
"$(LLVM_FILECHECK)" $(BASEDIR)/filecheck.testprog.txt $(LLVM_FILECHECK_OPTIONS)

View file

@ -1,50 +0,0 @@
# Check for metadata, variables, declarations, and function definitions injected
# into LLVM IR when compiling with -Cinstrument-coverage.
WINDOWS: $__llvm_profile_runtime_user = comdat any
CHECK: @__llvm_coverage_mapping = private constant
CHECK-SAME: section "[[INSTR_PROF_COVMAP]]", align 8
CHECK: @__covrec_{{[A-F0-9]+}}u = linkonce_odr hidden constant
CHECK-SAME: section "[[INSTR_PROF_COVFUN]]"[[COMDAT_IF_SUPPORTED]], align 8
WINDOWS: @__llvm_profile_runtime = external{{.*}}global i32
CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = {{private|internal}} global
CHECK-SAME: section "[[INSTR_PROF_CNTS]]"{{.*}}, align 8
CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog14will_be_called = {{private|internal}} global
CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called
CHECK-SAME: section "[[INSTR_PROF_DATA]]"{{.*}}, align 8
CHECK: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main = {{private|internal}} global
CHECK-SAME: section "[[INSTR_PROF_CNTS]]"{{.*}}, align 8
CHECK: @__profd__R{{[a-zA-Z0-9_]+}}testprog4main = {{private|internal}} global
CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog4main
CHECK-SAME: section "[[INSTR_PROF_DATA]]"{{.*}}, align 8
CHECK: @__llvm_prf_nm = private constant
CHECK-SAME: section "[[INSTR_PROF_NAME]]", align 1
CHECK: @llvm.used = appending global
CHECK-SAME: @__llvm_coverage_mapping
CHECK-SAME: @__llvm_prf_nm
CHECK-SAME: section "llvm.metadata"
CHECK: [[DEFINE_INTERNAL]] { {{.*}} } @_R{{[a-zA-Z0-9_]+}}testprog14will_be_called() unnamed_addr #{{[0-9]+}} {
CHECK-NEXT: start:
CHECK-NOT: [[DEFINE_INTERNAL]]
CHECK: atomicrmw add ptr
CHECK-SAME: @__profc__R{{[a-zA-Z0-9_]+}}testprog14will_be_called,
CHECK: declare void @llvm.instrprof.increment(ptr, i64, i32, i32) #[[LLVM_INSTRPROF_INCREMENT_ATTR:[0-9]+]]
WINDOWS: define linkonce_odr hidden i32 @__llvm_profile_runtime_user() #[[LLVM_PROFILE_RUNTIME_USER_ATTR:[0-9]+]] comdat {
WINDOWS-NEXT: %1 = load i32, ptr @__llvm_profile_runtime
WINDOWS-NEXT: ret i32 %1
WINDOWS-NEXT: }
CHECK: attributes #[[LLVM_INSTRPROF_INCREMENT_ATTR]] = { nounwind }
WINDOWS: attributes #[[LLVM_PROFILE_RUNTIME_USER_ATTR]] = { noinline }

View file

@ -1,38 +0,0 @@
pub fn will_be_called() -> &'static str {
let val = "called";
println!("{}", val);
val
}
pub fn will_not_be_called() -> bool {
println!("should not have been called");
false
}
pub fn print<T>(left: &str, value: T, right: &str)
where
T: std::fmt::Display,
{
println!("{}{}{}", left, value, right);
}
pub fn wrap_with<F, T>(inner: T, should_wrap: bool, wrapper: F)
where
F: FnOnce(&T)
{
if should_wrap {
wrapper(&inner)
}
}
fn main() {
let less = 1;
let more = 100;
if less < more {
wrap_with(will_be_called(), less < more, |inner| print(" ***", inner, "*** "));
wrap_with(will_be_called(), more < less, |inner| print(" ***", inner, "*** "));
} else {
wrap_with(will_not_be_called(), true, |inner| print("wrapped result is: ", inner, ""));
}
}