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 your language runtime needs a garbage collector and you must tell LLVM where GC-safe points are and which pointers are live.
| Model | Use when |
|---|---|
| gcroot / shadow stack | Simple stop-the-world GC, prototype, or interpreter. Easy to implement. |
| Statepoints | Moving/relocating collector (objects can be copied). Production use. |
For new language runtimes, prefer statepoints — gcroot is legacy and does not support moving objects.
// Mark each function that manages GC pointers
F->setGC("shadow-stack");Every heap-pointer local variable needs a root alloca in the entry block:
#include "llvm/IR/Intrinsics.h"
// Alloca for the GC root (always in entry block)
llvm::AllocaInst *Root = B.CreateAlloca(B.getPtrTy(), nullptr, "obj.root");
// Initialize to null before first use — GC may scan it before you store
B.CreateStore(llvm::ConstantPointerNull::get(B.getPtrTy()), Root);
// Declare llvm.gcroot
llvm::Function *GCRootFn = llvm::Intrinsic::getOrInsertDeclaration(
M, llvm::Intrinsic::gcroot, {});
// Mark the alloca as a GC root (must call immediately after alloca)
B.CreateCall(GCRootFn,
{Root, llvm::ConstantPointerNull::get(B.getPtrTy())});// Store the object pointer into the root before any call that may GC
B.CreateStore(ObjPtr, Root);
// ... calls that may trigger GC ...
// Reload after the GC point — the shadow-stack GC does NOT move objects,
// but reading from Root keeps the pattern consistent.
llvm::Value *LiveObj = B.CreateLoad(B.getPtrTy(), Root, "obj");You must supply a shadow-stack runtime (or link against one). Minimal C structure:
struct FrameMap { int NumRoots; int NumMeta; const void *Meta[]; };
struct StackEntry { struct StackEntry *Next; const struct FrameMap *Map; void *Roots[]; };
extern struct StackEntry *llvm_gc_root_chain; // thread-local in productionF->setGC("statepoint-example"); // built-in test strategy
// Or register a custom strategy — see Step B5RewriteStatepointsForGC pass (recommended)Let the pass automatically insert gc.statepoint + gc.relocate sequences around all calls:
#include "llvm/Transforms/Scalar/RewriteStatepointsForGC.h"
// Add to your function pass pipeline before codegen:
FPM.addPass(llvm::RewriteStatepointsForGCPass());The pass:
gc.statepoint + gc.relocate sequences.After this pass, every GC pointer use after a call site is replaced with a gc.relocate result.
If you emit statepoints manually instead of using the pass:
; Statepoint wraps a call to @foo(i32 %x) with GC pointer %obj1 live:
%sp = call token @llvm.experimental.gc.statepoint.p0(
i64 0, ; call ID
i32 0, ; patch bytes
ptr @foo, ; callee
i32 1, ; # call args
i32 0, ; flags
i32 %x, ; call arg 0
i32 0, ; # transition args
i32 0, ; # deopt args
ptr %obj1 ; GC live value (will appear in stack map)
)
%result = call i32 @llvm.experimental.gc.result.i32(token %sp)
%obj1.r = call ptr @llvm.experimental.gc.relocate(token %sp, i32 7, i32 7)
; After the statepoint, always use %obj1.r — NOT %obj1The C++ API equivalent:
#include "llvm/IR/Statepoint.h"
// Use IRBuilder helpers or construct via StatepointFlags / makeStatepointCall
// In practice, always prefer RewriteStatepointsForGCPassAfter compiling, the object file contains a __llvm_stackmaps section:
# Inspect the stack map
llvm-readobj --llvm-stackmap output.oYour GC runtime reads this section at startup to discover the locations of all live pointers at each GC-safe point.
Register a custom strategy for code generation hooks:
#include "llvm/CodeGen/GCStrategy.h"
class MyGCStrategy : public llvm::GCStrategy {
public:
MyGCStrategy() {
UseStatepoints = true;
UsesMetadata = false;
NeededSafePoints = 0;
}
};
static llvm::GCRegistry::Add<MyGCStrategy>
X("my-gc", "My language GC strategy");Functions with gc "my-gc" will use it during codegen.
# Compile the IR
llc -filetype=obj output.ll -o output.o
# Check the stack map section exists
llvm-readobj --llvm-stackmap output.o
# Disassemble to verify relocate sequences are present
llvm-dis output.bcgc.relocate result. The original pointer is invalid if the GC moved the object.RewriteStatepointsForGC without first calling F->setGC(...) — the pass silently skips functions without a GC strategy.null before the first gcroot call.gc.relocate for every live GC pointer across a statepoint — not just the ones you think are "interesting" to the optimizer.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