| name | llvm |
| description | [Applies to: **/*] This guide provides definitive, actionable best practices for developing with LLVM, focusing on code quality, performance, and robust testing using modern tools like LIT and TableGen. |
| source | cursor_mdc |
llvm Best Practices
Developing for LLVM requires adherence to specific patterns and standards to maintain consistency, performance, and testability across a massive codebase. This guide outlines critical best practices for our team.
1. Code Organization and Structure
1.1 Adhere to LLVM Coding Standards
Consistency is paramount. Follow LLVM's established naming conventions, whitespace rules, and C++17 usage. Deviations require strong justification.
- Naming:
CamelCase for types, lower_case for functions/variables.
- Whitespace: Use
clang-format with LLVM style.
- C++ Standard: Stick to C++17 features supported by major toolchains.
❌ BAD: Inconsistent naming and formatting
class my_custom_pass {
void RunPass() {
int temp_var = 0;
}
};
✅ GOOD: Consistent LLVM style
class MyCustomPass {
void runOnFunction(llvm::Function &F) {
int tempVar = 0;
}
};
1.2 Prefer LLVM ADT and Utilities
Leverage LLVM's specialized data structures and utilities over standard library equivalents for performance and consistency.
- Containers: Use
llvm::SmallVector, llvm::DenseMap, llvm::StringRef etc.
- I/O: Use
llvm::raw_ostream for all output.
❌ BAD: Using standard library containers and I/O
#include <vector>
#include <map>
#include <iostream>
std::vector<int> myVec;
std::map<std::string, int> myMap;
std::cout << "Debug info: " << val << std::endl;
✅ GOOD: Using LLVM ADT and raw_ostream
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/raw_ostream.h"
llvm::SmallVector<int, 8> myVec;
llvm::DenseMap<llvm::StringRef, int> myMap;
llvm::outs() << "Debug info: " << val << "\n";
1.3 Utilize TableGen for Declarative Definitions
For instruction definitions, target descriptions, or pass registration, use TableGen. It reduces boilerplate and ensures consistency.
// In a .td file
def MyInst : Instruction<"my_inst", ...>;
2. Common Patterns and Anti-patterns
2.1 Use LLVM_DEBUG for Debugging
Never use printf or std::cout for debug output. Use LLVM_DEBUG macros, which are compiled out in release builds.
❌ BAD: Debugging with printf or std::cerr
#include <iostream>
void MyPass::runOnFunction(llvm::Function &F) {
std::cerr << "Processing function: " << F.getName().str() << "\n";
}
✅ GOOD: Debugging with LLVM_DEBUG
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "my-pass"
void MyPass::runOnFunction(llvm::Function &F) {
LLVM_DEBUG(llvm::dbgs() << "Processing function: " << F.getName() << "\n");
}
2.2 Assertions are Your Friend
Use assert liberally to catch invariants and impossible states early. Always build with assertions enabled during development (-DLLVM_ENABLE_ASSERTIONS=On).
❌ BAD: Relying on runtime crashes for logic errors
if (!Val->hasOneUse()) {
}
✅ GOOD: Asserting expected conditions
assert(Val->hasOneUse() && "Expected value to have a single use!");
3. Performance Considerations
3.1 Optimize Data Structure Choices
llvm::SmallVector and llvm::DenseMap are specifically designed for compiler performance. Understand their tradeoffs and use them appropriately.
SmallVector: Avoids heap allocations for small collections.
DenseMap/DenseSet: Faster than std::map/std::set for many common cases due to hash-based implementation.
3.2 Incremental Builds
Use CMake effectively for incremental builds. Avoid full rebuilds.
cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On -DCMAKE_BUILD_TYPE=Debug ../llvm-project/llvm
ninja MyPassName
4. Common Pitfalls and Gotchas
4.1 Forgetting Pre-Commit Tests
Always add a baseline test before your change, then a second commit with the actual change and test diffs. This is crucial for review and regression safety.
4.2 Manual CHECK Line Editing
Never manually edit CHECK lines in LIT tests. Use llvm/utils/update_test_checks.py.
❌ BAD: Manually updating CHECK lines
; CHECK: define i32 @foo() {
; CHECK-NEXT: ret i32 42
; Manually updated this
✅ GOOD: Using the script
llvm/utils/update_test_checks.py --opt-binary build/bin/opt llvm/test/Transforms/InstCombine/my_test.ll
5. Testing Approaches
5.1 Embrace the Test-First Workflow
For new optimizations or bug fixes, always write the test first.
- Commit 1: Add a new LIT test case that demonstrates the original behavior (e.g., the bug or the pre-optimization IR). Use
update_test_checks.py to generate baseline CHECK lines.
- Commit 2: Implement your change. Run
update_test_checks.py again to update the CHECK lines to reflect the new, desired behavior.
5.2 Comprehensive LIT Testing
LLVM's LIT framework is the definitive testing tool. Ensure your tests are:
- Minimal: Test only the specific pattern being transformed.
- Meaningful Names: Use descriptive test names (e.g.,
@@add_of_selects_multi_use).
- Negative Tests: Include cases where your transform should not apply.
- Multi-Use Tests: Verify your transform doesn't increase instruction count if results are used multiple times.
- Commuted Tests: For commutative operations, test swapped operands (unless one is a constant, which is canonicalized).
- Vector Tests: Include splat, splat-with-poison, and non-splat vector cases.
- Flag Tests: Test interactions with flags like
nuw, nsw, and specific fast-math-flags (nnan, nsz, reassoc).
Example of a multi-use test:
; RUN: opt -S -instcombine < %s | FileCheck %s
; CHECK-LABEL: @add_mul_const_multi_use
; CHECK: %[[ADD:.*]] = add i8 %x, 1
; CHECK: call void @use(i8 %[[ADD]])
; CHECK: %[[MUL:.*]] = mul i8 %[[ADD]], 3
; CHECK: ret i8 %[[MUL]]
define i8 @add_mul_const_multi_use(i8 %x) {
%add = add i8 %x, 1
call void @use(i8 %add) ; Extra use
%mul = mul i8 %add, 3
ret i8 %mul
}
declare void @use(i8)
5.3 Provide Alive2 Proofs
For complex transformations, especially in InstCombine, include Alive2 proofs in your pull request description to demonstrate correctness. This significantly speeds up review.
// Example Alive2 proof snippet in PR description
// %0 = add i32 %a, 1
// %1 = mul i32 %0, 2
// =>
// %1 = add i32 (mul i32 %a, 2), 2