FileCheck - 灵活的模式匹配文件验证器

概要

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

描述

FileCheck 读取两个文件(一个来自标准输入,另一个在命令行中指定),并使用一个文件来验证另一个文件。此行为对于测试套件特别有用,测试套件想要验证某些工具(例如 llc)的输出是否包含预期的信息(例如,来自 esp 或任何有趣内容的 movsd)。这类似于使用 grep,但它针对在一个文件中以特定顺序匹配多个不同的输入进行了优化。

match-filename 文件指定包含要匹配的模式的文件。要验证的文件从标准输入读取,除非使用了 --input-file 选项。

选项

选项从环境变量 FILECHECK_OPTS 和命令行解析。

-help

打印命令行选项的摘要。

--check-prefix prefix

FileCheck 在 match-filename 的内容中搜索要匹配的模式。默认情况下,这些模式以 “CHECK:” 为前缀。如果您想使用不同的前缀(例如,因为同一个输入文件正在检查多个不同的工具或选项),--check-prefix 参数允许您指定(不带尾随 “:”)一个或多个要匹配的前缀。多个前缀对于可能因不同的运行选项而更改的测试很有用,但大多数行保持不变。

FileCheck 不允许重复的前缀,即使一个是检查前缀,另一个是注释前缀(请参阅下面的 --comment-prefixes)。

--check-prefixes prefix1,prefix2,...

作为 --check-prefix 的别名,允许将多个前缀指定为逗号分隔的列表。

--comment-prefixes prefix1,prefix2,...

默认情况下,FileCheck 忽略 match-filename 中任何检查前缀的任何出现,如果在同一行中它前面有 “COM:” 或 “RUN:”。有关用法详细信息,请参阅 “COM:” 指令 部分。

如果默认注释前缀不适合您的测试环境,则可以通过 --comment-prefixes 覆盖这些默认注释前缀。但是,在 LLVM 基于 LIT 的测试套件中,不建议这样做,如果所有测试套件都遵循一致的注释样式,则应该更容易维护。在这种情况下,请考虑建议更改默认注释前缀。

--allow-unused-prefixes

当使用 --check-prefix--check-prefixes 指定多个前缀,并且测试文件中缺少其中一些前缀时,此选项控制行为。如果为 true,则允许这样做;如果为 false,FileCheck 将报告错误,列出缺少的的前缀。默认值为 false。

--input-file filename

要检查的文件(默认为 stdin)。

--match-full-lines

默认情况下,FileCheck 允许在行上的任何位置进行匹配。此选项将要求所有正匹配覆盖整行。除非还指定了 --strict-whitespace,否则将忽略前导和尾随空格。(注意:来自 CHECK-NOT 的负匹配不受此选项的影响!)

传递此选项等效于在每个正检查模式之前插入 {{^ *}}{{^}},并在之后插入 {{ *$}}{{$}}

--strict-whitespace

默认情况下,FileCheck 规范化输入水平空格(空格和制表符),这使其忽略这些差异(空格将匹配制表符)。--strict-whitespace 参数禁用此行为。在所有模式下,行尾序列都被规范化为 UNIX 样式的 \n

--ignore-case

默认情况下,FileCheck 使用区分大小写的匹配。此选项使 FileCheck 使用不区分大小写的匹配。

--implicit-check-not check-pattern

在正检查之间添加指定模式的隐式负检查。该选项允许编写更严格的测试,而无需用 CHECK-NOT 填充它们。

例如,当测试来自没有类似于 clang -verify 的选项的工具的诊断消息时,“--implicit-check-not warning:” 可能很有用。使用此选项,FileCheck 将验证输入是否不包含任何 CHECK: 模式未涵盖的警告。

--dump-input <value>

将输入转储到 stderr,添加表示当前启用的诊断的注释。当此选项多次出现时,下面列表中最早出现的 <value> 具有优先权。默认值为 fail

  • help - 解释输入转储并退出

  • always - 始终转储输入

  • fail - 失败时转储输入

  • never - 永不转储输入

--dump-input-context <N>

--dump-input 请求的转储中,在 --dump-input-filter 指定的任何行之前打印 <N> 行输入行,并在之后打印 <N> 行输入行。当此选项多次出现时,指定的最大 <N> 具有优先权。默认值为 5。

--dump-input-filter <value>

