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 start a new compiler, language runtime, or LLVM-based tool that builds against an installed LLVM 22 rather than inside the LLVM source tree.
Confirm the user has LLVM 22 installed. Common locations:
# Homebrew (macOS)
brew install llvm@22
# Path: /opt/homebrew/opt/llvm@22
# apt (Ubuntu/Debian)
apt install llvm-22-dev libclang-22-dev
# Path: /usr/lib/llvm-22
# From source
cmake --install build --prefix /usr/local/llvm-22Find the cmake config dir:
llvm-config-22 --cmakedir
# or
find /usr -name "LLVMConfig.cmake" 2>/dev/nullCreate this minimal layout:
<project>/
├── CMakeLists.txt
├── include/
│ └── <Project>.h
└── src/
└── main.cppScale up later: add lib/, test/, tools/ as needed.
CMakeLists.txtcmake_minimum_required(VERSION 3.20)
project(<ProjectName> CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# ── Find LLVM 22 ──────────────────────────────────────────────────────────────
find_package(LLVM 22 REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
# ── Map component names to library names ──────────────────────────────────────
# Common components — add/remove as needed:
# Core — LLVMContext, Module, IRBuilder, basic IR types
# Support — raw_ostream, StringRef, Error, CommandLine
# Analysis — pass analyses (DominatorTree, ScalarEvolution, etc.)
# Passes — New Pass Manager, PassBuilder, StandardInstrumentations
# IRReader — parseIRFile / parseAssemblyString
# BitWriter — WriteBitcodeToFile
# Target — TargetMachine, TargetRegistry
# X86 — X86 target (or AArch64, RISCV, etc.)
llvm_map_components_to_libnames(LLVM_LIBS
Core
Support
Analysis
Passes
IRReader
BitWriter
Target
X86CodeGen # replace/extend with your target
X86AsmParser
X86Desc
X86Info
)
# ── Build the executable ──────────────────────────────────────────────────────
add_executable(<ProjectName> src/main.cpp)
target_include_directories(<ProjectName> PRIVATE
${CMAKE_SOURCE_DIR}/include
)
target_link_libraries(<ProjectName> PRIVATE ${LLVM_LIBS})Note: Use
llvm_map_components_to_libnames— do NOT hardcode-lLLVMCoreetc. Component names are case-sensitive and listed inllvm-config --components.
src/main.cppThis compiles, creates a module, prints IR, and JIT-compiles a trivial function:
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
int main(int argc, char **argv) {
// InitLLVM handles signal handlers, pretty-stack-trace, and ManagedStatic.
InitLLVM X(argc, argv);
// Initialize targets (required before any codegen or JIT).
InitializeAllTargetInfos();
InitializeAllTargets();
InitializeAllTargetMCs();
InitializeAllAsmParsers();
InitializeAllAsmPrinters();
LLVMContext Ctx;
auto M = std::make_unique<Module>("hello", Ctx);
M->setTargetTriple(sys::getDefaultTargetTriple());
// Build: define i32 @add(i32 %a, i32 %b) { ret i32 add(a, b) }
IRBuilder<> Builder(Ctx);
FunctionType *FT = FunctionType::get(
Builder.getInt32Ty(),
{Builder.getInt32Ty(), Builder.getInt32Ty()},
/*isVarArg=*/false);
Function *F = Function::Create(FT, Function::ExternalLinkage, "add", *M);
F->getArg(0)->setName("a");
F->getArg(1)->setName("b");
BasicBlock *Entry = BasicBlock::Create(Ctx, "entry", F);
Builder.SetInsertPoint(Entry);
Value *Sum = Builder.CreateAdd(F->getArg(0), F->getArg(1), "sum");
Builder.CreateRet(Sum);
// Verify the module.
if (verifyModule(*M, &errs())) {
errs() << "Module verification failed\n";
return 1;
}
// Print IR to stdout.
M->print(outs(), nullptr);
return 0;
}# Configure — point CMAKE_PREFIX_PATH at your LLVM 22 install prefix
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/path/to/llvm-22/install
# or: -DLLVM_DIR=$(llvm-config-22 --cmakedir)
# Build
cmake --build build -j$(nproc)
# Run
./build/<ProjectName>Expected output: LLVM IR for the add function.
#include "llvm/Passes/PassBuilder.h"
// After module is built and verified:
PassBuilder PB;
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
CGSCCAnalysisManager CGAM;
ModuleAnalysisManager MAM;
// Register all the standard analyses.
PB.registerModuleAnalyses(MAM);
PB.registerCGSCCAnalyses(CGAM);
PB.registerFunctionAnalyses(FAM);
PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
// Build a standard O2 pipeline.
ModulePassManager MPM = PB.buildPerModuleDefaultPipeline(OptimizationLevel::O2);
MPM.run(*M, MAM);If you want to ship a pass loadable by opt --load-pass-plugin, see the
add-npm-pass skill. Add this to your CMakeLists.txt alongside the executable:
add_library(MyPassPlugin MODULE src/MyPass.cpp)
target_include_directories(MyPassPlugin PRIVATE ${LLVM_INCLUDE_DIRS} include)
target_compile_definitions(MyPassPlugin PRIVATE ${LLVM_DEFINITIONS})
set_target_properties(MyPassPlugin PROPERTIES
CXX_STANDARD 17
POSITION_INDEPENDENT_CODE ON
)llvm-config --libs output directly in CMake — use llvm_map_components_to_libnames instead; the cmake integration handles RPATH and ordering correctly.InitializeAll*() functions after constructing a TargetMachine — initialize before.LLVMConfig.cmake.new Module(...) — use std::make_unique<Module>(...).Value * pointers after IR modifications that can RAUW (Replace All Uses With) them.verifyModule during development; it catches IR invariant violations early.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