FileCheck - 靈活的模式匹配文件驗證器

概要

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

描述

FileCheck 讀取兩個文件(一個從標準輸入,另一個在命令列指定),並使用其中一個來驗證另一個。這種行為對於測試套件特別有用,它希望驗證某些工具(例如 llc)的輸出是否包含預期資訊(例如,來自 esp 的 movsd 或任何有趣的東西)。這類似於使用 grep,但它針對以特定順序匹配一個文件中的多個不同輸入進行了優化。

匹配文件名 文件指定包含要匹配模式的文件。除非使用 --input-file 選項,否則將從標準輸入讀取要驗證的文件。

選項

選項從環境變數 FILECHECK_OPTS 和命令列解析。

-help

列印命令列選項的摘要。

--check-prefix 前綴

FileCheck 在 匹配文件名 的內容中搜尋要匹配的模式。默認情況下,這些模式以「CHECK:」為前綴。如果您想使用不同的前綴(例如,因為同一個輸入文件正在檢查多個不同的工具或選項),則 --check-prefix 參數允許您指定(不帶尾隨「:」)一個或多個要匹配的前綴。多個前綴對於可能因不同運行選項而改變但大多數行保持不變的測試很有用。

FileCheck 不允許重複的前綴,即使一個是檢查前綴而另一個是註釋前綴(請參閱下面的 --comment-prefixes)。

--check-prefixes 前綴1,前綴2,...

--check-prefix 的別名,允許以逗號分隔清單指定多個前綴。

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

預設情況下,如果在 match-filename 中的任何檢查前綴在同一行前面加上「COM:」或「RUN:」,則 FileCheck 會忽略該檢查前綴。有關使用細節,請參閱「COM:」指令一節。

如果這些預設註釋前綴不適合您的測試環境,則可以使用 --comment-prefixes 覆蓋它們。但是,不建議在 LLVM 的基於 LIT 的測試套件中這樣做,因為如果所有測試套件都遵循一致的註釋風格,則應該更容易維護。在這種情況下,請考慮提出對預設註釋前綴的更改。

--allow-unused-prefixes

此選項控制當使用由 --check-prefix--check-prefixes 指定的多個前綴時,以及測試檔案中缺少其中一些前綴時的行為。如果為 true,則允許這樣做;如果為 false,FileCheck 將會回報錯誤,並列出缺少的前綴。預設值為 false。

--input-file filename

要檢查的檔案(預設為標準輸入)。

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

例如,「--implicit-check-not warning:」在測試沒有類似 clang -verify 選項的工具的診斷訊息時非常有用。使用此選項,FileCheck 將驗證輸入不包含任何 CHECK: 模式未涵蓋的警告。

--dump-input <value>

將輸入轉儲到標準錯誤輸出,並添加表示當前啟用診斷的註釋。如果此選項出現多次,則下方列表中最先出現的 <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>

設定一個 FileCheck 數字變數 NUMVAR,其格式為 FMT,並將其設定為評估 <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:” 行的內容與測試文件中的某些內容完全匹配。

與 grep 相比,FileCheck 的一個優點是它允許將測試案例合併到邏輯組中。例如,由於上面的測試正在檢查 “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 以相反的順序發出虛擬函式表全域變數。使用 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

這種情況將拒絕 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」之前的行中提取。