--dump-input 请求的转储中,仅打印 <value> 类型的输入行以及 --dump-input-context 指定的任何上下文。当此选项多次出现时,下面列表中最早出现的 <value> 具有优先权。当 --dump-input=fail 时,默认值为 error,当 --dump-input=always 时,默认值为 all

  • all - 所有输入行

  • annotation-full - 带有注释的输入行

  • annotation - 带有注释起点的输入行

  • error - 带有错误注释起点的输入行

--enable-var-scope

启用正则表达式变量的作用域。

名称以 $ 开头的变量被认为是全局变量,并在整个文件中保持设置。

所有其他变量在每次遇到 CHECK-LABEL 后都会变为未定义。

-D<VAR=VALUE>

设置一个 filecheck 模式变量 VAR,其值为 VALUE,可以在 CHECK: 行中使用。

-D#<FMT>,<NUMVAR>=<NUMERIC EXPRESSION>

将格式为 FMT 的 filecheck 数字变量 NUMVAR 设置为计算 <NUMERIC EXPRESSION> 的结果,该结果可以在 CHECK: 行中使用。有关支持的数值表达式的详细信息,请参阅 FileCheck 数字变量和表达式 部分。

-version

显示此程序的版本号。

-v

打印良好的指令模式匹配。但是,如果 -dump-input=fail-dump-input=always,则将这些匹配项添加为输入注释。

-vv

打印有助于诊断 FileCheck 内部问题的信息,例如丢弃的重叠 CHECK-DAG: 匹配项、隐式 EOF 模式匹配项以及没有匹配项的 CHECK-NOT: 模式。暗示 -v。但是,如果 -dump-input=fail-dump-input=always,则只需将该信息添加为输入注释。

--allow-deprecated-dag-overlap

允许在一组连续的 CHECK-DAG: 指令的匹配之间重叠。此选项已弃用,仅为方便起见而提供,因为旧测试已迁移到新的非重叠 CHECK-DAG: 实现。

--allow-empty

允许检查空输入。默认情况下,空输入被拒绝。

--color

在输出中使用颜色(默认情况下自动检测)。

退出状态

如果 FileCheck 验证文件是否与预期内容匹配,则它以 0 退出。否则,如果不是,或者如果发生错误,它将以非零值退出。

教程

FileCheck 通常用于 LLVM 回归测试,在测试的 RUN 行中调用。从 RUN 行使用 FileCheck 的一个简单示例看起来像这样

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

此语法表示将当前文件(“%s”)通过管道传递到 llvm-as,然后通过管道传递到 llc,然后将 llc 的输出通过管道传递到 FileCheck。这意味着 FileCheck 将根据指定的文件名参数(由 “%s” 指定的原始 .ll 文件)验证其标准输入(llc 输出)。要了解其工作原理,让我们看一下 .ll 文件的其余部分(在 RUN 行之后)

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
}

在这里,您可以看到注释中指定的一些 “CHECK:” 行。现在您可以看到文件是如何通过管道传递到 llvm-as,然后是 llc,而机器代码输出是我们要验证的内容。FileCheck 检查机器代码输出,以验证它是否与 “CHECK:” 行指定的内容匹配。

CHECK:” 行的语法非常简单:它们是必须按顺序出现的固定字符串。FileCheck 默认忽略水平空格差异(例如,允许空格匹配制表符),但除此之外,“CHECK:” 行的内容必须与测试文件中的某些内容完全匹配。

FileCheck(与 grep 相比)的一个优点是它允许将测试用例合并到逻辑组中。例如,由于上面的测试正在检查 “sub1:” 和 “inc4:” 标签,因此除非在这些标签之间存在 “subl”,否则它将不匹配。如果它存在于文件中的其他位置,那将不算数:“grep subl” 在 “subl” 存在于文件中的任何位置时都会匹配。

FileCheck -check-prefix 选项

FileCheck -check-prefix 选项允许从一个 .ll 文件驱动多个测试配置。这在许多情况下都很有用,例如,使用 llc 测试不同的架构变体。这是一个简单的例子

; 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
}

在这种情况下,我们正在测试使用 32 位和 64 位代码生成时,我们是否获得了预期的代码生成。

“COM:” 指令

