Files
2026-02-16 03:31:07 +01:00

9.5 KiB

LLVM Instruction Operand Layout vs Printed IR

LLVM's internal operand storage order frequently diverges from the textual IR you see in .ll files. This is mostly due to a memory layout optimization: instructions with a variable number of operands (calls, branches, PHIs) use negative indexing from the end of the operand list (Op<-1>, Op<-2>, ...) so that fixed operands like the callee or branch targets always live at a known offset regardless of how many arguments or successors there are. The result is that raw getOperand(i) indices can be surprising — successors may be reversed, the callee may be last, and value/block pairs may be interleaved.

This document catalogs every instruction class, its internal operand layout, where it diverges from the printed IR, and the named accessor you should use instead. The golden rule: if a named accessor exists, use it. Treat getOperand(i) as an implementation detail.

Legend:

  • ⚠️ = operand order differs from printed IR or is a common source of confusion
  • = operand order matches printed IR straightforwardly

Terminal Instructions

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
⚠️ BranchInst (cond) br i1 %c, label %T, label %F 0: %c, 1: %F, 2: %T Successors reversed vs print getCondition(), getSuccessor(0) = true, getSuccessor(1) = false
BranchInst (uncond) br label %dst 0: %dst getSuccessor(0)
⚠️ SwitchInst switch i64 %v, label %def [ i64 0, label %bb0 ... ] 0: %v, 1: %def, then pairs: value, block, value, block... Case operands are interleaved value/block pairs getCondition(), getDefaultDest(), case_begin()/case_end() iterators, findCaseValue()
⚠️ IndirectBrInst indirectbr ptr %addr, [ label %bb0, label %bb1 ] 0: %addr, 1+: destinations Destinations are operands, not a separate list getAddress(), getDestination(i), getNumDestinations()
⚠️ InvokeInst invoke @foo(%a, %b) to label %N unwind label %E 0: %a, 1: %b, ..., -3: %E, -2: %N, -1: @foo Callee is last operand; successors reversed getCalledOperand(), getArgOperand(i), getNormalDest(), getUnwindDest()
⚠️ CallBrInst callbr @foo(%a) to label %fall [label %bb1, ...] 0: %a, ..., indirect dests..., -2: %fall, -1: @foo Same callee-last layout; fallthrough and indirect dests mixed in getCalledOperand(), getArgOperand(i), getDefaultDest(), getIndirectDest(i)
ReturnInst ret i32 %v / ret void 0: %v (or no operands) getReturnValue()
ResumeInst resume { ptr, i32 } %val 0: %val getValue()
⚠️ CatchSwitchInst catchswitch within %parent [label %h1, ...] unwind label %u 0: %parent, 1: %u (or none), 2+: handlers Unwind dest may or may not be present (unwind to caller) getParentPad(), getUnwindDest(), handler_begin()/handler_end()
CatchReturnInst catchret from %pad to label %bb 0: %pad, 1: %bb getCatchPad(), getSuccessor()
CleanupReturnInst cleanupret from %pad unwind label %bb 0: %pad, 1: %bb (optional) getCleanupPad(), getUnwindDest()
UnreachableInst unreachable (none)

Call-like Instructions

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
⚠️ CallInst call @foo(%a, %b) 0: %a, 1: %b, ..., last: @foo Callee is the last operand, not first getCalledOperand(), getArgOperand(i), arg_begin()/arg_end()
⚠️ InvokeInst (see above) (see above) (see above) (see above)
⚠️ CallBrInst (see above) (see above) (see above) (see above)

Memory Instructions

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
LoadInst load i32, ptr %p 0: %p getPointerOperand()
⚠️ StoreInst store i32 %v, ptr %p 0: %v, 1: %p Matches print, but opposite of load (where op 0 is the pointer). Easy to mix up getValueOperand(), getPointerOperand()
AllocaInst alloca i32, i64 %n 0: %n (array size, if present) getArraySize(), getAllocatedType()
FenceInst fence seq_cst (none) getOrdering(), getSyncScopeID()
⚠️ AtomicCmpXchgInst cmpxchg ptr %p, i32 %cmp, i32 %new 0: %p, 1: %cmp, 2: %new Matches print, but returns { i32, i1 } — easy to forget the success flag getPointerOperand(), getCompareOperand(), getNewValOperand()
AtomicRMWInst atomicrmw add ptr %p, i32 %v 0: %p, 1: %v getPointerOperand(), getValOperand(), getOperation()
GetElementPtrInst getelementptr i32, ptr %p, i64 %i, ... 0: %p, 1+: indices getPointerOperand(), idx_begin()/idx_end(), getNumIndices()

