LLVM 測試基礎架構指南

概觀

本文件是 LLVM 測試基礎架構的參考手冊。它記載了 LLVM 測試基礎架構的結構、使用它所需的工具,以及如何新增和執行測試。

需求

為了使用 LLVM 測試基礎架構,您需要建置 LLVM 所需的所有軟體,以及 Python 3.8 或更高版本。

LLVM 測試基礎架構組織

LLVM 測試基礎架構包含三大類測試:單元測試、回歸測試和完整程式。單元測試和回歸測試包含在 LLVM 儲存庫本身中,分別位於 llvm/unittestsllvm/test 之下,預計會始終通過 - 它們應該在每次提交之前執行。

完整程式測試稱為「LLVM 測試套件」(或「測試套件」),位於 GitHub 上的 test-suite 儲存庫 中。由於歷史原因,這些測試在某些地方也被稱為「夜間測試」,這比「測試套件」更明確,並且仍然在使用中,儘管我們執行它們的頻率遠高於夜間。

單元測試

單元測試使用 Google TestGoogle Mock 撰寫,並且位於 llvm/unittests 目錄中。一般來說,單元測試保留用於目標支援庫和其他通用資料結構,我們更喜歡依賴回歸測試來測試 IR 上的轉換和分析。

回歸測試

迴歸測試是一些小段程式碼,用於測試 LLVM 的特定功能或觸發 LLVM 中的特定錯誤。它們所使用的語言取決於被測試的 LLVM 部分。這些測試由 Lit 測試工具(它是 LLVM 的一部分)驅動,並且位於 llvm/test 目錄中。

通常,當在 LLVM 中發現錯誤時,應編寫一個僅包含足以重現問題的程式碼的迴歸測試,並將其放置在此目錄下的某個位置。例如,它可以是從實際應用程序或基準測試中提取的一小段 LLVM IR。

分析測試

分析是一種 pass,它推斷 IR 某些部分的屬性而不對其進行轉換。它們通常使用與迴歸測試相同的基礎結構進行測試,方法是創建一個單獨的“打印器”pass 來使用分析結果,並以適合 FileCheck 的文本格式將其打印到標準輸出中。有關此類測試的示例,請參見 llvm/test/Analysis/BranchProbabilityInfo/loop.ll

test-suite

測試套件包含完整的程式,這些程式是可以編譯和鏈接成獨立程式並執行的程式碼片段。這些程式通常使用高級語言(例如 C 或 C++)編寫。

這些程式使用用戶指定的編譯器和一組標誌進行編譯,然後執行以捕獲程式輸出和時序信息。這些程式的輸出會與參考輸出進行比較,以確保程式已正確編譯。

除了編譯和執行程式之外,完整程式測試還可以用作基準測試 LLVM 性能的一種方式,包括生成的程式的效率以及 LLVM 編譯、優化和生成程式碼的速度。

測試套件位於 GitHub 上的 test-suite 存儲庫 中。

有關詳細信息,請參見 測試套件指南

調試信息測試

測試套件包含用於檢查調試信息質量的測試。測試使用基於 C 語言或 LLVM 組合語言編寫。

這些測試在調試器下編譯和運行。將檢查調試器輸出以驗證調試信息的有效性。有關更多信息,請參見測試套件中的 README.txt。此測試套件位於 cross-project-tests/debuginfo-tests 目錄中。

快速入門

測試位於兩個單獨的存儲庫中。單元測試和迴歸測試位於主“llvm”/ 目錄下的 llvm/unittestsllvm/test 目錄中(因此您可以在主 LLVM 樹中免費獲得這些測試)。在構建 LLVM 後,使用 make check-all 運行單元測試和迴歸測試。

test-suite 模組包含更全面的測試,包括完整的 C 和 C++ 程式。有關詳細信息,請參見 測試套件指南

單元測試和迴歸測試

如欲執行所有 LLVM 單元測試,請使用 check-llvm-unit 目標

% make check-llvm-unit

如欲執行所有 LLVM 迴歸測試,請使用 check-llvm 目標

% make check-llvm

為了獲得合理的測試效能,請在發佈模式下建置 LLVM 及子專案,亦即

% cmake -DCMAKE_BUILD_TYPE="Release" -DLLVM_ENABLE_ASSERTIONS=On

如果您已簽出並建置 Clang,則可以使用以下指令同時執行 LLVM 和 Clang 測試

% make check-all

如欲使用 Valgrind(預設為 Memcheck)執行測試,請使用 LIT_ARGS make 變數將所需選項傳遞至 lit。例如,您可以使用

% make check LIT_ARGS="-v --vg --vg-leak"

來啟用 Valgrind 測試並啟用洩漏檢查。