有时您想要禁用 FileCheck 指令而不完全删除它,或者您想要编写提及指令名称的注释。“COM:” 指令使这变得容易。例如,您可能有

; 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

如果没有 “COM:”,您将需要使用重写和指令语法篡改的某种组合,以防止 FileCheck 将上面注释掉的 “X32:” 和 “X64:” 识别为指令。此外,已经提出了 FileCheck 诊断,可能会抱怨上面没有尾随 “:” 的 “X64” 出现,因为它们看起来像指令拼写错误。对于测试作者来说,避免所有这些问题可能很乏味,而指令语法篡改可能会使测试代码的目的不明确。“COM:” 避免了所有这些问题。

一些重要的使用说明

  • 另一个指令模式中的 “COM:不会 注释掉模式的其余部分。例如

    ; X32: pinsrd $1, 4(%esp), %xmm0 COM: This is part of the X32 pattern!
    

    如果您需要临时注释掉指令模式的一部分,请将其移动到另一行。原因是 FileCheck 以与任何其他指令相同的方式解析 “COM:”:只有行上的第一个指令被识别为指令。

  • 为了 LIT 的缘故,FileCheck 将 “RUN:” 视为与 “COM:” 相同。如果这不适合您的测试环境,请参阅 --comment-prefixes

  • 如果 “COM”、“RUN” 或任何用户定义的注释前缀与常见的检查指令后缀(例如 “-NEXT:” 或 “-NOT:”,如下所述)组合使用,则 FileCheck 不会将它们识别为注释指令。FileCheck 将这种组合视为纯文本。如果它需要充当您的测试环境的注释指令,请使用 --comment-prefixes 将其定义为注释指令。

“CHECK-NEXT:” 指令

有时您想匹配行,并且想验证匹配是否发生在完全连续的行上,并且它们之间没有其他行。在这种情况下,您可以使用 “CHECK:” 和 “CHECK-NEXT:” 指令来指定这一点。如果您指定了自定义检查前缀,只需使用 “<PREFIX>-NEXT:”。例如,像这样的东西可以按您期望的方式工作

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:” 指令与其之前的指令之间只有一个换行符,否则它会拒绝输入。“CHECK-NEXT:” 不能是文件中的第一个指令。

“CHECK-SAME:” 指令

有时您想匹配行,并且想验证匹配是否与上一个匹配发生在同一行上。在这种情况下,您可以使用 “CHECK:” 和 “CHECK-SAME:” 指令来指定这一点。如果您指定了自定义检查前缀,只需使用 “<PREFIX>-SAME:”。

CHECK-SAME:” 与 “CHECK-NOT:”(如下所述)结合使用时特别强大。

例如,以下内容的工作方式与您期望的相同

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

; CHECK:       !DILocation(line: 5,
; CHECK-NOT:               column:
; CHECK-SAME:              scope: ![[SCOPE:[0-9]+]]

如果 “CHECK-SAME:” 指令与其之前的指令之间有任何换行符,则它会拒绝输入。

CHECK-SAME:” 也可用于避免为不相关的字段编写匹配器。例如,假设您正在编写一个测试,该测试解析生成如下输出的工具

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

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

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

要编写一个测试来验证 foo 的值为 1,您可能首先编写这个

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

但是,这将是一个糟糕的测试:如果 foo 的值发生更改,则测试仍然会通过,因为 “CHECK: Value: 1” 行将匹配来自 baz 的值。要解决此问题,您可以为每个 FieldN: 行添加 CHECK-NEXT 匹配器,但这将很冗长,并且需要在添加 Field4 时进行更新。使用 “CHECK-SAME:” 匹配器编写测试的更简洁方法如下

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

这验证了下次Value:” 出现在输出中时,它的值为 1

注意:“CHECK-SAME:” 不能是文件中的第一个指令。

“CHECK-EMPTY:” 指令

如果您需要检查下一行上没有任何内容,甚至没有空格,则可以使用 “CHECK-EMPTY:” 指令。

declare void @foo()

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

就像 “CHECK-NEXT:” 一样,如果在其找到下一个空白行之前有多个换行符,则该指令将失败,并且它不能是文件中的第一个指令。

“CHECK-NOT:” 指令

CHECK-NOT:” 指令用于验证字符串是否未在两个匹配项之间(或在第一个匹配项之前或最后一个匹配项之后)出现。例如,要验证转换是否删除了加载,可以使用如下测试

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
}

