LLVM 測試基礎架構指南

概觀

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

需求條件

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

LLVM 測試基礎架構組織

LLVM 測試基礎架構包含三個主要類別的測試:單元測試、迴歸測試和完整程式。單元測試和迴歸測試包含在 LLVM 儲存庫本身中,分別位於 llvm/unittestsllvm/test 下,並且預期總是通過 – 它們應該在每次提交之前執行。

完整程式測試被稱為 “LLVM 測試套件”(或 “test-suite”),位於 test-suite GitHub 上的儲存庫 中。由於歷史原因,這些測試在某些地方也被稱為 “nightly tests”(夜間測試),這比 “test-suite” 更不容易混淆,並且仍然在使用,儘管我們比夜間更頻繁地執行它們。

單元測試

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

迴歸測試

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

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

測試分析

分析是一種 pass,用於推斷 IR 某些部分的屬性,而不是轉換它。它們通常使用與迴歸測試相同的基礎架構進行測試,方法是建立一個單獨的 “Printer” pass 來消耗分析結果,並以適合 FileCheck 的文字格式將其列印到標準輸出。請參閱 llvm/test/Analysis/BranchProbabilityInfo/loop.ll 以取得此類測試的範例。

test-suite

測試套件包含完整程式,這些程式碼可以編譯和連結成可執行的獨立程式。這些程式通常使用高階語言(如 C 或 C++)撰寫。

這些程式使用使用者指定的編譯器和一組旗標進行編譯,然後執行以捕獲程式輸出和計時資訊。將這些程式的輸出與參考輸出進行比較,以確保程式被正確編譯。

除了編譯和執行程式之外,完整程式測試還可以作為評估 LLVM 效能的一種方式,包括產生程式的效率以及 LLVM 編譯、最佳化和產生程式碼的速度。

測試套件位於 test-suite 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++ 程式。有關詳細資訊,請參閱 test-suite 指南

單元測試與迴歸測試

要執行所有 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 一部分建置的 llvm-lit 腳本。例如,要單獨執行 Integer/BitPacked.ll 測試,您可以執行

% llvm-lit <path to llvm-project>/llvm/test/Integer/BitPacked.ll

注意

測試檔案位於 llvm-project 目錄中,而不是您正在建置 LLVM 的目錄中。

或者您可以執行整個測試資料夾。要執行所有 ARM CodeGen 測試

% llvm-lit <path to llvm-project>/llvm/test/CodeGen/ARM

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

有關使用 lit 工具的更多資訊,請參閱 llvm-lit --helplit man page

偵錯資訊測試

要執行偵錯資訊測試,只需將 cross-project-tests 專案新增到 cmake 命令列上的 LLVM_ENABLE_PROJECTS 定義中即可。

迴歸測試結構

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 執行以執行測試案例的 “script”(腳本)。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 註解。

如果您具有提交權限,則基準測試(no-functional-change 或 NFC 修補程式)可以推送到 main 而無需預提交審查。

迴歸測試的最佳實踐

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

  • 包含有關特定測試中測試/預期的內容的註解。如果錯誤追蹤器中有相關問題,請新增對這些錯誤報告的參考(例如,“有關更多詳細資訊,請參閱 PR999”)。

  • 避免未定義的行為和 poison/undef 值,除非必要。例如,不要使用像 br i1 undef 這樣的模式,這些模式很可能會因為未來的最佳化而中斷。

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

  • 在 PhaseOrdering 測試之外,僅執行最少的一組 pass。例如,首選 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 後面的部分。

為了方便起見,如果測試需要單個組譯檔案,您也可以將 gen 及其所需檔案包裝在 .ifdef.endif 中。然後,您可以跳過 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 ... < %sopt 在輸入來自 stdin 時不會輸出 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_<ERRCODE>

某些錯誤訊息可能會被替換,以允許基於主機平台的不同拼寫。

目前支援以下錯誤代碼:ENOENT、EISDIR、EINVAL、EACCES。

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

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

%if feature %{<if branch>%} %else %{<else branch>%}

條件替換:如果 feature 可用,它會擴展為 <if branch>,否則它會擴展為 <else branch>%else %{<else branch>%} 是可選的,如果不存在,則視為 %else %{%}

%(line), %(line+<number>), %(line-<number>)

使用此替換的行號,帶有可選的整數偏移量。這些僅在它們立即出現在 RUN:DEFINE:REDEFINE: 指令中時才會擴展。在其他地方定義的替換中出現的情況永遠不會擴展。例如,這可以用於具有多個 RUN 行的測試中,這些行引用測試檔案的行號。

LLVM 特定的替換

%shlibext

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

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

%exeext

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

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

Clang 特定的替換

%clang

調用 Clang 驅動程式。

%clang_cpp

調用 C++ 的 Clang 驅動程式。

%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 組態允許使用使用者選項自訂某些項目

llc, opt, …

使用自訂命令列替換各自的 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. 它透過避免解釋檔案的其餘部分來加速真正大型的測試案例。