PHI & Select

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
⚠️ PHINode phi i32 [ %a, %bb1 ], [ %b, %bb2 ] 0: %a, 1: %bb1, 2: %b, 3: %bb2 Values and blocks interleaved — easy to get wrong with raw operands getIncomingValue(i), getIncomingBlock(i), getNumIncomingValues()
SelectInst select i1 %c, i32 %t, i32 %f 0: %c, 1: %t, 2: %f getCondition(), getTrueValue(), getFalseValue()

Binary & Unary

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
BinaryOperator add i32 %a, %b 0: %a, 1: %b getOperand(0), getOperand(1)
UnaryOperator fneg float %a 0: %a getOperand(0)

Comparison

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
ICmpInst icmp eq i32 %a, %b 0: %a, 1: %b getOperand(0), getOperand(1), getPredicate()
FCmpInst fcmp oeq float %a, %b 0: %a, 1: %b same as ICmpInst

Cast Instructions

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
All casts (ZExtInst, SExtInst, TruncInst, BitCastInst, PtrToIntInst, etc.) zext i32 %v to i64 0: %v getOperand(0), getSrcTy(), getDestTy()

Vector Instructions

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
ExtractElementInst extractelement <4 x i32> %v, i32 %idx 0: %v, 1: %idx getVectorOperand(), getIndexOperand()
InsertElementInst insertelement <4 x i32> %v, i32 %new, i32 %idx 0: %v, 1: %new, 2: %idx getOperand(0), getOperand(1), getOperand(2)
⚠️ ShuffleVectorInst shufflevector <4 x i32> %a, <4 x i32> %b, <4 x i32> <i32 0, ...> 0: %a, 1: %b — mask is not an operand Mask is stored separately since LLVM 11; getOperand(2) does not exist getShuffleMask(), getShuffleMaskForBitcode(), getMaskValue(i)

Aggregate Instructions

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
ExtractValueInst extractvalue { i32, i64 } %agg, 0 0: %agg — indices are not operands Indices stored separately as metadata getAggregateOperand(), getIndices(), idx_begin()/idx_end()
InsertValueInst insertvalue { i32, i64 } %agg, i32 %val, 0 0: %agg, 1: %val — indices not operands Same: indices stored separately getAggregateOperand(), getInsertedValueOperand(), getIndices()

Exception Handling

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
LandingPadInst landingpad { ptr, i32 } catch ptr @TypeInfo Clauses are operands getClause(i), getNumClauses(), isCatch(i), isFilter(i)
CatchPadInst catchpad within %cs [ptr %arg] 0: %cs, 1+: args getCatchSwitch(), getArgOperand(i)
CleanupPadInst cleanuppad within %parent [ptr %arg] 0: %parent, 1+: args getParentPad(), getArgOperand(i)

Miscellaneous

Instruction Printed IR getOperand(i) layout Gotcha Preferred API
FreezeInst freeze i32 %v 0: %v getOperand(0)

Summary of Major Gotchas

Gotcha Instructions Root cause
Successors stored in reverse BranchInst, InvokeInst, CallBrInst Negative indexing (Op<-1>) for variable operand count
Callee stored last CallInst, InvokeInst, CallBrInst Same negative indexing — callee is Op<-1>
Interleaved value/block pairs PHINode Each incoming edge is a (value, block) pair
Interleaved value/label pairs SwitchInst Each case is a (value, dest) pair
Mask is not an operand ShuffleVectorInst Mask moved out of operand list in LLVM 11
Indices are not operands ExtractValueInst, InsertValueInst Indices are stored as constant metadata
Pointer operand position differs LoadInst (op 0) vs StoreInst (op 1) StoreInst has value as op 0, pointer as op 1

Golden Rule

Never use raw getOperand(i) when a named accessor exists. The operand layout is an internal implementation detail optimized for memory layout, not human readability.