如欲執行個別測試或測試子集,您可以使用 llvm-lit 腳本,該腳本是 LLVM 的一部分。例如,如欲單獨執行 Integer/BitPacked.ll 測試,您可以執行

% llvm-lit ~/llvm/test/Integer/BitPacked.ll

或執行所有 ARM CodeGen 測試

% llvm-lit ~/llvm/test/CodeGen/ARM

迴歸測試只會在 Python psutil 模組安裝在非使用者位置時才會使用它。在 Linux 下,請使用 sudo 安裝或在虛擬環境中安裝。在 Windows 下,請為所有使用者安裝 Python,然後在提升權限的命令提示字元中執行 pip install psutil

如需有關使用 lit 工具的詳細資訊,請參閱 llvm-lit --helplit 線上手冊

除錯資訊測試

如欲執行除錯資訊測試,只需在 cmake 命令列的 LLVM_ENABLE_PROJECTS 定義中加入 cross-project-tests 專案即可。

迴歸測試結構

LLVM 迴歸測試由 lit 驅動,並位於 llvm/test 目錄中。

此目錄包含大量小型測試,這些測試會測試 LLVM 的各種功能,並確保不會發生迴歸。該目錄分為幾個子目錄,每個子目錄都專注於 LLVM 的特定領域。

撰寫新的迴歸測試

迴歸測試結構非常簡單,但確實需要設定一些資訊。這些資訊是透過 cmake 收集的,並寫入建置目錄中的檔案 test/lit.site.cfg.pyllvm/test Makefile 會為您完成這項工作。

為了讓迴歸測試正常運作,每個測試目錄都必須有一個 lit.local.cfg 檔案。 lit 會尋找此檔案來決定如何執行測試。這個檔案只是 Python 程式碼,因此非常靈活,但我們已將其標準化為 LLVM 迴歸測試。如果您要加入測試目錄,只需從另一個目錄複製 lit.local.cfg 即可開始執行。標準的 lit.local.cfg 只會指定要在哪個檔案中尋找測試。任何只包含目錄的目錄都不需要 lit.local.cfg 檔案。如需詳細資訊,請參閱 Lit 文件

每個測試檔案都必須包含以 “RUN:” 開頭的行,這些行會告訴 lit 如何執行它。如果沒有 RUN 行,lit 在執行測試時會發出錯誤訊息。

RUN 行在測試程式的註解中使用關鍵字 RUN 加上冒號來指定,最後是執行的指令(管道)。這些行共同構成了 lit 用於執行測試案例的「腳本」。RUN 行的語法類似於 Shell 的管道語法,包括 I/O 重定向和變數替換。但是,儘管這些行可能看起來像 Shell 腳本,但它們不是。RUN 行是由 lit 解譯的。因此,語法在某些方面與 Shell 不同。您可以根據需要指定任意數量的 RUN 行。

lit 會對每個 RUN 行執行替換,將 LLVM 工具名稱替換為針對每個工具建置的可執行檔的完整路徑(位於 $(LLVM_OBJ_ROOT)/bin 中)。這可確保 lit 在測試期間不會呼叫使用者路徑中的任何雜散 LLVM 工具。

每個 RUN 行都是獨立執行的,與其他行不同,除非其最後一個字元是 \。這個延續字元會導致 RUN 行與下一行連接。通過這種方式,您可以構建長指令管道,而無需使用很長的單行長度。以 \ 結尾的行會被連接起來,直到找到一個不以 \ 結尾的 RUN 行。然後,這個連接的 RUN 行集就構成了一次執行。 lit 將替換變數並安排管道的執行。如果管道中的任何程序失敗,則整行(和測試案例)也會失敗。

以下是 .ll 檔案中合法 RUN 行的示例

; RUN: llvm-as < %s | llvm-dis > %t1
; RUN: llvm-dis < %s.bc-13 > %t2
; RUN: diff %t1 %t2

與 Unix Shell 一樣,RUN 行允許使用管道和 I/O 重定向。

在編寫 RUN 行時,您必須注意一些引號規則。一般來說,不需要使用引號。 lit 不會去除任何引號字元,因此它們會被傳遞給被呼叫的程式。為避免這種情況,請使用大括號告訴 lit 將括號內的內容視為一個值。

一般來說,您應該儘量保持 RUN 行的簡單性,僅將它們用於執行可以產生文字輸出的工具,然後您可以檢查這些輸出。檢查輸出以確定測試是否通過的建議方法是使用 FileCheck 工具[不建議在 RUN 行中使用 grep - 請勿發送或提交使用它的補丁。]

將相關的測試放在單個檔案中,而不是每個測試都有一個單獨的檔案。檢查是否有已經涵蓋您的功能的檔案,並考慮將您的代碼添加到那裡,而不是創建新的檔案。

在回歸測試中產生斷言

