LLVM 22.x tile for building compilers, language runtimes, and out-of-tree tooling
88
83%
Does it follow best practices?
Impact
96%
1.23xAverage score across 5 eval scenarios
Passed
No known issues
Use this skill when the user wants to add tests for an LLVM pass, IR transform, codegen output, or compiler frontend.
# Verify tools are on PATH
llvm-lit --version # the lit test runner
FileCheck --version # the pattern matcher
opt --version # for IR transform tests
llc --version # for codegen testsFor out-of-tree projects, set LLVM_TOOLS_BINARY_DIR in cmake and use:
${LLVM_TOOLS_BINARY_DIR}/llvm-lit
${LLVM_TOOLS_BINARY_DIR}/FileCheckLit tests are plain text files (.ll for IR, .c/.cpp for Clang tests):
; RUN: opt -passes=<pass-name> -S %s | FileCheck %s
;
; A short comment explaining what this test covers.
define i32 @test_basic(i32 %x, i32 %y) {
entry:
; CHECK-LABEL: @test_basic
; CHECK: %sum = add i32 %x, %y
%sum = add i32 %x, %y
ret i32 %sum
; CHECK: ret i32 %sum
}%s — expands to the test file path-S — print IR as text (not bitcode)FileCheck %s — reads CHECK patterns from the same file; RUN: <command> | FileCheck %s
; RUN: <command> | FileCheck %s --check-prefix=OPT
; RUN: <command> 2>&1 | FileCheck %s ; capture stderr too
; RUN: not <command> 2>&1 | FileCheck %s ; expect non-zero exitMultiple RUN lines run sequentially; each must pass:
; RUN: opt -passes=instcombine -S %s | FileCheck %s --check-prefix=IC
; RUN: opt -passes=simplifycfg -S %s | FileCheck %s --check-prefix=SCFG; CHECK: <pattern> — match pattern anywhere on the next matching line
; CHECK-NEXT: <pattern> — match on the very next line after previous CHECK
; CHECK-SAME: <pattern> — match on the same line as previous CHECK
; CHECK-NOT: <pattern> — assert pattern does NOT appear until next CHECK
; CHECK-EMPTY: — assert next line is empty
; CHECK-LABEL: <pattern> — like CHECK but resets the search position (use for function names); CHECK: %[[REG:[a-z0-9.]+]] = add i32
; CHECK: ret i32 %[[REG]]%[[REG:[a-z0-9.]+]] captures the register name into REG for later use.
Numeric variables:
; CHECK: define i32 @foo(i32 %x)
; CHECK: [[#LINE:]]:
; CHECK: line [[#LINE+1]]; CHECK-NOT: call void @expensive_function
; CHECK-NOT: unreachableCHECK-NOT asserts the pattern never appears between the previous and next CHECK.
; RUN: opt -passes=instcombine -S %s | FileCheck %s
; RUN: opt -passes=instcombine -disable-output %s ; smoke test: must not crash
; Test: x + 0 → x
define i32 @add_zero(i32 %x) {
; CHECK-LABEL: @add_zero(
; CHECK-NEXT: ret i32 %x
%r = add i32 %x, 0
ret i32 %r
}
; Test: x * 1 → x
define i32 @mul_one(i32 %x) {
; CHECK-LABEL: @mul_one(
; CHECK-NEXT: ret i32 %x
%r = mul i32 %x, 1
ret i32 %r
}
; Negative test: should NOT eliminate a volatile load
define i32 @volatile_load(ptr %p) {
; CHECK-LABEL: @volatile_load(
; CHECK: load volatile i32
; CHECK-NOT: ret i32 0
%v = load volatile i32, ptr %p
ret i32 %v
}; RUN: opt -load-pass-plugin=%llvmshlibdir/MyPass%pluginext \
; RUN: -passes=my-pass -S %s 2>&1 | FileCheck %s
; CHECK: MyPass: processing function foo
define void @foo() {
ret void
}For in-tree passes, just use -passes=my-pass without --load-pass-plugin.
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -O2 < %s | FileCheck %s
define i32 @add(i32 %a, i32 %b) {
entry:
; CHECK-LABEL: add:
; CHECK: leal (%rdi,%rsi), %eax
; CHECK: retq
%r = add i32 %a, %b
ret i32 %r
}Test for a specific CPU feature:
; RUN: llc -mtriple=x86_64 -mattr=+avx2 < %s | FileCheck %s --check-prefix=AVX2
; AVX2: vmovdqu; For a custom compiler binary:
; RUN: my-compiler %s -emit-llvm -S -o - | FileCheck %s
; RUN: my-compiler %s -o %t && %t | FileCheck %s --check-prefix=OUTPUT
; OUTPUT: Hello, World!In the root of your test directory, create lit.cfg.py:
import lit.formats
config.name = "MyProject"
config.test_format = lit.formats.ShTest(True)
config.test_source_root = os.path.dirname(__file__)
config.suffixes = [".ll", ".c"]
# Substitutions for tool paths
config.substitutions.append(("%opt", "/path/to/opt"))
config.substitutions.append(("%filecheck","/path/to/FileCheck"))
config.substitutions.append(("%llvmshlibdir", "/path/to/llvm/lib"))
config.substitutions.append(("%pluginext",
".dylib" if sys.platform == "darwin" else ".so"))# Run all tests in a directory
llvm-lit test/ -v
# Run a single test file
llvm-lit test/Transforms/my-pass.ll -v
# Run with parallelism
llvm-lit test/ -j8
# Filter by name
llvm-lit test/ --filter "instcombine"
# Show output of failing tests
llvm-lit test/ --show-all# Print the exact FileCheck invocation
llvm-lit test/my-test.ll -v --show-all
# Run the RUN line manually (replace %s with the file path)
opt -passes=my-pass -S test/my-test.ll | FileCheck test/my-test.ll
# Check what the pass actually emits:
opt -passes=my-pass -S test/my-test.ll
# Use FileCheck --dump-input to show which lines matched/failed:
opt -passes=my-pass -S test/my-test.ll | \
FileCheck test/my-test.ll --dump-input=fail; Match any register name
; CHECK: %{{[a-z0-9.]+}} = add
; Match optional whitespace
; CHECK: ret{{[ \t]+}}i32
; Match a function definition regardless of attributes
; CHECK-LABEL: define {{.*}}@my_func(
; Assert a block is completely eliminated (no label in output)
; CHECK-NOT: dead_block:
; Test that a call is inlined (no call instruction remains)
; CHECK-NOT: call {{.*}}@small_funcCHECK: without CHECK-LABEL: for function boundaries — without labels, patterns from one function can match in another.%0, %1 — they change easily; use named values or %[[VAR:...]] captures instead.RUN line with -disable-output to catch crashes separately from correctness.CHECK-LABEL: at each function so FileCheck resets its search position.docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
skills
add-alias-analysis
add-attributes-metadata
add-calling-convention
add-debug-info
add-exception-handling
add-gc-statepoints
add-intrinsic
add-lto
add-sanitizer
add-vectorization-hint
frontend-to-ir
jit-setup
lit-filecheck
lower-struct-types
new-target
out-of-tree-setup
tessl-llvm
version-sync