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

概要

FileCheck 匹配文件名 [–check-prefix=XXX] [–strict-whitespace]

描述

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

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

选项

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

-help

打印命令行选项的摘要。

--check-prefix 前缀

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

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

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

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

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

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

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

--allow-unused-prefixes

此选项控制在使用 --check-prefix--check-prefixes 指定多个前缀时,以及其中一些前缀在测试文件中丢失时的行为。如果为真,则允许,如果为假,FileCheck 将报告错误,列出丢失的前缀。默认值为假。

--input-file 文件名

要检查的文件(默认为标准输入)。

--match-full-lines

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

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

--strict-whitespace

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

--ignore-case

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

--implicit-check-not 检查模式

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

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

--dump-input <值>

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

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

  • always - 始终转储输入

  • fail - 失败时转储输入

  • never - 从不转储输入

--dump-input-context <N>

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

--dump-input-filter <值>

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

  • all - 所有输入行

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

  • annotation - 输入包含注释起点的行

  • error - 输入包含错误注释起点的行

--enable-var-scope

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

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

所有其他变量在遇到每个CHECK-LABEL后都会被取消定义。

-D<VAR=VALUE>

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

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

将格式匹配FMT的文件检查数值变量NUMVAR设置为计算结果<NUMERIC EXPRESSION>,可以在CHECK:行中使用。有关支持的数值表达式的详细信息,请参见FileCheck Numeric Variables and Expressions部分。

-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将根据指定的filename参数(由“%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+<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”之前的行中提取。