一些回歸測試案例非常龐大和複雜,難以手動編寫/更新。在這種情況下,為了減少人工,我們可以使用 llvm/utils/ 中提供的腳本產生斷言。

例如,要在基於 llc 的測試中產生斷言,在添加一個或多個 RUN 行之後,請使用

% llvm/utils/update_llc_test_checks.py --llc-binary build/bin/llc test.ll

這將產生 FileCheck 斷言,並在頂部插入一行 NOTE: 表示斷言是自動產生的。

如果您想更新現有測試案例中的斷言,請傳遞 -u 選項,該選項首先檢查 NOTE: 行是否存在並與腳本名稱相符。

有時,測試完全依賴手動編寫的斷言,並且不應自動產生斷言。在這種情況下,請在第一行添加文字 NOTE: Do not autogenerate,腳本將會跳過該測試。最好解釋一下為什麼產生的斷言對測試不起作用,以便未來的開發人員了解發生了什麼。

以下是最常見的腳本及其在產生斷言方面的用途/應用

update_analyze_test_checks.py
opt -passes='print<cost-model>'

update_cc_test_checks.py
C/C++, or clang/clang++ (IR checks)

update_llc_test_checks.py
llc (assembly checks)

update_mca_test_checks.py
llvm-mca

update_mir_test_checks.py
llc (MIR checks)

update_test_checks.py
opt

測試的預提交工作流程

如果測試沒有崩潰、斷言或無限迴圈,請先使用基準檢查行提交測試。也就是說,測試將顯示編譯錯誤或缺少優化。添加「TODO」或「FIXME」註釋以指示預期測試中會發生變化。

後續對編譯器進行程式碼更改的補丁程式將會顯示與測試的檢查行差異,因此更容易看到補丁程式的效果。如果問題已解決,請移除上一步中添加的 TODO/FIXME 註釋。

基準測試(無功能變更或 NFC 補丁程式)可以在沒有預提交審查的情況下推送至主線,前提是您具有提交權限。

迴歸測試的最佳實務

  • 盡可能使用自動產生的檢查行(由上述腳本產生)。

  • 包含有關特定測試中測試/預期內容的註釋。如果錯誤追蹤器中有相關問題,請添加對這些錯誤報告的參考(例如,「有關詳細資訊,請參閱 PR999」)。

  • 除非必要,否則避免未定義的行為和毒藥/未定義的值。例如,不要使用 br i1 undef 之類的模式,因為它們可能會因未來的優化而損壞。

  • 透過移除不必要的指令、中繼資料、屬性等來最小化測試。像 llvm-reduce 這樣的工具可以幫助自動執行此操作。

  • 在 PhaseOrdering 測試之外,僅運行最少的一組遍。例如,優先使用 opt -S -passes=instcombine 而不是 opt -S -O3

  • 避免未命名的指令/區塊(例如 %01:),因為它們可能需要在未來的測試修改中重新編號。可以透過 opt -S -passes=instnamer 運行測試來移除這些內容。

  • 嘗試為值(包括變數、區塊和函數)賦予有意義的名稱,並避免保留由優化管道產生的複雜名稱(例如 %foo.0.0.0.0.0.0)。

額外檔案

如果您的測試需要包含 RUN: 行的檔案之外的額外檔案,並且額外檔案很小,請考慮在同一個檔案中指定它們,並使用 split-file 來提取它們。例如,

; RUN: split-file %s %t
; RUN: llvm-link -S %t/a.ll %t/b.ll | FileCheck %s

; CHECK: ...

;--- a.ll
...
;--- b.ll
...

