llvm
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.
The skill
# 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
```cpp
class my_custom_pass { // Class name should be CamelCase
void RunPass() { // Function name should be lower_case
int temp_var = 0;
}
};
```
✅ GOOD: Consistent LLVM style
```cpp
class MyCustomPass {
void runOnFunction(llvm::Function &F) { // LLVM passes typically run on a Function
int tempVar = 0; // Local variables can be camelCase or snake_case, consistency is key.
}
};
```
### 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
```cpp
#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`
```cpp
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/raw_ostream.h"
llvm::SmallVector<int, 8> myVec; // SmallVector avoids heap allocation for small sizes
llvm::DenseMap<llvm::StringRef, int> myMap; // DenseMap is faster for many use cases
llvm::outs() << "Debug info: " << val << "\n"; // Use llvm::outs() for stdout
```
### 1.3 Utilize TableGen for Declarative Definitions
For instruction definitions, target descriptions, or pass registration, use TableGen. It reduces boilerplate and ensures consistency.
```td
// 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`
```cpp
#include <iostream>
void MyPass::runOnFunction(llvm::Function &F) {
std::cerr << "Processing function: " << F.getName().str() << "\n";
// ...
}
```
✅ GOOD: Debugging with `LLVM_DEBUG`
```cpp
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "my-pass" // Define a debug type for your 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
```cpp
if (!Val->hasOneUse()) {
// This should never happen, but no assertion.
}
```
✅ GOOD: Asserting expected conditions
```cpp
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.
```bash
# Configure for debug with assertions
cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On -DCMAKE_BUILD_TYPE=Debug ../llvm-project/llvm
# Build only the necessary targets
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
```llvm
; CHECK: define i32 @foo() {
; CHECK-NEXT: ret i32 42
; Manually updated this
```
✅ GOOD: Using the script
```bash
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.
1. **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.
2. **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:
```llvm
; 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
```
How to install it
1. Open the tool and start a new conversation, project or template. 2. Paste the text below wherever the tool keeps standing instructions — usually called system prompt, instructions, or persona. 3. Save it so it applies to every run rather than a single message.