Add garbage-collection support to an LLVM 22 IR frontend. Covers the shadow-stack (gcroot) model for simple GCs and the statepoint/gc.relocate model for moving/relocating collectors, including the RewriteStatepointsForGC pass and StackMap section.
67
80%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./tiles/tessl-llvm/skills/add-gc-statepoints/SKILL.mdUse 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.a251bde
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.