FILECHECK(1)

๐Ÿ“ฆ
version 12.0.1 ยท 4 versions
, in fedora 43, fedora 44

๐ŸŽ€ FILECHECK(1) ๐ŸŽ€

okay bestie, this is the LLVM manual page for FileCheck, and it's giving main character energy ๐Ÿ’…โœจ let's go


โœจ NAME โœจ

FileCheck - Flexible pattern matching file verifier ๐Ÿฆ‹

๐ŸŒธ SYNOPSIS ๐ŸŒธ

FileCheck match-filename [--check-prefix=XXX] [--strict-whitespace]

๐Ÿ’œ DESCRIPTION ๐Ÿ’œ

okay so FileCheck reads two files ๐Ÿ‘€ one comes from standard input and the other one you name on the command line, and then it uses one to verify the other. this is super useful for the testsuite, which wants to check that the output of some tool (like llc) has the info it's supposed to have, like a movsd from esp or whatever is interesting ๐Ÿ”ฅ it's kinda like grep, but it's way more slay at matching a bunch of different inputs inside one file, in a specific order ๐Ÿ’–

the match-filename file is the one holding the patterns to match ๐Ÿงš the file being verified gets read from standard input unless you use the --input-file option ๐Ÿซถ

๐Ÿฆ‹ OPTIONS ๐Ÿฆ‹

