LLVM 测试基础设施指南

概述

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

要求

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

LLVM 测试基础设施组织

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

完整的程序测试被称为“LLVM 测试套件”(或“test-suite”),位于 test-suite GitHub 上的仓库 中。由于历史原因,这些测试在某些地方也被称为“夜间测试”,这比“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

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

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

调试信息测试

要运行调试信息测试,只需将 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 执行以运行测试用例的“脚本”。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 测试之外,仅运行最少的 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 后的部分。

为方便起见,如果测试需要单个汇编文件,您也可以使用 .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 ... < %sopt 在输入来自 stdin 时不会输出 ModuleID

平台特定的测试

每当添加需要了解特定平台的测试时,无论是与生成的代码、特定输出还是后端功能相关,都必须确保隔离这些功能,以便在不同架构上运行(甚至不编译所有后端)的 buildbot 不会失败。

第一个问题是检查特定于目标的输出,例如结构的大小、路径和架构名称,例如

  • 包含 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{{.*}}

REQUIRESUNSUPPORTED 以及 XFAIL 都接受逗号分隔的布尔表达式列表。每个表达式中的值可以是

  • 配置文件的配置添加到 config.available_features 的功能,例如 lit.cfg。功能的字符串比较区分大小写。此外,布尔表达式可以包含用 {{ }} 括起来的任何 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}

展开为文件系统分隔符,即 Unix 系统上的 / 或 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 行的测试中,这些 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 调用之前使用它。 通常很容易判断:只需查找重定向或管道 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. 它通过避免解释文件的其余部分来加速真正大型的测试用例。