各部分由正則表達式 ^(.|//)--- <part> 分隔。

若想測試相對行號,例如 [[#@LINE+1]],請指定 --leading-lines 來添加前導空行以保留行號。

如果額外檔案很大,習慣上的存放位置是子目錄 Inputs。然後,您可以將額外檔案稱為 %S/Inputs/foo.bar

例如,考慮 test/Linker/ident.ll。目錄結構如下

test/
  Linker/
    ident.ll
    Inputs/
      ident.a.ll
      ident.b.ll

為方便起見,這些是內容

;;;;; ident.ll:

; RUN: llvm-link %S/Inputs/ident.a.ll %S/Inputs/ident.b.ll -S | FileCheck %s

; Verify that multiple input llvm.ident metadata are linked together.

; CHECK-DAG: !llvm.ident = !{!0, !1, !2}
; CHECK-DAG: "Compiler V1"
; CHECK-DAG: "Compiler V2"
; CHECK-DAG: "Compiler V3"

;;;;; Inputs/ident.a.ll:

!llvm.ident = !{!0, !1}
!0 = metadata !{metadata !"Compiler V1"}
!1 = metadata !{metadata !"Compiler V2"}

;;;;; Inputs/ident.b.ll:

!llvm.ident = !{!0}
!0 = metadata !{metadata !"Compiler V3"}

出於對稱性的原因,ident.ll 只是一個虛擬檔案,除了保存 RUN: 行之外,它實際上並不參與測試。

備註

一些現有的測試在額外檔案中使用 RUN: true,而不是將額外檔案放在 Inputs/ 目錄中。此模式已棄用。

精細化測試

通常,IR 和組譯測試檔案會受益於清理以移除不必要的細節。但是,對於需要精細化 IR 或組譯檔案的測試,其中清理不太實際(例如,Clang 輸出的大量偵錯資訊),您可以在名為 gensplit-file 部分中包含產生指令。然後,在測試檔案上執行 llvm/utils/update_test_body.py 以產生所需的內容。

; RUN: rm -rf %t && split-file %s %t && cd %t
; RUN: opt -S a.ll ... | FileCheck %s

; CHECK: hello

;--- a.cc
int va;
;--- gen
clang --target=x86_64-linux -S -emit-llvm -g a.cc -o -

;--- a.ll
# content generated by the script 'gen'
PATH=/path/to/clang_build/bin:$PATH llvm/utils/update_test_body.py path/to/test.ll

該腳本將使用 split-file 準備額外檔案,調用 gen,然後用其標準輸出重寫 gen 之後的部分。

為方便起見,如果測試只需要一個組譯檔案,您也可以使用 .ifdef.endif 包裹 gen 及其所需檔案。然後,您可以在 RUN 行中跳過 split-file

# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o a.o
# RUN: ... | FileCheck %s

# CHECK: hello

.ifdef GEN
#--- a.cc
int va;
#--- gen
clang --target=x86_64-linux -S -g a.cc -o -
.endif
# content generated by the script 'gen'

備註

考慮指定明確的目標三元組,以避免在另一台機器上需要重新產生時的差異。

genPWD 設定為 /proc/self/cwd 的情況下被調用。Clang 命令不需要 -fdebug-compilation-dir=,因為其預設值是 PWD

檢查前綴應放在 .endif 之前,因為 .endif 之後的部分會被替換。

如果測試主體包含多個檔案,您可以在 RUN 行中打印 --- 分隔符號並利用 split-file

# RUN: rm -rf %t && split-file %s %t && cd %t
...

#--- a.cc
int va;
#--- b.cc
int vb;
#--- gen
clang --target=x86_64-linux -S -O1 -g a.cc -o -
echo '#--- b.s'
clang --target=x86_64-linux -S -O1 -g b.cc -o -
#--- a.s

脆弱測試

很容易寫出一個脆弱的測試,如果被測試的工具輸出輸入檔案的完整路徑,則該測試將會錯誤地失敗。例如,opt 預設會輸出 ModuleID

$ cat example.ll
define i32 @main() nounwind {
    ret i32 0
}

$ opt -S /path/to/example.ll
; ModuleID = '/path/to/example.ll'

define i32 @main() nounwind {
    ret i32 0
}

ModuleID 有可能意外地與 CHECK 行相符。例如:

; RUN: opt -S %s | FileCheck

define i32 @main() nounwind {
    ; CHECK-NOT: load
    ret i32 0
}

如果將此測試置於 download 目錄中,則測試將會失敗。

為了使您的測試穩固,請務必在 RUN 行中使用 opt ... < %s。當輸入來自標準輸入時,opt 不會輸出 ModuleID

平台特定的測試

每當添加需要特定平台知識的測試時,無論是與生成的代碼、特定輸出還是後端功能相關,都必須確保隔離這些功能,以便在不同架構上運行(甚至不編譯所有後端)的構建機器人不會失敗。

第一個問題是要檢查目標特定的輸出,例如結構的大小、路徑和架構名稱,例如:

  • 包含 Windows 路徑的測試在 Linux 上將會失敗,反之亦然。

  • 在文字中任何地方檢查 x86_64 的測試都將會在其他任何地方失敗。

  • 偵錯資訊會計算類型和結構大小的測試。

此外,如果測試依賴於任何後端中編碼的任何行為,則它必須位於其自己的目錄中。因此,例如,ARM 的代碼生成器測試會進入 test/CodeGen/ARM,依此類推。這些目錄包含一個特殊的 lit 配置文件,確保該目錄中的所有測試只在編譯並提供特定後端時才會運行。

例如,在 test/CodeGen/ARM 上,lit.local.cfg 為:

config.suffixes = ['.ll', '.c', '.cpp', '.test']
if not 'ARM' in config.root.targets:
  config.unsupported = True

其他平台特定的測試是那些依賴於特定子架構的特定功能的測試,例如僅適用於支持 AVX2 的 Intel 晶片。

例如,test/CodeGen/X86/psubus.ll 測試了三個子架構變體:

; RUN: llc -mcpu=core2 < %s | FileCheck %s -check-prefix=SSE2
; RUN: llc -mcpu=corei7-avx < %s | FileCheck %s -check-prefix=AVX1
; RUN: llc -mcpu=core-avx2 < %s | FileCheck %s -check-prefix=AVX2

檢查也不同:

; SSE2: @test1
; SSE2: psubusw LCPI0_0(%rip), %xmm0
; AVX1: @test1
; AVX1: vpsubusw LCPI0_0(%rip), %xmm0, %xmm0
; AVX2: @test1
; AVX2: vpsubusw LCPI0_0(%rip), %xmm0, %xmm0

因此,如果您要測試的行為是您知道是平台特定的或依賴於子架構的特殊功能,則必須添加特定的三元組,使用特定的 FileCheck 進行測試,並將其放入將過濾掉所有其他架構的特定目錄中。

限制測試執行

某些測試只能在特定配置中運行,例如使用偵錯版本或在特定平台上。使用 REQUIRESUNSUPPORTED 來控制何時啟用測試。

有些測試預計會失敗。例如,測試可能會偵測到已知的錯誤。使用 XFAIL 將測試標記為預期失敗。如果 XFAIL 測試執行失敗,則測試成功;如果執行成功,則測試失敗。

; This test will be only enabled in the build with asserts.
; REQUIRES: asserts
; This test is disabled when running on Linux.
; UNSUPPORTED: system-linux
; This test is expected to fail when targeting PowerPC.
; XFAIL: target=powerpc{{.*}}

REQUIRESUNSUPPORTEDXFAIL 都接受以逗號分隔的布林運算式清單。每個運算式中的值可以是:

  • lit.cfg 等設定檔加入至 config.available_features 的功能。功能的字串比較區分大小寫。此外,布林運算式可以包含以 {{ }} 包起來的任何 Python 正規表示式,在這種情況下,如果任何功能符合正規表示式,則滿足布林運算式。正規表示式可以出現在識別碼內部,例如 he{{l+}}o 會符合 helohellohelllo 等等。

  • 預設目標三元組,前面加上字串 target=(例如,target=x86_64-pc-windows-msvc)。通常使用正規表示式來匹配三元組的部分(例如,target={{.*}}-windows{{.*}} 匹配任何 Windows 目標三元組)。

如果所有運算式都為真,則 REQUIRES 會啟用測試。
如果任何運算式為真,則 UNSUPPORTED 會停用測試。
如果任何運算式為真,則 XFAIL 預期測試會失敗。

如果預期測試在任何地方都失敗,請使用 XFAIL: *。同樣地,使用 UNSUPPORTED: target={{.*}} 停用任何地方的測試。

; This test is disabled when running on Windows,
; and is disabled when targeting Linux, except for Android Linux.
; UNSUPPORTED: system-windows, target={{.*linux.*}} && !target={{.*android.*}}
; This test is expected to fail when targeting PowerPC or running on Darwin.
; XFAIL: target=powerpc{{.*}}, system-darwin

撰寫限制條件的技巧

``REQUIRES`` 和 ``UNSUPPORTED``

這些是邏輯反轉。原則上,並非絕對需要 UNSUPPORTED(可以使用 REQUIRES 的邏輯否定來達到完全相同的效果),但它可以使這些子句更容易閱讀和理解。一般來說,人們使用 REQUIRES 來陳述測試依賴於正確運作的事物,並使用 UNSUPPORTED 來排除預期測試永遠不會運作的情況。

``UNSUPPORTED`` 和 ``XFAIL``

這兩者都表示預期測試不會運作;但是,它們具有不同的效果。UNSUPPORTED 會導致測試被跳過;這可以節省執行時間,但您永遠不會知道測試實際上是否會開始運作。相反地,XFAIL 實際上會執行測試,但預期會失敗輸出,需要額外的執行時間,但會在測試開始正常運作時(XPASS 測試結果)向您發出警報。您需要在每種情況下決定哪種方法更合適。

使用 ``target=…``

檢查目標三元組可能會很棘手;很容易指定錯誤。例如,target=mips{{.*}} 不僅會匹配 mips,還會匹配 mipsel、mips64 和 mips64el。target={{.*}}-linux-gnu 會匹配 x86_64-unknown-linux-gnu,但不會匹配 armv8l-unknown-linux-gnueabihf。建議使用連字號分隔三元組組件(target=mips-{{.*}}),並且通常最好使用尾隨萬用字元來允許意外的後綴。

另外,一般來說,編寫使用整個三重組件的正規表達式比為了縮短它們而做一些聰明的事情要好。例如,要在一個表達式中匹配 freebsd 和 netbsd,您可以寫成 target={{.*(free|net)bsd.*}},這樣可以運作。但是,它會阻止 grep freebsd 找到這個測試。最好使用:target={{.+-freebsd.*}} || target={{.+-netbsd.*}}

替換

除了替換 LLVM 工具名稱之外,還會在 RUN 行中執行以下替換

%%

由單個 % 替換。這允許對其他替換進行轉義。

%s

測試案例來源的檔案路徑。這適用於在命令列上传遞作為 LLVM 工具的輸入。

範例:/home/user/llvm/test/MC/ELF/foo_test.s

%S

測試案例來源的目錄路徑。

範例:/home/user/llvm/test/MC/ELF

%t

可用於此測試案例的臨時檔案名稱的檔案路徑。檔案名稱不會與其他測試案例衝突。如果您需要多個臨時檔案,可以附加到它。這很有用,因為它是某些重定向輸出的目的地。

範例:/home/user/llvm.build/test/MC/ELF/Output/foo_test.s.tmp

%T

%t 的目錄。已棄用。不應使用,因為它很容易被誤用並導致測試之間的競爭條件。

如果需要臨時目錄,請改用 rm -rf %t && mkdir %t

範例:/home/user/llvm.build/test/MC/ELF/Output

%{pathsep}

展開為路徑分隔符號,即 :(或 Windows 上的 ;)。

%{fs-src-root}

展開為來源目錄的檔案系統路徑的根組件,即 Unix 系統上的 / 或 Windows 上的 C:\(或其他磁碟機)。

%{fs-tmp-root}

展開為測試的臨時目錄的檔案系統路徑的根組件,即 Unix 系統上的 / 或 Windows 上的 C:\(或其他磁碟機)。

%{fs-sep}

展開為檔案系統分隔符號,即 / 或 Windows 上的 \

%/s, %/S, %/t, %/T

行為類似於上面的相應替換,但將任何 \ 字元替換為 /。這對於標準化路徑分隔符號很有用。

範例:%s:  C:\Desktop Files/foo_test.s.tmp

範例:%/s: C:/Desktop Files/foo_test.s.tmp

%{s:real}, %{S:real}, %{t:real}, %{T:real} %{/s:real}, %{/S:real}, %{/t:real}, %{/T:real}

行為類似於相應的替換,包括使用 /,但通過展開所有符號連結並替換磁碟機來使用實際路徑。

範例:%s:  S:\foo_test.s.tmp

範例: %{/s:real}: C:/SDrive/foo_test.s.tmp

%:s, %:S, %:t, %:T

行為與上述對應的替換相同,但會移除 Windows 路徑開頭的冒號。這有助於允許在 Windows 上連接絕對路徑以產生合法的路徑。

範例: %s:  C:\Desktop Files\foo_test.s.tmp

範例: %:s: C\Desktop Files\foo_test.s.tmp

%errc_<錯誤碼>

某些錯誤訊息可能會被替換,以允許根據主機平台使用不同的拼寫方式。

目前支援的錯誤碼有:ENOENT、EISDIR、EINVAL、EACCES。

範例: Linux %errc_ENOENT: No such file or directory

範例: Windows %errc_ENOENT: no such file or directory

%if 功能 %{<if 分支>%} %else %{<else 分支>%}

條件式替換:如果 功能 可用,則展開為 <if 分支>,否則展開為 <else 分支>%else %{<else 分支>%} 是可選的,如果不存在,則視為 %else %{%}

%(行數), %(行數+<數字>), %(行數-<數字>)

使用此替換的行號,以及可選的整數偏移量。這些只會在 RUN:DEFINE:REDEFINE: 指令中直接出現時才會展開。在其他地方定義的替換中出現的則永遠不會展開。例如,這可以用於具有多個 RUN 行的測試中,這些行會參考測試檔案的行號。

LLVM 特定的替換

%shlibext

主機平台共用程式庫檔案的後綴。這包括作為第一個字元的句點。

範例: .so (Linux)、 .dylib (macOS)、 .dll (Windows)

%exeext

主機平台可執行檔案的後綴。這包括作為第一個字元的句點。

範例: .exe (Windows),在 Linux 上為空。

Clang 特定的替換

%clang

呼叫 Clang 驅動程式。

%clang_cpp

呼叫 Clang 驅動程式來處理 C++。

%clang_cl

呼叫與 CL 相容的 Clang 驅動程式。

%clangxx

呼叫與 G++ 相容的 Clang 驅動程式。

%clang_cc1

呼叫 Clang 前端。

%itanium_abi_triple, %ms_abi_triple

這些替換可以用於取得調整為所需 ABI 的目前目標三元組。例如,如果測試套件使用 i686-pc-win32 目標執行,則 %itanium_abi_triple 將展開為 i686-pc-mingw32。這允許測試使用特定的 ABI 執行,而不會將其限制為特定的三元組。

FileCheck 特定的替換

%ProtectFileCheckOutput

僅當呼叫的文字輸出會影響測試結果時,才應該在 FileCheck 呼叫之前加上此項。這通常很容易判斷:只要查看 FileCheck 呼叫的 stdout 或 stderr 是否有重新導向或管道即可。

測試專用替換

可以按如下方式定義其他替換

  • Lit 設定檔(例如,lit.cfglit.local.cfg)可以為測試目錄中的所有測試定義替換。它們透過擴展替換清單 config.substitutions 來實現這一點。清單中的每一項都是一個由模式及其替換組成的元組,lit 將其作為純文字應用(即使它包含 python 的 re.sub 視為轉義序列的序列)。

  • 為了在單個測試檔案中定義替換,lit 支援 DEFINE:REDEFINE: 指令,詳情如下所述。為了不影響其他測試檔案,這些指令會修改由 lit 設定檔產生的替換清單副本。

例如,可以在測試檔案中插入以下指令,以使用空初始值定義 %{cflags}%{fcflags} 替換,這些替換作為另一個新定義的 %{check} 替換的參數

; DEFINE: %{cflags} =
; DEFINE: %{fcflags} =

; DEFINE: %{check} =                                                  \
; DEFINE:   %clang_cc1 -verify -fopenmp -fopenmp-version=51 %{cflags} \
; DEFINE:              -emit-llvm -o - %s |                           \
; DEFINE:     FileCheck %{fcflags} %s

或者,可以在 lit 設定檔中定義上述替換,以便與其他測試檔案共用。無論哪種方式,測試檔案都可以在 RUN: 行中每次使用 %{check} 之前,指定如下指令來根據需要重新定義參數替換

; REDEFINE: %{cflags} = -triple x86_64-apple-darwin10.6.0 -fopenmp-simd
; REDEFINE: %{fcflags} = -check-prefix=SIMD
; RUN: %{check}

; REDEFINE: %{cflags} = -triple x86_64-unknown-linux-gnu -fopenmp-simd
; REDEFINE: %{fcflags} = -check-prefix=SIMD
; RUN: %{check}

; REDEFINE: %{cflags} = -triple x86_64-apple-darwin10.6.0
; REDEFINE: %{fcflags} = -check-prefix=NO-SIMD
; RUN: %{check}

; REDEFINE: %{cflags} = -triple x86_64-unknown-linux-gnu
; REDEFINE: %{fcflags} = -check-prefix=NO-SIMD
; RUN: %{check}

除了提供初始值之外,上述範例中參數替換的初始 DEFINE: 指令還有第二個目的:它們建立替換順序,以便 %{check} 及其參數都能按預期擴展。在測試檔案中記住所需的定義順序有一個簡單的方法:在任何可能引用替換之前定義它。

一般來說,替換擴展的行為如下

  • 到達每個 RUN: 行時,lit 會使用替換清單中的當前值擴展該 RUN: 行中的所有替換。除了 %(line)%(line+<number>)%(line-<number>) 之外,不會在 DEFINE:REDEFINE: 指令處立即執行替換擴展。

  • 根據預設,在擴展 RUN: 行中的替換時,lit 只會遍歷替換清單一次。在這種情況下,替換必須比出現在其值中的任何替換更早插入替換清單中,以便後者進行擴展。(為了獲得更大的靈活性,您可以透過在 lit 設定檔中設定 recursiveExpansionLimit 來啟用多次遍歷替換清單。)

  • 雖然 lit 設定檔可以插入替換清單中的任何位置,但 DEFINE:REDEFINE: 指令的插入行為如下所述,並且專為上述範例中介紹的用例而設計。

  • 應避免以遞迴的方式定義替換,無論是直接或透過其他替換,因為這通常會產生無法完全展開的無限遞迴定義。即使使用 REDEFINE:,它也不會根據其先前值定義替換。

DEFINE:REDEFINE: 指令之間的關係類似於許多程式語言中變數宣告和變數賦值之間的關係

  • DEFINE: %{name} = value

    此指令會將指定值賦予一個新的替換,其模式為 %{name},如果已經存在模式包含 %{name} 的替換,則會回報錯誤,因為這可能會產生令人困惑的展開(例如,lit 設定檔可能會定義一個模式為 %{name}\[0\] 的替換)。新的替換會插入到替換清單的開頭,以便首先展開。因此,它的值可以包含任何先前定義的替換,無論是在同一個測試檔案中還是在 lit 設定檔中,並且兩者都會展開。

  • REDEFINE: %{name} = value

    此指令會將指定值賦予一個現有的替換,其模式為 %{name},如果沒有該模式的替換,或者有多個模式包含 %{name} 的替換,則會回報錯誤。替換在替換清單中的當前位置不會改變,以便保留相對於其他現有替換的展開順序。

以下屬性適用於 DEFINE:REDEFINE: 指令

  • **替換名稱**:在指令中,%{name} 前後緊鄰的空白是可選的,並且會被捨棄。%{name} 必須以 %{ 開頭,以 } 結尾,其餘部分必須以字母或底線開頭,並且只能包含字母數字字元、連字號、底線和冒號。這種語法有幾個優點

    • %{name} 不可能包含在 Python 的 re.sub 模式中具有特殊含義的序列。否則,嘗試在 lit 設定檔中將 %{name} 指定為替換模式可能會產生令人困惑的展開。

    • 大括號有助於避免另一個替換的模式匹配 %{name} 的一部分或反之亦然,從而產生令人困惑的展開。但是,lit 設定檔和 lit 本身定義的替換模式並不局限於這種形式,因此理論上仍然可能發生重疊。

  • **替換值**:值包括從 = 之後的第一个非空白字元到最后一个非空白字元的所有文字。如果 = 之後沒有非空白字元,則值為空字串。可以在 Python re.sub 替換字串中出現的跳脫序列在值中被視為純文字。

  • 行接續:如果 : 後、該行最後一個非空白字元是 \,則下一個指令必須使用相同的指令關鍵字(例如 DEFINE:),如果沒有額外的指令,則會產生錯誤。該指令作為接續。也就是說,在遵循上述規則解析任一指令中 : 後的文字之前,lit 會將這些文字連接起來形成單一指令,將 \ 替換為單一空格,並移除現在與該空格相鄰的任何其他空白。接續可以相同的方式繼續。接續如果在 : 後只包含空白,則會產生錯誤。

recursiveExpansionLimit

如上一節所述,在展開 RUN: 行中的替換時,lit 預設只會遍歷替換清單一次。因此,如果替換沒有以正確的順序定義,有些替換將會保持在 RUN: 行中未展開。例如,以下指令在 %{outer} 中引用了 %{inner},但在 %{outer} 之後才定義 %{inner}

; By default, this definition order does not enable full expansion.

; DEFINE: %{outer} = %{inner}
; DEFINE: %{inner} = expanded

; RUN: echo '%{outer}'

DEFINE: 會在替換清單的開頭插入替換,所以 %{inner} 會先展開,但沒有作用,因為原始的 RUN: 行不包含 %{inner}。接下來,%{outer} 展開,而 echo 命令的輸出會變成

%{inner}

當然,解決這個簡單情況的一個方法是反轉 %{outer}%{inner} 的定義。但是,如果測試有一組複雜的替換,並且這些替換可以互相引用,則可能不存在足夠的替換順序。

為了處理這種使用情況,lit 配置文件支持 config.recursiveExpansionLimit,它可以設置為一個非負整數,以指定遍歷替換清單的最大次數。因此,在上面的例子中,將限制設置為 2 將導致 lit 進行第二次遍歷,從而在 RUN: 行中展開 %{inner},此時 echo 命令的輸出將會是

expanded

為了提高性能,當 lit 發現 RUN: 行停止變化時,它將停止遍歷。因此,在上面的例子中,將限制設置為大於 2 是無害的。

為了方便調試,在達到限制後,lit 將會再進行一次遍歷,如果 RUN: 行再次發生變化,則會報告錯誤。因此,在上面的例子中,將限制設置為 1 將導致 lit 報告錯誤,而不是產生錯誤的輸出。

選項

llvm lit 配置允許使用用戶選項自訂某些內容

llcopt

可以使用自訂命令列取代各別的 llvm 工具名稱。這允許為這些工具指定自訂路徑和預設參數。範例:

% llvm-lit “-Dllc=llc -verify-machineinstrs”

run_long_tests

啟用長時間執行測試。

llvm_site_config

載入指定的 lit 組態,而不是預設組態。

其他功能

為了讓 RUN 行更容易撰寫,我們提供了一些輔助程式。這些輔助程式在執行測試時會包含在 PATH 中,因此您可以直接使用它們的名稱來呼叫它們。例如:

not

此程式會執行其參數,然後反轉其結果代碼。零結果代碼會變成 1。非零結果代碼會變成 0。

為了讓輸出更有用,lit 會掃描測試案例的行,尋找包含符合 PR[0-9]+ 模式之字樣的那些行。這是指定與測試案例相關的 PR(問題報告)編號的語法。「PR」之後的數字指定 LLVM Bugzilla 編號。指定 PR 編號時,它將用於通過/失敗報告中。這在測試失敗時快速獲取一些上下文資訊時很有用。

最後,任何包含「END.」的行都會導致行的特殊解釋終止。這通常在最後一個 RUN: 行之後完成。這有兩個副作用:

  1. 它可以防止對屬於測試程式而非測試案例指令的行進行特殊解釋,並且

  2. 它可以透過避免解釋檔案的剩餘部分來加快大型測試案例的速度。