LLVM中FileCheck开发者工具–2--入门教程
FileCheck使用
FileCheck
通常在LLVM回归测试中使用,在测试的RUN行中调用。在RUN所在的那一行,使用FileCheck
的一个简单例子如下:
; RUN: llvm-as < %s | llc -march=x86-64 | FileCheck %s
%s
代表当前的文件。FileCheck
将根据指定的文件名参数(由%s
指定的原始.ll文件)验证其标准输入(llc命令的输出)。让我们查看剩下部分的.ll文件(RUN行后的部分),来了解FileCheck
是如何运作的:
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:"行。"CHECK:"行的语法非常简单:它们是固定的字符串,必须按顺序出现。FileCheck
默认忽略"水平空白符"的差异(如:空格允许匹配上tab)。但"CHECK:"行的内容必须与测试文件中某些内容完全匹配。
FileCheck
比较好的是它允许测试用例合并到逻辑组。例如:由于上面的测试代码中检查了"sub1"和"inc4"两个标签,“subl"将直到出现在这两个标准之间,才会成功匹配,而其他位置的"subl"标签则不会匹配成功。相反"grep subl"将会匹配文件的任何位置的"subl”。
-check-prefix选项
-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
}
CHECK-NEXT指令
在你希望匹配行并且想验证匹配紧接着那一行时,可以使用"CHECK"和"CHECK-NEXT"指令。如果你有自定义的CHECK前缀,使用"-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*"只能相隔一个换行符。"CHECK-NEXT"不能是文件中的第一个指令。
CHECK-SAME指令
有时你想验证匹配和之前的匹配是否发生在同一行。这时你可以使用"CHECK:“和"CHECK-SAME:“指令。如果你有自定义的CHECK前缀,使用”-NEXT:”。下面是一个例子:
!0 = !DILocation(line: 5, scope: !1, inlinedAt: !2)
; CHECK: !DILocation(line: 5,
; CHECK-NOT: column:
; CHECK-SAME: scope: ![[SCOPE:[0-9]+]]
CHECK-EMPTY指令
如果需要检查下一行是否没有任何内容,甚至没有空格,可以使用"CHECK-EMPTY:"指令。下面是一个例子:
declare void @foo()
declare void @bar()
; CHECK: foo
; CHECK-EMPTY:
; CHECK-NEXT: bar
就像"CHECK-NEXT:"一样,如果在查找下一个空白行之前有多个换行符,则该指令将失败,并且该指令不能成为文件中的第一个指令。
CHECK-NOT指令
"CHECK-NOT:"指令用于验证两个匹配项之间(或第一个匹配项之前或最后一个匹配项之后)没有出现字符串。 例如,要验证是否已通过转换除去了load,可以使用如下测试:
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-COUNT- :",其中是正十进制数。 它将与模式精确匹配次,不多也不少。 如果指定了自定义检查前缀,则只需使用" -COUNT- :"即可达到相同的效果。 这是一个简单的示例:
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-NOT:"之前匹配所有"CHECK-DAG:"的出现都不能落后于"CHECK-NOT:"之后匹配"CHECK-DAG:"的出现。 例如,
; CHECK-DAG: BEFORE
; CHECK-NOT: NOT
; CHECK-DAG: AFTER
这种情况将拒绝在AFTER之后发生BEFORE的输入字符串。
使用捕获的变量,“ 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]]
在这种情况下,将允许该两个添加指令的任何顺序。
如果要在同一"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:"伪指令不能包含变量定义或使用。
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:指令验证两个数字之间的数字关系,例如需要使用连续的寄存器。
定义数字变量的语法为:
[[#%,:]]
其中:%是可选的scanf样式匹配格式说明符,用于指示要匹配的数字格式(例如,十六进制数字)。 当前接受的格式说明符是%u,%x和%X。 如果不存在,则格式说明符默认为%u。
是要定义为匹配值的数字变量的名称。
例如:
; CHECK: mov r[[#REG:]], 0x[[#%X,IMM:]]
上面的例子将会匹配下面的代码
mov r5, 0xF0F0
REG变量将会设置为5,IMM的值将会设置为0xF0F0。
数字表达式的替换语法为:
[[#%,]]
其中:%与用于定义数字变量的匹配格式说明符相同,但用作printf样式的格式,用于指示应如何匹配数字表达式值。 如果不存在,则从表达式约束(如果有)使用的数字变量的匹配格式中推断格式说明符,如果不使用数字变量,则默认为%u。 如果多个数字变量的匹配格式之间存在冲突,则格式说明符是强制性的。
是一个表达式。 表达式又递归定义为:
- 数字操作数
- 一个表达式,后跟一个运算符和一个数字操作数。
数字操作数是先前定义的数字变量或整数文字。 支持的运算符为+和-。 在任何这些元素之前,之后和之间都接受空格。
例如:
; CHECK: load r[[#REG:]], [r0]
; CHECK: load r[[#REG+1]], [r1]
; CHECK: Loading from 0x[[#%x,ADDR:] 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
。
该语法还支持一个空表达式,等效于编写{{[0-9]+}}。对于输入必须包含数字值但值本身无关紧要,如下:
; CHECK-NOT: mov r0, r[[#]]
检查值是综合值,而不是四处移动。
也可以将数字变量定义为数字表达式的结果,在这种情况下,将检查数字表达式,如果已验证,则将变量分配给该值。 因此,定义数字变量和检查数字表达式的统一语法是[[#%,:]],如前所述。 可以使用这种语法通过使用变量而不是值来使测试用例更加自我描述:
; 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
选项对数字变量和字符串变量具有相同的作用。
Note:
在当前定义的变量中,不能在先前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 + ]]和[[@ LINE- []]在方括号内没有任何空格,其中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"之前的行中提取的。