“CHECK-COUNT:” 指令

如果您需要一遍又一遍地使用相同的模式匹配多行,则可以根据需要重复多次普通的 CHECK:。如果这看起来太枯燥,您可以改用计数检查 “CHECK-COUNT-<num>:”,其中 <num> 是一个正十进制数。它将精确匹配模式 <num> 次,不多也不少。如果您指定了自定义检查前缀,只需使用 “<PREFIX>-COUNT-<num>:” 即可获得相同的效果。这是一个简单的例子

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]+}}

“CHECK-DAG:” 指令

如果需要匹配不按严格顺序出现的字符串,则可以使用 “CHECK-DAG:” 在两个匹配项之间(或在第一个匹配项之前或最后一个匹配项之后)验证它们。例如,clang 以相反的顺序发出 vtable 全局变量。使用 CHECK-DAG:,我们可以保持自然顺序的检查

// 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: 指令可以与 CHECK-DAG: 指令混合使用,以排除周围 CHECK-DAG: 指令之间的字符串。因此,周围的 CHECK-DAG: 指令不能重新排序,即所有匹配 CHECK-DAG:CHECK-NOT: 之前的出现次数不得落后于匹配 CHECK-DAG:CHECK-NOT: 之后的出现次数。例如,

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

这种情况将拒绝 BEFORE 出现在 AFTER 之后的输入字符串。

使用捕获的变量,CHECK-DAG: 能够匹配 DAG 的有效拓扑排序,其中边从变量的定义到其使用。例如,当您的测试用例需要匹配指令调度程序的不同输出序列时,它很有用。例如,

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

在这种情况下,将允许两个 add 指令的任何顺序。

如果您在同一个 CHECK-DAG: 块中定义使用变量,请注意,定义规则可以在其使用之后匹配。

因此,例如,以下代码将通过

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

而这段代码将不会通过

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

虽然这可能非常有用,但也存在危险,因为在寄存器序列的情况下,您必须具有严格的顺序(先读后写、先复制后使用等)。如果您要查找的定义与(由于编译器中的错误)不匹配,则它可能会在离使用位置更远的地方匹配,并掩盖真正的错误。

在这些情况下,要强制执行顺序,请在 DAG 块之间使用非 DAG 指令。

CHECK-DAG: 指令跳过与同一 CHECK-DAG: 块中任何先前 CHECK-DAG: 指令的匹配项重叠的匹配项。这种非重叠行为不仅与其他指令一致,而且对于处理非唯一字符串或模式集也是必要的。例如,以下指令在并行程序(例如 OpenMP 运行时)中查找两个任务的无序日志条目

// 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

即使模式相同,甚至日志条目的文本相同,因为线程 ID 设法被重用,第二对指令也保证不会匹配与第一对指令相同的日志条目。

“CHECK-LABEL:” 指令

有时,在包含划分为逻辑块的多个测试的文件中,一个或多个 CHECK: 指令可能会通过匹配稍后块中的行而无意中成功。虽然通常最终会生成错误,但标记为导致错误的检查可能实际上与问题的实际来源没有任何关系。

为了在这些情况下生成更好的错误消息,可以使用 “CHECK-LABEL:” 指令。它的处理方式与普通的 CHECK 指令相同,只是 FileCheck 额外假设由该指令匹配的行也不能被 match-filename 中存在的任何其他检查匹配;这旨在用于包含标签或其他唯一标识符的行。从概念上讲,CHECK-LABEL 的存在将输入流划分为单独的块,每个块都独立处理,防止一个块中的 CHECK: 指令匹配另一个块中的行。如果 --enable-var-scope 生效,则所有局部变量在块的开头被清除。

例如,

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:

在这种情况下使用 CHECK-LABEL: 指令可确保三个 CHECK: 指令仅接受与 @C_ctor_base 函数主体对应的行,即使模式与文件中稍后找到的行匹配也是如此。此外,如果这三个 CHECK: 指令之一失败,FileCheck 将通过继续到下一个块来恢复,从而允许在单个调用中检测到多个测试失败。

CHECK-LABEL: 指令不需要包含与源语言或输出语言中的实际语法标签对应的字符串:它们只需唯一匹配被验证文件中的单行。

