LLVM 测试基础设施指南

概述

本文档是 LLVM 测试基础设施的参考手册。它记录了 LLVM 测试基础设施的结构、使用它所需的工具以及如何添加和运行测试。

需求

为了使用 LLVM 测试基础设施,您将需要构建 LLVM 所需的所有软件,以及 3.8 或更高版本的Python

LLVM 测试基础设施组织

LLVM 测试基础设施包含三类主要测试:单元测试、回归测试和完整程序。单元测试和回归测试分别包含在 LLVM 代码库本身的 llvm/unittestsllvm/test 目录下,并且预期始终通过——它们应该在每次提交之前运行。

完整程序测试被称为“LLVM 测试套件”(或“test-suite”),位于 test-suite GitHub 上的代码库中。由于历史原因,这些测试在某些地方也被称为“夜间测试”,这比“测试套件”更不容易产生歧义,并且仍在使用,尽管我们运行它们的频率远高于每晚一次。

单元测试

单元测试使用 Google TestGoogle Mock 编写,并位于 llvm/unittests 目录中。通常,单元测试保留用于针对支持库和其他通用数据结构,我们更倾向于依靠回归测试来测试 IR 上的转换和分析。

回归测试

回归测试是测试 LLVM 的特定功能或触发 LLVM 中特定错误的小段代码。它们使用的语言取决于正在测试的 LLVM 部分。这些测试由 Lit 测试工具(它是 LLVM 的一部分)驱动,并位于 llvm/test 目录中。

通常,当在 LLVM 中发现错误时,应该编写一个包含足以重现该问题的回归测试,并将其放置在此目录下的某个位置。例如,它可以是从实际应用程序或基准测试中提取的一小段 LLVM IR。

测试分析

分析是一个传递,它推断 IR 的某些部分的属性,而不是转换它。通常,它们使用与回归测试相同的基础设施进行测试,通过创建一个单独的“打印机”传递来使用分析结果,并以适合 FileCheck 的文本格式将其打印到标准输出。有关此类测试的示例,请参见 llvm/test/Analysis/BranchProbabilityInfo/loop.ll

test-suite

测试套件包含完整的程序,它们是可以编译并链接成独立程序的代码段,可以执行。这些程序通常用高级语言(如 C 或 C++)编写。

这些程序使用用户指定的编译器和标志集进行编译,然后执行以捕获程序输出和计时信息。这些程序的输出与参考输出进行比较,以确保程序正在正确编译。

除了编译和执行程序之外,完整程序测试还提供了一种基准测试 LLVM 性能的方法,包括生成的程序效率以及 LLVM 编译、优化和生成代码的速度。

测试套件位于 test-suite GitHub 上的代码库中。

有关详细信息,请参见 测试套件指南

调试信息测试

测试套件包含用于检查调试信息质量的测试。测试是用基于 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 代码生成测试

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

仅当 Python psutil 模块安装在**非用户**位置时,回归测试才会使用它。在 Linux 上,使用 sudo 或在虚拟环境中安装。在 Windows 上,为所有用户安装 Python,然后在提升的命令提示符下运行 pip install psutil

有关使用 lit 工具的更多信息,请参见 llvm-lit --helplit 手册页

调试信息测试

要运行调试信息测试,只需在 cmake 命令行上将 cross-project-tests 项目添加到您的 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执行以运行测试用例的“脚本”。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”)。

  • 避免未定义的行为和 poison/undef 值,除非必要。例如,不要使用诸如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之后的部分。

为了方便起见,如果测试需要一个单独的汇编文件,您还可以将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'

注意

考虑指定显式目标三元组,以避免在其他机器上需要重新生成时出现差异。

gen在将PWD设置为/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 的英特尔芯片。

例如,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 调用的标准输出或标准错误的重定向或管道即可。

测试特定替换

可以按如下方式定义其他替换

  • 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. 它通过避免解释文件的其余部分来加快大型测试用例的速度。