the options get parsed from the environment variable FILECHECK_OPTS and from the command line, so you can set them in either spot ๐Ÿ’…

  • -help โ€” prints a summary of all the command line options ๐ŸŽ€
  • --check-prefix prefix โ€” FileCheck goes hunting through match-filename for patterns, and by default they start with CHECK: ๐Ÿ” if you want a different prefix (maybe the same input file checks multiple tools or options), this lets you pick one or more prefixes to match, written without the trailing :. multiple prefixes are super handy for tests that change between run options but where most lines stay the same ๐Ÿ’– and FileCheck does not allow duplicate prefixes, even if one is a check prefix and another is a comment prefix (see --comment-prefixes below) ๐Ÿ™…โ€โ™€๏ธ
  • --check-prefixes prefix1,prefix2,... โ€” an alias of --check-prefix that lets you pass multiple prefixes as a comma separated list ๐ŸŒˆ
  • --comment-prefixes prefix1,prefix2,... โ€” by default, FileCheck ignores any check prefix in match-filename if it has COM: or RUN: right before it on the same line ๐Ÿ™ˆ see the section The "COM:" directive for the deets. you can override these default comment prefixes with --comment-prefixes if they don't fit your testing setup, but honestly? not recommended in LLVM's LIT-based test suites, which are easier to maintain when everyone follows one consistent comment style ๐Ÿซ  so if you think they're wrong, consider proposing a change to the default comment prefixes instead โœจ
  • --allow-unused-prefixes โ€” this one controls what happens when you use more than one prefix (via --check-prefix or --check-prefixes) and some of those prefixes are missing from the test file. if it's true, that's allowed ๐Ÿ™†โ€โ™€๏ธ if it's false, FileCheck throws an error and lists the missing prefixes ๐Ÿšจ it's currently, temporarily, true by default, and will be switched to false later on โณ
  • --input-file filename โ€” the file to check. defaults to stdin ๐Ÿ“ฅ
  • --match-full-lines โ€” by default FileCheck is chill about matching anywhere on a line ๐Ÿ˜Œ this option makes every positive match cover an entire line. leading and trailing whitespace gets ignored, unless --strict-whitespace is also specified. (Note: negative matches from CHECK-NOT are not affected by this option, so don't get it twisted! ๐Ÿ’€) passing this option is the same as sticking {{^ *}} or {{^}} before, and {{ *$}} or {{$}} after every positive check pattern ๐Ÿง 
  • --strict-whitespace โ€” by default FileCheck canonicalizes horizontal whitespace (spaces and tabs), which means it ignores those differences, so a space will match a tab ๐Ÿซง this flag turns that behaviour off. end-of-line sequences get canonicalized to UNIX-style \n in all modes ๐Ÿ“
  • --ignore-case โ€” by default FileCheck is case-sensitive ๐Ÿ™ƒ this makes it case-insensitive instead ๐ŸŽ€
  • --implicit-check-not check-pattern โ€” adds implicit negative checks for the patterns you specify, between the positive checks ๐Ÿงš it lets you write stricter tests without stuffing them full of CHECK-NOTs. for example, --implicit-check-not warning: can be iconic when you're testing diagnostic messages from tools that don't have an option like clang -verify. with this, FileCheck verifies that the input doesn't contain any warnings that aren't covered by a CHECK: pattern ๐Ÿ‘‘
  • --dump-input <value> โ€” dumps the input to stderr and adds annotations for whatever diagnostics are enabled ๐Ÿ”ฅ if you use this option more than once, the <value> that shows up earliest in the list below wins. the default is fail ๐Ÿ’…
    • help โ€” explains the input dump and quits
    • always โ€” always dumps the input
    • fail โ€” dumps the input on failure
    • never โ€” never dumps the input
  • --dump-input-context <N> โ€” in the dump requested by --dump-input, prints <N> input lines before and <N> input lines after any lines picked by --dump-input-filter ๐Ÿ“œ if you use it multiple times, the largest <N> wins. the default is 5 โœจ
  • --dump-input-filter <value> โ€” in that same dump, prints only the input lines of kind <value>, plus whatever context --dump-input-context asks for ๐ŸŽฏ multiple uses? the <value> earliest in the list below has precedence. the default is error when --dump-input=fail, and it's all when --dump-input=always ๐ŸŒธ
    • all โ€” all input lines
    • annotation-full โ€” input lines with annotations
    • annotation โ€” input lines with starting points of annotations
    • error โ€” input lines with starting points of error annotations
  • --enable-var-scope โ€” enables scope for regex variables ๐Ÿงฉ variables whose names start with $ are considered global and stay set the whole way through the file ๐ŸŒ all the other variables get undefined after each CHECK-LABEL they run into ๐Ÿงน
  • -D<VAR=VALUE> โ€” sets a FileCheck pattern variable VAR to the value VALUE, so you can use it in CHECK: lines ๐Ÿ’–
  • -D#<FMT>,<NUMVAR>=<NUMERIC EXPRESSION> โ€” sets a FileCheck numeric variable NUMVAR in the matching format FMT to whatever you get from evaluating <NUMERIC EXPRESSION>, which you can then use in CHECK: lines ๐Ÿงฎ see section FileCheck Numeric Variables and Expressions for the supported numeric expressions ๐Ÿค“
  • -version โ€” shows the version number of this program ๐Ÿท๏ธ
  • -v โ€” prints the good directive pattern matches โœ… but if you're using -dump-input=fail or -dump-input=always, it adds those matches as input annotations instead ๐Ÿ“
  • -vv โ€” prints info that helps diagnose internal FileCheck issues ๐Ÿ”ง think discarded overlapping CHECK-DAG: matches, implicit EOF pattern matches, and CHECK-NOT: patterns that never found a match ๐Ÿ‘€ it implies -v. however, if you're using -dump-input=fail or -dump-input=always, it just adds that info as input annotations instead ๐Ÿฆ‹
  • --allow-deprecated-dag-overlap โ€” enables overlapping among matches in a group of consecutive CHECK-DAG: directives ๐Ÿฅ€
โš ๏ธ this option is deprecated! it's only here for convenience while old tests get migrated to the new non-overlapping CHECK-DAG: implementation, so don't build new stuff on it ๐Ÿ˜ญ
  • --allow-empty โ€” allows checking empty input. by default, empty input gets rejected ๐Ÿซ™
  • --color โ€” uses colors in the output ๐ŸŒˆ (it's autodetected by default, so you probably don't even need it ๐Ÿ’…)

๐ŸŒ™ EXIT STATUS ๐ŸŒ™

if FileCheck verifies that the file matches the expected contents, it exits with 0 โœ… otherwise, if it doesn't match, or if an error happens, it exits with a non-zero value โŒ

๐ŸŽฌ TUTORIAL ๐ŸŽฌ

okay so FileCheck is basically always used inside LLVM regression tests, where it gets invoked on the RUN line of the test ๐ŸŽ€ here's a super simple example of using FileCheck from a RUN line:

; RUN: llvm-as < %s | llc -march=x86-64 | FileCheck %s

so this line pipes the current file (%s) into llvm-as, pipes that into llc, and then pipes the output of llc into FileCheck ๐Ÿงš which means FileCheck is verifying its standard input (the llc output) against the filename argument you gave it (the original .ll file named by %s) ๐Ÿ” to see how it all works, let's look at the rest of the .ll file, after the RUN line:

define void @sub1(i32* %p, i32 %v) {
entry:
; CHECK: sub1:
; CHECK: subl
        %0 = tail call i32 @llvm.atomic.load.sub.i32.p0i32(i32* %p, i32 %v)
        ret void
}

define void @inc4(i64* %p) {
entry:
; CHECK: inc4:
; CHECK: incq
        %0 = tail call i64 @llvm.atomic.load.add.i64.p0i64(i64* %p, i64 1)
        ret void
}

see those CHECK: lines? they're sitting in comments ๐Ÿ‘€ so the file goes into llvm-as, then llc, and the machine code output is what we're verifying ๐Ÿ’– FileCheck checks that machine code output to make sure it matches what the CHECK: lines say โœจ

the syntax of CHECK: lines is super simple: they're fixed strings that have to show up in order ๐Ÿซถ FileCheck defaults to ignoring horizontal whitespace differences (a space is allowed to match a tab ๐Ÿซง), but otherwise the contents of the CHECK: line have to match something in the test file exactly ๐Ÿ’…

one thing that's lowkey amazing about FileCheck (compared to grep) is that it lets you merge test cases together into logical groups ๐Ÿงฉ for example, because the test above checks for the sub1: and inc4: labels, it won't match unless there's a subl in between those labels ๐ŸŽฏ if it showed up somewhere else in the file, that wouldn't count! grep subl would match if subl existed anywhere in the file, but FileCheck is more precise than that ๐Ÿฆ‹

๐ŸŒธ The FileCheck -check-prefix option ๐ŸŒธ

the FileCheck -check-prefix option lets you drive multiple test configurations from one .ll file ๐ŸŒˆ this is useful in lots of situations, like testing different architectural variants with llc ๐Ÿ’œ here's a simple example:

; RUN: llvm-as < %s | llc -mtriple=i686-apple-darwin9 -mattr=sse41 \
; RUN:              | FileCheck %s -check-prefix=X32
; RUN: llvm-as < %s | llc -mtriple=x86_64-apple-darwin9 -mattr=sse41 \
; RUN:              | FileCheck %s -check-prefix=X64

define <4 x i32> @pinsrd_1(i32 %s, <4 x i32> %tmp) nounwind {
        %tmp1 = insertelement <4 x i32>; %tmp, i32 %s, i32 1
        ret <4 x i32> %tmp1
; X32: pinsrd_1:
; X32:    pinsrd $1, 4(%esp), %xmm0
; X64: pinsrd_1:
; X64:    pinsrd $1, %edi, %xmm0
}

Okay so in this one we're making sure we get the right codegen for both 32-bit and 64-bit code generation ๐Ÿ’–โœจ

๐ŸŽ€ The "COM:" directive ๐ŸŽ€

Sometimes you want to switch off a FileCheck directive without deleting it, or you want to leave a comment that mentions a directive by name ๐Ÿ™ˆ That's where COM: comes in, and it makes this super easy. For example, you might have:

; X32: pinsrd_1:
; X32:    pinsrd $1, 4(%esp), %xmm0

; COM: FIXME: X64 isn't working correctly yet for this part of codegen, but
; COM: X64 will have something similar to X32:
; COM:
; COM:   X64: pinsrd_1:
; COM:   X64:    pinsrd $1, %edi, %xmm0

Without COM:, you'd have to reword stuff and mangle the directive syntax so FileCheck doesn't mistake the commented X32: and X64: lines for real directives ๐Ÿ˜ญ And there are FileCheck diagnostics that have been proposed which might yell about those X64 lines that don't have the trailing :, because they look like directive typos. Dodging all of that is exhausting for the test author, and mangling the syntax makes the point of the test code super unclear. COM: dodges every single one of these problems ๐Ÿซถ

A few important usage notes, bestie:

  • COM: sitting inside another directive's pattern does not comment out the rest of that pattern. For example:
; X32: pinsrd $1, 4(%esp), %xmm0 COM: This is part of the X32 pattern!

If you need to temporarily comment out part of a directive's pattern, move it to another line ๐Ÿฆ‹ The reason is that FileCheck parses COM: the same way it parses any other directive, so only the first directive on the line gets recognized.

  • For the sake of LIT, FileCheck treats RUN: just like COM:. If that doesn't work for your test environment, check out --comment-prefixes ๐Ÿ’…
  • FileCheck does not recognize COM, RUN, or any user-defined comment prefix as a comment directive when it's combined with one of the usual check directive suffixes, like -NEXT: or -NOT: (we'll get to those below). Instead, FileCheck treats the whole combo as plain text ๐Ÿ’€ If it needs to act as a comment directive for your test environment, define it that way with --comment-prefixes.