CHECK-LABEL: 指令不能包含变量定义或使用。

指令修饰符

指令修饰符可以附加到指令,方法是在指令后跟上 {<modifier>},其中 <modifier> 唯一支持的值是 LITERAL

LITERAL 指令修饰符可用于执行字面量匹配。此修饰符使指令不识别任何用于执行正则表达式匹配、变量捕获或任何替换的语法。当要匹配的文本需要过多的转义时,这非常有用。例如,以下内容将执行字面量匹配,而不是将它们视为正则表达式:

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 正则表达式匹配语法

所有 FileCheck 指令都接受一个要匹配的模式。对于 FileCheck 的大多数用途,固定字符串匹配完全足够。对于某些情况,需要更灵活的匹配形式。为了支持这一点,FileCheck 允许您在匹配字符串中指定正则表达式,用双大括号括起来:{{yourregex}}。FileCheck 实现了 POSIX 正则表达式匹配器;它支持扩展 POSIX 正则表达式 (ERE)。因为我们希望将固定字符串匹配用于我们所做的大部分工作,所以 FileCheck 的设计旨在支持混合和匹配固定字符串匹配和正则表达式。这允许您编写如下内容:

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

在这种情况下,将允许来自 ESP 寄存器的任何偏移量,并且将允许任何 xmm 寄存器。

由于正则表达式用双大括号括起来,因此它们在视觉上是不同的,并且您不需要像在 C 语言中那样在双大括号内使用转义字符。在极少数情况下,如果您想从输入中显式匹配双大括号,您可以像 {{[}][}]}} 这样使用一些难看的模式。或者,如果您使用重复计数语法,例如 [[:xdigit:]]{8} 来匹配正好 8 个十六进制数字,您需要添加括号,例如 {{([[:xdigit:]]{8})}},以避免与 FileCheck 的结束双大括号混淆。

FileCheck 字符串替换块

通常,匹配一个模式,然后验证它是否稍后再次出现在文件中很有用。对于代码生成测试,这对于允许任何寄存器,但验证该寄存器稍后是否被一致地使用可能很有用。为此,FileCheck 支持字符串替换块,允许定义字符串变量并将其替换到模式中。这是一个简单的例子:

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

第一行检查匹配正则表达式 %[a-z]+ 并将其捕获到字符串变量 REGISTER 中。第二行验证 REGISTER 中的内容是否稍后出现在文件中 “andw” 之后。FileCheck 字符串替换块始终包含在 [[ ]] 对中,并且字符串变量名称可以使用正则表达式 \$[a-zA-Z_][a-zA-Z0-9_]* 构成。如果名称后跟冒号,则它是变量的定义;否则,它是替换。

FileCheck 变量可以多次定义,并且替换始终获取最新的值。变量也可以在定义它们的同一行稍后被替换。例如:

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

如果您希望 op 的操作数是相同的寄存器,并且不关心具体是哪个寄存器,这可能很有用。

如果 --enable-var-scope 生效,则名称以 $ 开头的变量被认为是全局变量。所有其他变量都是局部变量。所有局部变量在每个 CHECK-LABEL 块的开头都会变为未定义。全局变量不受 CHECK-LABEL 的影响。这使得更容易确保各个测试不受先前测试中设置的变量的影响。

FileCheck 数字替换块

FileCheck 还支持数字替换块,允许定义数字变量并检查满足基于这些变量的数字表达式约束的数字值,通过数字替换。这允许 CHECK: 指令验证两个数字之间的数字关系,例如需要使用连续的寄存器。

