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 a new first-class LLVM IR intrinsic — a built-in function with a well-known name (llvm.*) that the optimizer and backends can reason about specially.
If the user only needs to call an existing intrinsic from a frontend, skip to Step 4.
| Category | TableGen file | Example |
|---|---|---|
| Generic (target-independent) | include/llvm/IR/Intrinsics.td | llvm.memcpy, llvm.expect |
| Target-specific | include/llvm/IR/Intrinsics<Target>.td | llvm.x86.*, llvm.aarch64.* |
| In-tree experimental | include/llvm/IR/IntrinsicsExperimental.td | Prefix: llvm.experimental.* |
For a new language or target, use target-specific (IntrinsicsMyTarget.td) or prefix with llvm.experimental. during development.
Open the appropriate .td file and add:
// In include/llvm/IR/Intrinsics.td (or Intrinsics<Target>.td)
def int_my_intrinsic : DefaultAttrsIntrinsic<
[llvm_i32_ty], // Return types (list; use [] for void)
[llvm_i32_ty, llvm_i32_ty], // Parameter types
[IntrNoMem, IntrWillReturn] // Attributes / properties
>;The function name is derived from the def name:
int_my_intrinsic → llvm.my.intrinsicint_x86_my_op → llvm.x86.my.op| Property | Meaning |
|---|---|
IntrNoMem | Does not access memory |
IntrReadMem | Only reads memory |
IntrWriteMem | Only writes memory |
IntrArgMemOnly | Only accesses memory via pointer args |
IntrWillReturn | Always returns (no infinite loops/throws) |
IntrSpeculatable | Safe to speculate (no side effects) |
IntrHasSideEffects | Has side effects; cannot be removed |
Commutative | First two args commute |
ImmArg<ArgIndex<N>> | Arg N must be an immediate constant |
ReadOnly<ArgIndex<N>> | Pointer arg N is read-only |
llvm_i1_ty llvm_i8_ty llvm_i16_ty llvm_i32_ty llvm_i64_ty
llvm_half_ty llvm_float_ty llvm_double_ty
llvm_ptr_ty // opaque pointer (LLVM 22 default)
llvm_anyint_ty llvm_anyfloat_ty // overloaded
llvm_anyvector_ty
llvm_void_tyFor an overloaded intrinsic (type varies per call):
def int_my_abs : DefaultAttrsIntrinsic<
[llvm_anyint_ty],
[LLVMMatchType<0>], // param type must match return type
[IntrNoMem, IntrWillReturn]
>;LLVM generates IntrinsicEnums.inc and IntrinsicImpl.inc from the .td files.
During development (rebuild LLVM):
cmake --build build --target intrinsics_gen
# or just rebuild:
cmake --build build -j$(nproc)The generated files end up in:
build/include/llvm/IR/IntrinsicEnums.inc
build/include/llvm/IR/IntrinsicImpl.incYou do not hand-edit these files — they are always regenerated.
After regeneration, the new intrinsic is accessible via:
llvm::Intrinsic::my_intrinsic // enum valueFor a target-specific intrinsic to generate real machine code:
Add a GINodeEquiv entry in the target's .td file and a GICombineRule or
GISelCombinerPass entry, or handle it in <Target>LegalizerInfo.cpp:
// In <Target>LegalizerInfo.cpp, legalizeIntrinsic():
case Intrinsic::my_intrinsic: {
MIRBuilder.buildInstr(MyTarget::MY_OPCODE)
.addDef(MI.getOperand(0).getReg())
.addUse(MI.getOperand(2).getReg());
MI.eraseFromParent();
return true;
}Override LowerINTRINSIC_WO_CHAIN or LowerINTRINSIC_W_CHAIN in
<Target>ISelLowering.cpp:
SDValue <Target>TargetLowering::LowerINTRINSIC_WO_CHAIN(
SDValue Op, SelectionDAG &DAG) const {
unsigned IntNo = Op.getConstantOperandVal(0);
switch (IntNo) {
case Intrinsic::my_intrinsic:
return DAG.getNode(MyTargetISD::MY_NODE, SDLoc(Op), Op.getValueType(),
Op.getOperand(1), Op.getOperand(2));
}
return SDValue();
}#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
// Inside a function body:
IRBuilder<> Builder(InsertBB, InsertBB->end());
// Option A: IRBuilder::CreateIntrinsic (LLVM 22 preferred API)
Value *Result = Builder.CreateIntrinsic(
Intrinsic::my_intrinsic,
{Builder.getInt32Ty()}, // type arguments for overloaded intrinsics; {} if not overloaded
{ArgA, ArgB} // call arguments
);
// Option B: Manual — getOrInsertFunction then CreateCall
Function *F = Intrinsic::getOrInsertDeclaration(M, Intrinsic::my_intrinsic,
{Builder.getInt32Ty()});
Value *Result2 = Builder.CreateCall(F, {ArgA, ArgB});In LLVM 22,
Intrinsic::getDeclaration()is removed — useIntrinsic::getOrInsertDeclarationinstead.
If the intrinsic has non-trivial constraints (e.g., arg 2 must be a constant,
pointer alignment requirements), add a check in lib/IR/Verifier.cpp:
case Intrinsic::my_intrinsic: {
Assert(isa<ConstantInt>(Call.getArgOperand(1)),
"llvm.my.intrinsic: second argument must be a constant integer", &Call);
break;
}Path: test/CodeGen/<Target>/my-intrinsic.ll (for codegen tests)
or test/Transforms/<Category>/my-intrinsic.ll (for IR-level tests)
; RUN: opt -passes=verify -S %s | FileCheck %s
; RUN: opt -passes=instcombine -S %s | FileCheck %s --check-prefix=OPT
declare i32 @llvm.my.intrinsic(i32, i32)
define i32 @test(i32 %a, i32 %b) {
; CHECK-LABEL: @test
%r = call i32 @llvm.my.intrinsic(i32 %a, i32 %b)
; CHECK: call i32 @llvm.my.intrinsic
ret i32 %r
}
; Test constant folding (if applicable)
define i32 @test_const() {
; OPT-LABEL: @test_const
%r = call i32 @llvm.my.intrinsic(i32 3, i32 4)
; OPT: ret i32 7
ret i32 %r
}Run:
llvm-lit test/CodeGen/<Target>/my-intrinsic.ll -vIf the intrinsic has constant-fold / simplification rules, add them in
lib/Transforms/InstCombine/InstCombineCalls.cpp:
case Intrinsic::my_intrinsic: {
// Fold llvm.my.intrinsic(c1, c2) to constant when both args are constants.
if (auto *C1 = dyn_cast<ConstantInt>(II->getArgOperand(0)))
if (auto *C2 = dyn_cast<ConstantInt>(II->getArgOperand(1)))
return IC.replaceInstUsesWith(
*II, ConstantInt::get(II->getType(),
C1->getValue() + C2->getValue()));
break;
}Intrinsic::getDeclaration() — it is removed in LLVM 22; use getOrInsertDeclaration() or getDeclarationIfExists().intrinsics_gen after editing .td files.i8*) in intrinsic signatures — LLVM 22 uses opaque pointers (ptr) everywhere.declare in your .td and also hand-write a declare in IR — the verifier will reject duplicate declarations with mismatched types.IntrNoMem, IntrReadMem, etc.) — wrong attributes block valid optimizations or allow incorrect ones.-passes=verify before testing with optimization passes.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