๐ŸŒˆ The "CHECK-NEXT:" directive ๐ŸŒˆ

Sometimes you want to match lines and make sure the matches happen on exactly consecutive lines, with no other lines squeezed in between ๐Ÿ‘‘ For that, you pair up a CHECK: with a CHECK-NEXT:. If you picked a custom check prefix, just use <PREFIX>-NEXT: instead. Something like this works exactly how you'd expect:

define void @t2(<2 x double>* %r, <2 x double>* %A, double %B) {
     %tmp3 = load <2 x double>* %A, align 16
     %tmp7 = insertelement <2 x double> undef, double %B, i32 0
     %tmp9 = shufflevector <2 x double> %tmp3,
                            <2 x double> %tmp7,
                            <2 x i32> < i32 0, i32 2 >
     store <2 x double> %tmp9, <2 x double>* %r, align 16
     ret void

; CHECK:          t2:
; CHECK:             movl    8(%esp), %eax
; CHECK-NEXT:        movapd  (%eax), %xmm0
; CHECK-NEXT:        movhpd  12(%esp), %xmm0
; CHECK-NEXT:        movl    4(%esp), %eax
; CHECK-NEXT:        movapd  %xmm0, (%eax)
; CHECK-NEXT:        ret
}
โš ๏ธ CHECK-NEXT: directives reject the input unless there is exactly one newline between them and the previous directive. A CHECK-NEXT: also cannot be the very first directive in a file.