捕获数值的语法是 [[#%<fmtspec>,<NUMVAR>:]],其中:

  • %<fmtspec>, 是一个可选的格式说明符,用于指示要匹配的数字格式以及期望的最小位数。

  • <NUMVAR>: 是可选的变量 <NUMVAR> 定义,来自捕获的值。

<fmtspec> 的语法是:#.<precision><conversion specifier>,其中:

  • # 是一个可选标志,可用于十六进制值(参见下面的 <conversion specifier>),它要求匹配的值以 0x 为前缀。

  • .<precision> 是一个可选的 printf 样式精度说明符,其中 <precision> 指示匹配的值必须具有的最小位数,如果需要,则期望前导零。

  • <conversion specifier> 是一个可选的 scanf 样式转换说明符,用于指示要匹配的数字格式(例如,十六进制数)。当前接受的格式说明符是 %u%d%x%X。如果缺席,格式说明符默认为 %u

例如:

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

将匹配 mov r5, 0x0000FEFE,并将 REG 设置为值 5,将 ADDR 设置为值 0xFEFE。请注意,由于精度,它将无法匹配 mov r5, 0xFEFE

由于数字变量定义是可选的,因此可以仅检查数字值是否以给定格式存在。当值本身没有用处时,这可能很有用,例如:

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

检查值是否是合成的而不是移动的。

数字替换的语法是 [[#%<fmtspec>, <constraint> <expr>]],其中:

  • <fmtspec> 是与定义变量相同的格式说明符,但在这种上下文中指示如何将数字表达式值与匹配项进行比较。如果缺席,则格式说明符的两个组成部分都从表达式约束使用的任何数字变量的匹配格式中推断出来,如果未使用数字变量,则默认为 %u,表示该值应为无符号,且没有前导零。如果多个数字变量的格式说明符之间存在冲突,则转换说明符变为强制性的,但精度说明符仍然是可选的。

  • <constraint> 是描述要匹配的值必须如何与数字表达式的值相关的约束。当前唯一接受的约束是 == 用于精确匹配,如果未提供 <constraint>,则为默认值。当 <expr> 为空时,不得指定匹配约束。

  • <expr> 是一个表达式。表达式又被递归地定义为:

    • 一个数字操作数,或

    • 一个表达式后跟一个运算符和一个数字操作数。

    数字操作数是先前定义的数字变量、整数文字或函数。在这些元素的任何一个之前、之后和之间都接受空格。数字操作数具有 64 位精度。溢出和下溢被拒绝。不支持运算符优先级,但可以使用括号来更改求值顺序。

支持的运算符有:

  • + - 返回其两个操作数的和。

  • - - 返回其两个操作数的差。

函数调用的语法是 <name>(<arguments>),其中:

  • name 是一个预定义的字符串文字。接受的值是:

    • add - 返回其两个操作数的和。

    • div - 返回其两个操作数的商。

    • max - 返回其两个操作数中最大的一个。

    • min - 返回其两个操作数中最小的一个。

    • mul - 返回其两个操作数的积。

    • sub - 返回其两个操作数的差。

  • <arguments> 是一个逗号分隔的表达式列表。

例如:

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

上面的例子将匹配文本:

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

但不会匹配文本:

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

由于 7 不等于 5 + 1,并且 a0463443 不等于 a0463440 + 7

数字变量也可以定义为数字表达式的结果,在这种情况下,将检查数字表达式约束,如果验证成功,则将变量赋值为该值。因此,用于检查数字表达式并将其值捕获到数字变量中的统一语法是 [[#%<fmtspec>,<NUMVAR>: <constraint> <expr>]],每个元素如前所述。可以使用此语法通过使用变量而不是值来使测试用例更具自描述性:

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

这将匹配:

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

--enable-var-scope 选项对数字变量和字符串变量具有相同的效果。

重要提示:在其当前实现中,表达式不能使用在同一 CHECK 指令中较早定义的数字变量。

FileCheck 伪数字变量

有时需要验证包含匹配文件行号的输出,例如在测试编译器诊断时。这引入了匹配文件结构的某些脆弱性,因为 “CHECK:” 行包含同一文件中的绝对行号,这些行号在由于文本添加或删除而导致行号更改时必须更新。

为了支持这种情况,FileCheck 表达式理解 @LINE 伪数字变量,它评估为找到它的 CHECK 模式的行号。

这样,匹配模式可以放在相关的测试行附近,并包括相对行号引用,例如:

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

为了支持 @LINE 作为特殊字符串变量的旧用法,FileCheck 也接受以下使用带有字符串替换块语法的 @LINE 的方式:[[@LINE]][[@LINE+<offset>]][[@LINE-<offset>]],括号内没有任何空格,其中 offset 是一个整数。

匹配换行符

要在正则表达式中匹配换行符,可以使用字符类 [[:space:]]。例如,以下模式:

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

匹配以下形式的输出(来自 llvm-dwarfdump):

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

让我们将 FileCheck 变量 DLOC 设置为期望值 0x00000233,从紧接在 “intd” 之前的行中提取。