๐Ÿฆ‹ The "CHECK-SAME:" directive ๐Ÿฆ‹

Sometimes you want to match lines and verify that a match happens on the same line as the previous match โœจ For that, you use CHECK: and CHECK-SAME:. If you picked a custom check prefix, use <PREFIX>-SAME: instead.

CHECK-SAME: is especially powerful when you team it up with CHECK-NOT: (which we'll hit below ๐Ÿงš).

For example, the following works like you'd expect:

!0 = !DILocation(line: 5, scope: !1, inlinedAt: !2)

; CHECK:       !DILocation(line: 5,
; CHECK-NOT:               column:
; CHECK-SAME:              scope: ![[SCOPE:[0-9]+]]
โš ๏ธ CHECK-SAME: directives reject the input if there are any newlines between them and the previous directive.

CHECK-SAME: is also a lifesaver for skipping matchers on fields you don't care about ๐Ÿ“ Say you're writing a test for a tool that spits out output like this:

Name: foo
Field1: ...
Field2: ...
Field3: ...
Value: 1

Name: bar
Field1: ...
Field2: ...
Field3: ...
Value: 2

Name: baz
Field1: ...
Field2: ...
Field3: ...
Value: 1

To write a test that checks foo has the value 1, your first instinct might be this:

CHECK: Name: foo
CHECK: Value: 1{{$}}

But that's a bad test, bestie ๐Ÿ’€ If foo's value changes, the test would still pass, because the CHECK: Value: 1 line would just match the value from baz. You could fix it by adding CHECK-NEXT matchers for every FieldN: line, but that's verbose and you'd have to update it every time a Field4 shows up. The more succinct move is using CHECK-SAME::

CHECK:      Name: foo
CHECK:      Value:
CHECK-SAME:        {{ 1$}}

This checks that the next time Value: shows up in the output, it has the value 1 ๐ŸŽฏ

Note: a CHECK-SAME: cannot be the first directive in a file.

๐ŸŒธ The "CHECK-EMPTY:" directive ๐ŸŒธ

If you need to check that the next line is totally empty (not even whitespace), reach for the CHECK-EMPTY: directive ๐Ÿซง

declare void @foo()

declare void @bar()
; CHECK: foo
; CHECK-EMPTY:
; CHECK-NEXT: bar

Just like CHECK-NEXT:, this directive fails if there's more than one newline before it finds the next blank line, and it also cannot be the first directive in a file.

๐Ÿ’œ The "CHECK-NOT:" directive ๐Ÿ’œ

The CHECK-NOT: directive makes sure a string does not show up between two matches (or before the first match, or after the last one) ๐Ÿ™…โ€โ™€๏ธ For example, to check that a transformation removed a load, you can write a test like this:

define i8 @coerce_offset0(i32 %V, i32* %P) {
  store i32 %V, i32* %P

  %P2 = bitcast i32* %P to i8*
  %P3 = getelementptr i8* %P2, i32 2

  %A = load i8* %P3
  ret i8 %A
; CHECK: @coerce_offset0
; CHECK-NOT: load
; CHECK: ret i8
}

๐Ÿ”ฅ The "CHECK-COUNT:" directive ๐Ÿ”ฅ

If you need to match the same pattern over and over again, you can repeat a plain CHECK: as many times as you need ๐Ÿฅฑ If that feels too boring, use a counted check, CHECK-COUNT-<num>:, where <num> is a positive decimal number. It'll match the pattern exactly <num> times, no more and no less ๐ŸŽ€ If you picked a custom check prefix, use <PREFIX>-COUNT-<num>: for the same effect. Here's a simple example:

Loop at depth 1
Loop at depth 1
Loop at depth 1
Loop at depth 1
  Loop at depth 2
    Loop at depth 3

; CHECK-COUNT-6: Loop at depth {{[0-9]+}}
; CHECK-NOT:     Loop at depth {{[0-9]+}}

๐Ÿงš The "CHECK-DAG:" directive ๐Ÿงš

If you need to match strings that don't show up in a strictly sequential order, CHECK-DAG: is your bestie ๐Ÿ’ซ It checks them between two matches (or before the first match, or after the last one). For example, clang emits vtable globals in reverse order, so using CHECK-DAG: lets us keep the checks in the natural order:

// RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s

struct Foo { virtual void method(); };
Foo f;  // emit vtable
// CHECK-DAG: @_ZTV3Foo =

struct Bar { virtual void method(); };
Bar b;
// CHECK-DAG: @_ZTV3Bar =

CHECK-NOT: directives can be mixed in with CHECK-DAG: directives to exclude strings between the surrounding CHECK-DAG: directives. Because of that, the surrounding CHECK-DAG: directives can't be reordered. In other words, every occurrence matching a CHECK-DAG: before a CHECK-NOT: must not fall behind an occurrence matching a CHECK-DAG: after that CHECK-NOT:. For example:

; CHECK-DAG: BEFORE
; CHECK-NOT: NOT
; CHECK-DAG: AFTER

This case will reject input strings where BEFORE shows up after AFTER ๐Ÿšซ

With captured variables, CHECK-DAG: can match valid topological orderings of a DAG, with edges going from a variable's definition to its use. This is super useful when your test cases need to match different output sequences from the instruction scheduler ๐Ÿ”ฅ For example:

; CHECK-DAG: add [[REG1:r[0-9]+]], r1, r2
; CHECK-DAG: add [[REG2:r[0-9]+]], r3, r4
; CHECK:     mul r5, [[REG1]], [[REG2]]

In this case, either order of those two add instructions will be allowed โœจ

If you're defining and using variables in the same CHECK-DAG: block, be aware that the definition rule can match after its use.

So, for instance, the code below will pass:

; CHECK-DAG: vmov.32 [[REG2:d[0-9]+]][0]
; CHECK-DAG: vmov.32 [[REG2]][1]
vmov.32 d0[1]
vmov.32 d0[0]

But this other code will not:

; CHECK-DAG: vmov.32 [[REG2:d[0-9]+]][0]
; CHECK-DAG: vmov.32 [[REG2]][1]
vmov.32 d1[1]
vmov.32 d0[0]

As useful as this is, it's also a little dangerous ๐Ÿ˜ฌ With register sequences you need a strong order (read before write, copy before use, and so on). If the definition your test is looking for doesn't match (because of a bug in the compiler), it might match further away from the use and hide real bugs. In those cases, to enforce the order, use a non-DAG directive between your DAG blocks.

A CHECK-DAG: directive skips matches that overlap the matches of any preceding CHECK-DAG: directives in the same CHECK-DAG: block. This non-overlapping behaviour is consistent with other directives, and it's also necessary for handling sets of non-unique strings or patterns ๐Ÿฅน For example, the following directives look for unordered log entries for two tasks in a parallel program, like the OpenMP runtime:

// CHECK-DAG: [[THREAD_ID:[0-9]+]]: task_begin
// CHECK-DAG: [[THREAD_ID]]: task_end
//
// CHECK-DAG: [[THREAD_ID:[0-9]+]]: task_begin
// CHECK-DAG: [[THREAD_ID]]: task_end

The second pair of directives is guaranteed not to match the same log entries as the first pair, even when the patterns are identical and even when the log entries' text is identical, because the thread ID manages to get reused ๐ŸŒˆ

๐ŸŽ€ The "CHECK-LABEL:" directive ๐ŸŽ€

okay so bestie, picture a file with a bunch of tests split into logical blocks ๐Ÿงš. One or more CHECK: directives can sneakily pass by matching lines in a later block ๐Ÿ˜ญ. An error usually shows up eventually, but the check it blames might have nothing to do with the real problem ๐Ÿ’€.

To get better error messages in these cases, use the CHECK-LABEL: directive โœจ. It works exactly like a normal CHECK directive, with one extra assumption: FileCheck figures a line matched by CHECK-LABEL: can't also be matched by any other check in match-filename. It's meant for lines with labels or other unique identifiers ๐Ÿฆ‹. So a CHECK-LABEL: splits the input stream into separate blocks, and each block gets processed on its own. That means a CHECK: in one block can't match a line in another block ๐Ÿ‘‘. If --enable-var-scope is in effect, all local variables get cleared at the beginning of the block ๐Ÿ’œ.

Here's the vibe:

define %struct.C* @C_ctor_base(%struct.C* %this, i32 %x) {
entry:
; CHECK-LABEL: C_ctor_base:
; CHECK: mov [[SAVETHIS:r[0-9]+]], r0
; CHECK: bl A_ctor_base
; CHECK: mov r0, [[SAVETHIS]]
  %0 = bitcast %struct.C* %this to %struct.A*
  %call = tail call %struct.A* @A_ctor_base(%struct.A* %0)
  %1 = bitcast %struct.C* %this to %struct.B*
  %call2 = tail call %struct.B* @B_ctor_base(%struct.B* %1, i32 %x)
  ret %struct.C* %this
}

define %struct.D* @D_ctor_base(%struct.D* %this, i32 %x) {
entry:
; CHECK-LABEL: D_ctor_base:

So in this case, the CHECK-LABEL: directives make sure those three CHECK: directives only accept lines from the body of @C_ctor_base, even if their patterns match lines further down the file ๐Ÿ™ˆ. Even better, if one of those three CHECK: directives fails, FileCheck recovers and moves on to the next block ๐Ÿซถ. That means you can catch multiple test failures in one single invocation ๐Ÿ”ฅ.

There's no requirement that CHECK-LABEL: directives contain strings matching actual syntactic labels in a source or output language ๐ŸŒˆ. They just have to uniquely match a single line in the file being verified โœจ.

Heads up: CHECK-LABEL: directives cannot contain variable definitions or uses ๐Ÿšซ.

๐ŸŒธ Directive modifiers ๐ŸŒธ

A directive modifier can be appended to a directive by putting {<modifier>} right after it, and the only supported value for <modifier> is LITERAL ๐Ÿ“.

The LITERAL modifier gives you a literal match ๐Ÿ’–. It turns off all regex matching, variable capture, and substitutions, which is super handy when the text you're matching would need a mountain of escaping otherwise ๐Ÿ˜ฉ. For example, these will be literal matches instead of being treated as regular expressions:

Input: [[[10, 20]], [[30, 40]]]
Output %r10: [[10, 20]]
Output %r10: [[30, 40]]

; CHECK{LITERAL}: [[[10, 20]], [[30, 40]]]
; CHECK-DAG{LITERAL}: [[30, 40]]
; CHECK-DAG{LITERAL}: [[10, 20]]

๐Ÿ’… FileCheck Regex Matching Syntax ๐Ÿ’…

Every FileCheck directive takes a pattern to match ๐ŸŽฏ. For most of what you'll do, plain fixed string matching is totally sufficient ๐Ÿ’โ€โ™€๏ธ. For fancier stuff, you can use a more flexible form of matching. FileCheck lets you write regular expressions inside double braces, like {{yourregex}} โœจ. Under the hood it's a POSIX regular expression matcher that supports Extended POSIX regular expressions (ERE) ๐Ÿง . Since fixed string matching covers most of what we do, FileCheck is built so you can mix and match fixed strings with regexes in the same pattern ๐Ÿฆ‹. Like this:

; CHECK: movhpd      {{[0-9]+}}(%esp), {{%xmm[0-7]}}

Here, any offset from the ESP register is allowed, and any xmm register is allowed too ๐ŸŒท.

Regexes are wrapped in double braces, so they stand out visually, and you don't need to escape anything inside them the way you would in C ๐Ÿ™Œ. The rare time you need to match double braces literally from the input, you can use something kinda ugly like {{[}][}]}} as your pattern ๐Ÿ˜ต. And if you're using the repetition count syntax, like [[:xdigit:]]{8} to match exactly 8 hex digits, you need to add parentheses like {{([[:xdigit:]]{8})}}. Otherwise it gets confused with FileCheck's closing double-brace ๐Ÿ™ˆ.


๐Ÿงƒ FileCheck String Substitution Blocks ๐Ÿงƒ

Sometimes you want to match a pattern and then check that it shows up again later in the file ๐Ÿ‘€. For codegen tests, that's great for allowing any register while making sure that same register gets used consistently later ๐Ÿ’œ. FileCheck handles this with string substitution blocks, which let you define string variables and substitute them into patterns. Here's a simple example:

; CHECK: test5:
; CHECK:    notw     [[REGISTER:%[a-z]+]]
; CHECK:    andw     {{.*}}[[REGISTER]]

The first check line matches the regex %[a-z]+ and captures it into the string variable REGISTER ๐Ÿซง. The second line checks that whatever's in REGISTER shows up later in the file after an "andw" ๐Ÿ’…. String substitution blocks are always wrapped in [[ ]] pairs โœจ. Variable names can be formed with the regex [a-zA-Z_][a-zA-Z0-9_]*. If a colon follows the name, it's a definition. If there's no colon, it's a substitution ๐ŸŽ€.

Variables can be defined multiple times, and substitutions always grab the latest value ๐Ÿ”. You can even substitute a variable later on the same line it was defined on. For example:

; CHECK: op [[REG:r[0-9]+]], [[REG]]

This is perfect when you want the operands of op to be the same register but don't care which register it is ๐Ÿฅฐ.

If --enable-var-scope is in effect, variables whose names start with $ count as global ๐ŸŒ. Everything else is local ๐Ÿ . All local variables get undefined at the beginning of each CHECK-LABEL block, but global variables aren't affected by CHECK-LABEL at all ๐Ÿ’ซ. This makes it way easier to make sure individual tests aren't messed up by variables set in earlier tests ๐Ÿงš.


๐Ÿ”ข FileCheck Numeric Substitution Blocks ๐Ÿ”ข

FileCheck also has numeric substitution blocks ๐Ÿงฎ. They let you define numeric variables and check for numeric values that satisfy a constraint based on those variables. This is how a CHECK: directive can verify a numeric relationship between two numbers, like making sure consecutive registers get used ๐Ÿ”ฅ.

To capture a numeric value, the syntax is [[#%<fmtspec>,<NUMVAR>:]], where:

  • %<fmtspec>, is an optional format specifier that tells FileCheck what number format to match and the minimum number of digits to expect ๐Ÿ”.
  • <NUMVAR>: is an optional definition of the variable <NUMVAR> from the captured value ๐Ÿ’–.

The syntax of <fmtspec> is .<precision><conversion specifier>, where:

  • .<precision> is an optional printf-style precision specifier. <precision> is the minimum number of digits the matched value must have, and leading zeros are expected if needed 0๏ธโƒฃ.
  • <conversion specifier> is an optional scanf-style conversion specifier that tells FileCheck what number format to match, like hex. The accepted format specifiers right now are %u, %d, %x and %X. If it's missing, the format specifier defaults to %u ๐ŸŽฏ.

For example:

; CHECK: mov r[[#REG:]], 0x[[#%.8X,ADDR:]]

This would match mov r5, 0x0000FEFE and set REG to the value 5 and ADDR to the value 0xFEFE ๐Ÿฆ‹. But heads up, because of the precision, it would fail to match mov r5, 0xFEFE ๐Ÿ˜ญ.

Since the numeric variable definition is optional, you can also just check that a numeric value is present in a given format ๐ŸŒธ. This is useful when the actual value doesn't matter:

; CHECK-NOT: mov r0, r[[#]]

This checks that a value is synthesized rather than moved around ๐Ÿงƒ.

The syntax of a numeric substitution is [[#%<fmtspec>, <constraint> <expr>]], where:

  • <fmtspec> is the same format specifier you'd use when defining a variable, but here it tells FileCheck how a numeric expression's value should be matched. If it's missing, both parts of the format specifier get inferred from the matching format of the numeric variables used by the constraint (if there are any). If no numeric variable is used, it defaults to %u, meaning the value is unsigned with no leading zeros ๐Ÿ”ข. If several numeric variables have conflicting format specifiers, the conversion specifier becomes mandatory, but the precision specifier stays optional ๐Ÿ’….
  • <constraint> describes how the value to match must relate to the value of the numeric expression. The only accepted constraint right now is == for an exact match, and it's the default if <constraint> isn't provided. No matching constraint should be specified when <expr> is empty ๐Ÿšซ.
  • <expr> is an expression, and it's defined recursively as:
    • a numeric operand, or
    • an expression followed by an operator and a numeric operand.

A numeric operand is a previously defined numeric variable, an integer literal, or a function. You can put spaces before, after, and between any of these elements ๐Ÿซถ. Numeric operands have 64-bit precision, and overflow and underflow get rejected ๐Ÿšจ. There's no operator precedence, but you can use parentheses to change the evaluation order ๐Ÿง .

The supported operators are:

  • + โ€” Returns the sum of its two operands โž•.
  • - โ€” Returns the difference of its two operands โž–.

The syntax of a function call is <name>(<arguments>), where:

  • name is a predefined string literal. The accepted values are:
    • add โ€” Returns the sum of its two operands.
    • div โ€” Returns the quotient of its two operands.
    • max โ€” Returns the largest of its two operands.
    • min โ€” Returns the smallest of its two operands.
    • mul โ€” Returns the product of its two operands.
    • sub โ€” Returns the difference of its two operands.
  • <arguments> is a comma separated list of expressions ๐Ÿง.

For example:

; CHECK: load r[[#REG:]], [r0]
; CHECK: load r[[#REG+1]], [r1]
; CHECK: Loading from 0x[[#%x,ADDR:]]
; CHECK-SAME: to 0x[[#ADDR + 7]]

The above example would match this text:

load r5, [r0]
load r6, [r1]
Loading from 0xa0463440 to 0xa0463447

But it would not match this text:

load r5, [r0]
load r7, [r1]
Loading from 0xa0463440 to 0xa0463443

That's because 7 isn't equal to 5 + 1, and a0463443 isn't equal to a0463440 + 7 ๐Ÿ’€.

Okay so bestie, numeric variables can ALSO be set from a numeric expression โœจ FileCheck checks the expression against the constraint, and if it passes, the variable gets assigned that value. The unified syntax for checking an expression and capturing its value into a numeric variable is [[#%<fmtspec>,<NUMVAR>: <constraint> <expr>]], and each piece works the way we covered earlier ๐ŸŽ€ This is lowkey amazing for making a testcase self-describing, because you can use variable names instead of raw values:

          ; CHECK: mov r[[#REG_OFFSET:]], 0x[[#%X,FIELD_OFFSET:12]]
          ; CHECK-NEXT: load r[[#]], [r[[#REG_BASE:]], r[[#REG_OFFSET]]]

And it would match stuff like this ๐Ÿฆ‹

          mov r4, 0xC
          load r6, [r5, r4]

The --enable-var-scope option has the same effect on numeric variables as it does on string variables, so it's giving consistency ๐ŸŒธ

Important note ๐Ÿšจ: in its current implementation, an expression cannot use a numeric variable defined earlier in the same CHECK directive. Don't get caught out, it's a whole trap ๐Ÿ’€

๐Ÿงš FileCheck Pseudo Numeric Variables

Sometimes you need to verify output that contains line numbers from the match file, like when you're testing compiler diagnostics ๐Ÿ™ˆ The problem is that CHECK: lines hold absolute line numbers in that same file, so every time someone adds or deletes text, those numbers have to be updated. Major fragility energy ๐Ÿ˜ญ

Enter the @LINE pseudo numeric variable ๐Ÿ‘‘ It evaluates to the line number of the CHECK pattern where it's found. That means your match patterns can live right next to the relevant test lines and use relative line number references, like this:

          // CHECK: test.cpp:[[# @LINE + 4]]:6: error: expected ';' after top level declarator
          // CHECK-NEXT: {{^int a}}
          // CHECK-NEXT: {{^     \^}}
          // CHECK-NEXT: {{^     ;}}
          int a

To support legacy uses of @LINE as a special string variable, FileCheck also accepts [[@LINE]], [[@LINE+<offset>]] and [[@LINE-<offset>]] with string substitution block syntax ๐Ÿซถ No spaces inside the brackets, and the offset has to be an integer.

๐ŸŒˆ Matching Newline Characters

Wanna match newline characters in regular expressions? Use the character class [[:space:]] ๐Ÿ’– For example, this pattern:

          // CHECK: DW_AT_location [DW_FORM_sec_offset] ([[DLOC:0x[0-9a-f]+]]){{[[:space:]].*}}"intd"

matches output of this form (from llvm-dwarfdump):

          DW_AT_location [DW_FORM_sec_offset]   (0x00000233)
          DW_AT_name [DW_FORM_strp]  ( .debug_str[0x000000c9] = "intd")

Which lets us set the FileCheck variable DLOC to the value 0x00000233, pulled from the line immediately preceding "intd" โœจ Main character behavior.


โœจ AUTHOR โœจ

Maintained by the LLVM Team (https://llvm.org/) ๐ŸŒธ

2003-2024, LLVM Project ๐Ÿ’œ


And that's the whole page, bestie! ๐Ÿฆ‹ This was the FileCheck manpage, source version 12, dated 2024-07-18. Stay iconic ๐Ÿ’…๐Ÿ’–

๐Ÿ“ฆ where this page comes from