Lesson 10.3 — Building IR: IRBuilder, insertion points, folders and inserters¶
Techniques: direct construction (
BinaryOperator::Createwith anInsertPosition), insertion points (SetInsertPoint, iterator-based insertion and the debug-record head bit), folders (ConstantFolder,NoFolder,InstSimplifyFolder,TargetFolder), inserters (IRBuilderDefaultInserter,IRBuilderCallbackInserter, custom inserters), builder state (fast-math flags, debug locations, constrained FP, guards) · Pebble uses:IRBuilder<>in its code generator (Ch 11) and in every pass that creates code · Lab: Lab 10.1 · Prerequisites: Lessons 10.1, 10.2 · Time: 3 hours
To build the running example you could call five constructors, each with the right operand types and a position to insert at. Or you could write:
IRBuilder<> B(BasicBlock::Create(Ctx, "entry", F));
Value *A = B.CreateAdd(X, Y, "a");
Value *Bv = B.CreateMul(A, A, "b");
Value *C = B.CreateSub(Bv, A, "c");
B.CreateRet(B.CreateXor(X, C, "d"));
The builder remembers where to insert, so each call appends to entry. It also decides whether to create an instruction at all: B.CreateAdd(B.getInt32(6), B.getInt32(7)) creates nothing and returns the constant 13. And it carries state (debug location, fast-math flags) that is stamped onto every instruction it creates. This lesson makes the three mechanisms precise: position, folding and state.
1. Problem and motivation¶
Front ends and passes emit instructions all the time, and every emission has to answer the same questions:
- which block, and before which instruction (and, since LLVM 19, before or after the debug records attached there);
- should an operation on constants be computed now or emitted;
- which flags and which source location go on the new instruction.
Getting any of these wrong gives invalid IR (the verifier catches it), wrong debug info (nobody catches it), or IR that later passes must clean up [LLVM-IRB, KAL].
Direct construction¶
Every instruction class has static Create functions that take operands and an InsertPosition (a block iterator, a block end, or nothing). This is the lowest level: no folding and no state. It is what the builder itself calls, and what passes use when they need exact control, such as the rewrite in Lab 10.2 [LLVM-Instr].
Insertion points¶
A builder holds a current block and an iterator in it, and new instructions go before the iterator. Since the move from debug-info intrinsics to debug records [LLVM-DbgRecords], an iterator carries one more bit, the head bit. It decides whether an instruction inserted at a position with attached #dbg_value records goes before or after them. That is why LLVM 23 deprecates the InsertPosition(Instruction *) constructor and getFirstNonPHI(). (IRBuilder::SetInsertPoint(Instruction *) still exists; it behaves like the instruction's own iterator, with the head bit clear.)
Folders¶
A folder is the builder's peephole: before creating op a, b it asks the folder whether the result is already known. Three folders are provided:
ConstantFolder(the default) folds operations whose operands are all constants;NoFoldernever folds, which is useful in tests and when you need the literal instruction;InstSimplifyFolderruns InstSimplify, sox + 0returnsxandx - xreturns0.
TargetFolder is the data-layout-aware variant that Clang uses [LLVM-Folders].
Inserters¶
The inserter is the hook that actually places a created instruction (InsertHelper). Clang replaces it to attach its own bookkeeping to every emitted instruction (CGBuilderInserter), and IRBuilderCallbackInserter lets a pass count or post-process what it creates [LLVM-IRB, CLANG-CGBuilder].
Builder state¶
Floating-point code needs fast-math flags and sometimes strict FP semantics, and everything needs a source location. The builder holds these as state (setFastMathFlags, SetCurrentDebugLocation, setIsFPConstrained) and applies them to each instruction it creates. RAII guards (InsertPointGuard, FastMathFlagGuard) restore them [LLVM-IRB].
2. Definitions and algorithms¶
Definition 10.3.1 (Insertion point)
An insertion point is a pair \((B, p)\) of a block and a position \(p \in \mathrm{insts}(B) \cup \{\mathrm{end}(B)\}\), together with a head bit \(\eta \in \{0, 1\}\). Inserting an instruction \(I\) at \((B, p, \eta)\) links \(I\) immediately before \(p\) (Algorithm 10.1.7 Insert). If \(p\) carries debug records \(R_p\) (records that describe variables just before \(p\)), then:
- \(\eta = 1\) places \(I\) before \(R_p\);
- \(\eta = 0\) places \(I\) after \(R_p\) (the records move onto \(I\)).
BasicBlock::begin(), getFirstNonPHIIt() and getFirstInsertionPt() return iterators with \(\eta = 1\). Instruction::getIterator() returns \(\eta = 0\). A null insertion point means "create but do not insert".
Definition 10.3.2 (Folder)
A folder is a partial function \(\phi(\mathit{op}, a_1, \dots, a_k)\) that either returns an existing value \(v\) (a constant, an argument, or an already-built instruction) or is undefined (\(\bot\)). It is sound if \(\phi(\mathit{op}, \vec{a}) = v\) implies that \(v\) refines the instruction \(\mathit{op}\ \vec{a}\): for every assignment of the free values, whenever op a⃗ is not poison, \(v\) has the same value (Ch 9, refinement, Ch 13). The provided folders satisfy:
- \(\mathrm{dom}(\phi_{\mathrm{No}}) = \emptyset\);
- \(\mathrm{dom}(\phi_{\mathrm{Const}}) \subseteq \{\,(\mathit{op}, \vec{a}) \mid \text{every } a_i \text{ is a constant}\,\}\): the all-constant inputs that constant folding evaluates, or that LLVM 23 can still keep as a constant expression (
add,sub,xor). The rest stay instructions:mul i64 ptrtoint (ptr @g to i64), 2is not folded, becausemulconstant expressions no longer exist; - $\phi_{\mathrm{Simplify}} = $
simplifyBinOpetc., defined on all-constant inputs and on the identities InstSimplify proves (x+0,x-x,x&x, …).
Algorithm 10.3.3 (IRBuilder create)
- Input: builder state \(\sigma\) (insertion point, folder \(\phi\), inserter \(\iota\), debug location \(\ell\), fast-math flags \(\mathit{fmf}\), …); an opcode and operands.
- Output: a value \(v\) equal to (a refinement of)
op a⃗. - Precondition: operand types match the opcode; every operand dominates the insertion point (or is a constant or argument).
- Postcondition: either (folded) \(v = \phi(\mathit{op}, \vec{a})\) and nothing was inserted, or (created) \(v\) is a new instruction inserted at the insertion point, named, with debug location \(\ell\) and, for FP operations, flags \(\mathit{fmf}\). The inserter ran exactly once for it.
- Invariant: the builder's state is unchanged by the call.
function Create(σ, op, a⃗, name):
if σ.constrainedFP and op has a constrained form: # checked first: never folded
I ← call of the constrained intrinsic with a⃗, σ.rounding, σ.except
else:
v ← σ.folder.Fold(op, a⃗) # e.g. FoldNoWrapBinOp, FoldBinOpFMF
if v ≠ ⊥:
return v # no instruction, no inserter call
I ← new Instruction(op, a⃗) # direct construction, unlinked
if op is floating point: I.setFastMathFlags(σ.fmf)
σ.inserter.InsertHelper(I, name, σ.insertPt) # links I before insertPt, sets name
I.setDebugLoc(σ.debugLoc)
return I
In llvm/include/llvm/IR/IRBuilder.h: CreateAdd calls Folder.FoldNoWrapBinOp and then CreateInsertNUWNSWBinOp, and Insert calls Inserter.InsertHelper and then SetInstDebugLocation [LLVM-IRB]. CreateFAddFMF tests IsFPConstrained before asking the folder and then calls CreateConstrainedFPBinOp, which builds the intrinsic call with CreateIntrinsicWithoutFolding (llvm/lib/IR/IRBuilder.cpp): in constrained mode even CreateFAdd(1.0, 2.0) emits a call, because folding would lose the exception behavior.
Direct construction¶
Algorithm 10.3.4 (Create an instruction at a position)
- Input: an opcode, operands, a name, an
InsertPosition\(q\) (an iterator, a block to append to, or null). - Output: a new instruction \(I\).
- Precondition: as for Algorithm 10.3.3.
- Postcondition: \(I\)'s operands are set in order (Algorithm 10.2.3), and \(I\) is linked before \(q\) (or at the end of the block, or not at all if \(q\) is null).
- Invariant: nothing but \(I\) and its operands' use lists changes.
Direct construction, and the deprecated Instruction * position
Reproduce (clang 23.1.2, LLVM 23.1.2, Linux x86-64):
cd chapters/10-llvm-cpp-api/examples
clang++-23 $(llvm-config --cxxflags) -std=c++23 direct.cpp $(llvm-config --ldflags --libs) \
-Wl,-rpath,$(llvm-config --libdir) -o direct 2>&1 | head -2 && ./direct
Output (complete):
direct.cpp:19:63: warning: 'InsertPosition' is deprecated: Use BasicBlock::iterators for insertion instead [-Wdeprecated-declarations]
19 | BinaryOperator::Create(Instruction::Sub, X, Y, "old_style", Ret);
define i32 @f(i32 %0, i32 %1) {
entry:
%sum = add i32 %0, %1
%sq = mul i32 %0, %0
%old_style = sub i32 %0, %1
ret i32 %sum
}
What to notice: Create(…, BB->end()) appends, and Create(…, Ret->getIterator()) inserts before ret (Algorithm 10.3.4). Passing the Instruction * still works but is deprecated in LLVM 23: it cannot carry the head bit of Definition 10.3.1. No folding happened: sub i32 %0, %1 is created even though a builder would have produced the same thing. Direct construction never folds.
Insertion points¶
The head bit: before or after the debug records
Reproduce (clang 23.1.2, LLVM 23.1.2, Linux x86-64):
cd chapters/10-llvm-cpp-api/examples
clang++-23 $(llvm-config --cxxflags) -std=c++23 insert.cpp $(llvm-config --ldflags --libs) \
-Wl,-rpath,$(llvm-config --libdir) -o insert && ./insert dbg.ll
dbg.ll's entry block is #dbg_value(i32 %x, …) followed by %y = add and ret. The program parses it twice, and inserts a mul once with B.SetInsertPoint(&BB, BB.begin()) (an iterator, \(\eta = 1\)) and once with B.SetInsertPoint(&BB.front()) (an Instruction *, \(\eta = 0\)).
Output (complete):
entry:
%via_iterator = mul i32 %x, %x, !dbg !6
#dbg_value(i32 %x, !7, !DIExpression(), !6)
%y = add i32 %x, 1, !dbg !6
ret i32 %y, !dbg !6
entry:
#dbg_value(i32 %x, !6, !DIExpression(), !8)
%via_pointer = mul i32 %x, %x, !dbg !8
%y = add i32 %x, 1, !dbg !8
ret i32 %y, !dbg !8
What to notice: the same "insert before %y" lands on different sides of the #dbg_value record, exactly as Definition 10.3.1 says. It matters when the new instruction defines something the record should describe, and for phis: inserting a phi after debug records asserts ("Inserting PHI after debug-records!" in Instruction::insertBefore). Both copies got a !dbg location because SetInsertPoint copies the location of the instruction at the position.
What the verifier says about bad insertion points
Reproduce (clang 23.1.2, LLVM 23.1.2, Linux x86-64):
cd chapters/10-llvm-cpp-api/examples
clang++-23 $(llvm-config --cxxflags) -std=c++23 verify.cpp $(llvm-config --ldflags --libs) \
-Wl,-rpath,$(llvm-config --libdir) -o verify && ./verify
The program defines %t in block then, moves the insertion point back to before entry's branch to emit %u = mul %t, %t, and then emits two rets in then.
Output (complete):
Instruction does not dominate all uses!
%t = add i32 %0, 1
%u = mul i32 %t, %t
Instruction does not dominate all uses!
%t = add i32 %0, 1
%u = mul i32 %t, %t
Terminator found in the middle of a basic block!
label %then
broken=1
What to notice: the builder happily violates the precondition of Algorithm 10.3.3 ("every operand dominates the insertion point"); verifyFunction reports it once per offending use. Inserting after a terminator is the other classic mistake of a forgotten SetInsertPoint. Always run the verifier on what you build (Lab 10.1 R1 requires it).
Folders¶
Three folders, one instruction sequence
Reproduce (clang 23.1.2, LLVM 23.1.2, Linux x86-64):
cd chapters/10-llvm-cpp-api/examples
clang++-23 $(llvm-config --cxxflags) -std=c++23 folders.cpp $(llvm-config --ldflags --libs) \
-Wl,-rpath,$(llvm-config --libdir) -o folders && ./folders
Each function issues the same calls: k = 6*7, p = x+0, q = p&p, r = q-q, ret r+k.
Output (complete):
; ModuleID = 'folders'
source_filename = "folders"
define i32 @constant_folder(i32 %0) {
entry:
%p = add i32 %0, 0
%q = and i32 %p, %p
%r = sub i32 %q, %q
%s = add i32 %r, 42
ret i32 %s
}
define i32 @no_folder(i32 %0) {
entry:
%k = mul i32 6, 7
%p = add i32 %0, 0
%q = and i32 %p, %p
%r = sub i32 %q, %q
%s = add i32 %r, %k
ret i32 %s
}
define i32 @instsimplify_folder(i32 %0) {
entry:
ret i32 42
}
What to notice: the domains of Definition 10.3.2. ConstantFolder folded only 6*7, NoFolder folded nothing (even mul i32 6, 7 stays an instruction), and InstSimplifyFolder folded everything. Each fold returned a value that already existed, so the next call's operands were already simplified: x+0 → %0, then %0 & %0 → %0, then %0 - %0 → 0, then 0 + 42 → 42.
Inserters¶
Definition 10.3.5 (Inserter)
An inserter is an object with a method \(\iota(I, \mathit{name}, q)\) that links an unlinked instruction \(I\) at insertion point \(q\) and gives it a name. It may do more, such as attach metadata or record \(I\) in a worklist. IRBuilder<Folder, Inserter> calls it for, and only for, every instruction it creates (Algorithm 10.3.3).
A callback inserter counts and tags what the builder creates
Reproduce (clang 23.1.2, LLVM 23.1.2, Linux x86-64):
cd chapters/10-llvm-cpp-api/examples
clang++-23 $(llvm-config --cxxflags) -std=c++23 inserter.cpp $(llvm-config --ldflags --libs) \
-Wl,-rpath,$(llvm-config --libdir) -o inserter && ./inserter
Output (complete):
instructions inserted: 4
define double @g(double %0, double %1) {
entry:
%plain = fadd double %0, %1, !built.by !0
%reassoc_nnan = fmul reassoc nnan double %plain, %0, !built.by !0
%plain_again = fsub double %reassoc_nnan, %1, !built.by !0
ret double %plain_again, !built.by !0
}
What to notice: the program made five builder calls. The last one, CreateFAdd(1.0, 2.0), was folded to a constant and never reached the inserter, so the count is 4 (Theorem 10.3.9). The flags also show builder state and its guard: only the fmul inside the FastMathFlagGuard scope carries reassoc nnan.
Builder state¶
Definition 10.3.6 (Builder state)
The state of an IRBuilderBase is the tuple
the insertion block and position, the current debug location, the default fast-math flags, the default !fpmath tag, whether FP operations are constrained (\(c\)), the default rounding mode (\(\rho\)) and exception behavior (\(\epsilon\)) for constrained operations, and the default operand bundles. Every creation reads \(\sigma\) (Algorithm 10.3.3), and only the setters change it. A guard is an RAII object that saves some components of \(\sigma\) on construction and restores them on destruction (InsertPointGuard saves \((B, p, \ell)\); FastMathFlagGuard saves \((\mathit{fmf}, \mathit{fpmath}, c, \rho, \epsilon)\)).
Debug location, fast-math and constrained FP are builder state
Reproduce (clang 23.1.2, LLVM 23.1.2, Linux x86-64):
cd chapters/10-llvm-cpp-api/examples
clang++-23 $(llvm-config --cxxflags) -std=c++23 state.cpp $(llvm-config --ldflags --libs) \
-Wl,-rpath,$(llvm-config --libdir) -o state && ./state
Output (complete):
; Function Attrs: strictfp
define double @h(double %0, double %1) #0 !dbg !2 {
entry:
%a = fadd double %0, %1, !dbg !5
%b = fmul fast double %a, %0, !dbg !5
%c = call double @llvm.experimental.constrained.fadd.f64(double %b, double %1, metadata !"round.towardzero", metadata !"fpexcept.strict") #0, !dbg !5
ret double %c, !dbg !5
}
What to notice: one SetCurrentDebugLocation stamped !dbg !5 on all four instructions, setFastMathFlags(fast) affected only the fmul because the flags were cleared right after, and after setIsFPConstrained(true) the same call CreateFAdd produced a constrained intrinsic with the builder's rounding mode, using the MetadataAsValue operands of Lesson 10.2. (This program mixes both modes only to show the switch. In a real strictfp function every FP operation must be constrained.)
3. Worked example¶
We build the folders.cpp sequence under each folder, starting from an empty entry block of define i32 @f(i32 %0). The insertion point is \((\mathit{entry}, \mathrm{end})\) throughout.
Direct construction¶
With BinaryOperator::Create(…, BB->end()) for each step, nothing folds. The block gets the same five instructions (mul 6, 7, add %0, 0, and, sub, add) as the NoFolder column below, in call order (Algorithm 10.3.4).
Insertion points¶
All calls use the fixed point \((\mathit{entry}, \mathrm{end})\), so by Theorem 10.3.7 the created instructions appear in call order. If after step 3 we set the insertion point to %q's iterator and emit %z = add %0, 1, the block order becomes …, %p, %z, %q, …: %z lands before %q. It may use %p (defined earlier) but not %q (defined later): the verifier box's mistake.
Folders¶
| step | call | ConstantFolder | NoFolder | InstSimplifyFolder |
|---|---|---|---|---|
| 1 | k = CreateMul(6, 7) |
42 (folded) | new %k |
42 |
| 2 | p = CreateAdd(%0, 0) |
new %p |
new %p |
%0 (\(x + 0 = x\)) |
| 3 | q = CreateAnd(p, p) |
new %q = and %p, %p |
new %q |
%0 (\(x \wedge x = x\); here \(p\) is %0) |
| 4 | r = CreateSub(q, q) |
new %r = sub %q, %q |
new %r |
0 (\(x - x = 0\)) |
| 5 | s = CreateAdd(r, k) |
new %s = add %r, 42 |
new %s = add %r, %k |
42 (\(0 + 42\), both constants) |
| 6 | CreateRet(s) |
ret %s |
ret %s |
ret i32 42 |
| instructions inserted | 5 | 6 | 1 |
(The step counts include the ret, which no folder can fold.)
Inserters¶
With the callback inserter of the box around the InstSimplifyFolder builder, the callback runs once, for ret (step 6). With a ConstantFolder builder it runs five times (steps 2–6), and with NoFolder six times.
Builder state¶
The builder state (Definition 10.3.6) during the state.cpp run:
| call | \(\ell\) | \(\mathit{fmf}\) | \(c\), \(\rho\) | result |
|---|---|---|---|---|
SetCurrentDebugLocation(3:7) |
3:7 | none | off | — |
CreateFAdd(x, y) |
3:7 | none | off | fadd, !dbg 3:7 |
setFastMathFlags(fast); CreateFMul |
3:7 | fast | off | fmul fast |
clearFastMathFlags; setIsFPConstrained(true); rounding ← towardzero |
3:7 | none | on, towardzero | — |
CreateFAdd(b, y) |
3:7 | none | on | call @llvm.experimental.constrained.fadd.f64(…, "round.towardzero", "fpexcept.strict") |
Try it
./course drill irbuilder-fold --seed 1 --difficulty hard asks for these results under all three folders; --solution shows the table.
4. Invariants and correctness¶
Direct construction¶
Algorithm 10.3.4 is correct by construction: operand Sets keep def-use consistency (Theorem 10.2.11), and the insertion is Algorithm 10.1.7's Insert, so the list stays well formed (Lemma 10.1.13). What it does not guarantee is dominance, because the caller chose the position. Theorem 10.3.7(b) states the condition.
Insertion points¶
Theorem 10.3.7 (Emission order and dominance)
Let a builder make creation calls \(c_1, \dots, c_n\) while its insertion point stays \((B, p)\), and let \(I_{j_1}, \dots, I_{j_r}\) be the instructions actually created. Then:
- (a) they appear in \(B\) in the order \(I_{j_1}, \dots, I_{j_r}\), immediately before \(p\);
- (b) if every operand of every call is a constant, an argument, an instruction that dominates \(p\), or the result of an earlier call \(c_i\), then every created instruction's operands dominate it, so the function keeps the SSA dominance property.
Proof
(a) By induction on \(r\). \(I_{j_1}\) is inserted immediately before \(p\). Suppose \(I_{j_1}, \dots, I_{j_{s}}\) are, in order, immediately before \(p\). The next creation inserts \(I_{j_{s+1}}\) immediately before \(p\) (Algorithm 10.1.7), which is after \(I_{j_s}\), and by Lemma 10.1.13 the relative order of the others is unchanged. (b) Take an operand \(v\) of \(I_{j_s}\). Constants and arguments dominate everything. If \(v\) dominates \(p\), it dominates every instruction immediately before \(p\), including \(I_{j_s}\): a dominator of \(p\) is either in a block strictly dominating \(B\), or earlier in \(B\) than the instructions inserted just before \(p\). If \(v\) is the result of an earlier call, it is either a folded value (a constant, an argument, or an operand of that call, which dominates by induction), or an instruction \(I_{j_t}\) with \(t < s\), which precedes \(I_{j_s}\) in \(B\) by (a) and therefore dominates it.
When it breaks: changing the insertion point to a block that does not dominate the earlier results (the verifier box), or to a position before an earlier result in the same block.
Folders¶
Proposition 10.3.8 (Folding is safe and never inserts)
If the folder is sound (Definition 10.3.2), then replacing op a⃗ by \(\phi(\mathit{op}, \vec{a})\) in Algorithm 10.3.3 preserves the program's semantics (the result refines the instruction), the returned value satisfies Theorem 10.3.7(b), and the inserter is not called for the folded operation. Moreover, on integer operations, \(\mathrm{dom}(\phi_{\mathrm{No}}) \subseteq \mathrm{dom}(\phi_{\mathrm{Const}}) \subseteq \mathrm{dom}(\phi_{\mathrm{Simplify}})\).
Proof
Semantics: soundness says \(v\) refines op a⃗, and refinement is preserved under substitution into any context (Ch 13). Dominance: the provided folders return a constant, one of the operands \(a_i\) (InstSimplify's identities return operands or constants), or, rarely, another value that InstSimplify found among the operands' operands, which dominates them and hence the insertion point, by the same transitivity argument as Theorem 10.2.13. No insertion: in Algorithm 10.3.3 the folded path returns before reaching the inserter. Inclusions: \(\emptyset\) is contained in everything. For integer operations on constants, simplifyBinOp calls ConstantFoldBinaryOpOperands (llvm/lib/Analysis/ConstantFolding.cpp), which tries symbolic evaluation first and otherwise does exactly what ConstantFolder::FoldBinOp does (a desirable constant expression, or ConstantFoldBinaryInstruction), so every integer input that ConstantFolder folds, InstSimplifyFolder folds too.
InstSimplifyFolder's fold is a real InstSimplify query. That is why the drill irbuilder-fold can be checked against real LLVM, exhaustively, by tests/ch10/unit/OracleCrossCheckTest.cpp.
Inserters¶
Theorem 10.3.9 (The inserter sees exactly the created instructions)
For any sequence of builder calls, the multiset of instructions passed to \(\iota\) equals the set of instructions the builder created, each passed once.
Proof
By Algorithm 10.3.3, the only path to Insert is the non-folded path, which is taken once per created instruction, and Insert calls the inserter once. Folded calls return before Insert. A builder method that creates several instructions creates each one through Insert.
Builder state¶
Proposition 10.3.10 (Guards restore state)
If code between a guard's construction and destruction changes only builder state (and creates instructions), then after the guard's destruction the saved components of \(\sigma\) equal their values at construction. This holds on every exit path, including early returns.
Proof
The guard copies the components into its members at construction. Its destructor, which C++ runs on every exit from the enclosing scope, writes them back with the setters. No other code writes those members of the guard.
5. Complexity¶
Variables: \(k\) = number of operands, \(d\) = InstSimplify's recursion limit (RecursionLimit = 3 in llvm/lib/Analysis/InstructionSimplify.cpp), \(s\) = size of the constant expression being folded.
| Technique | Time per call (worst) | Time (typical) | Space | Notes |
|---|---|---|---|---|
| Direct construction | \(O(k)\) | \(O(k)\) | one allocation (co-allocated operands) | no folding |
| Insertion points | \(O(1)\) to set; \(O(1)\) insert | \(O(1)\) | the iterator + head bit | getFirstInsertionPt scans past phis: \(O(\#\text{phis})\) |
| ConstantFolder | \(O(s)\) | \(O(1)\) | uniqued constants | only all-constant operands |
| NoFolder | \(O(1)\) | \(O(1)\) | — | always creates |
| InstSimplifyFolder | bounded by the recursion limit: \(O(c^{d})\) for a branching factor \(c\) of the simplifier, in practice small | \(O(1)\)–\(O(k)\) plus known-bits queries | — | returns existing values |
| Inserters | \(O(1)\) + callback cost | \(O(1)\) | — | — |
| Builder state | \(O(1)\) per setter; \(O(1)\) per stamped instruction | \(O(1)\) | a few words | guards \(O(1)\) |
Proposition 10.3.11 (Builder emission is linear)
A front end that emits \(n\) instructions through IRBuilder<ConstantFolder> with operands of bounded arity does \(O(n)\) work in the builder.
Proof
Each call does constant work outside the folder (Algorithm 10.3.3). ConstantFolder either returns \(\bot\) in \(O(1)\) (after a check per operand) or folds constants of bounded size. Constants built by previous folds are uniqued, so they do not grow with \(n\) unless the program itself contains large constant expressions.
Pathological input. B.SetInsertPoint(BB->getFirstInsertionPt()) inside a loop that emits one instruction per iteration into a block with \(p\) phis costs \(\Theta(n \cdot p)\): each call rescans the phis. Save the iterator once. With InstSimplifyFolder, each fold may compute known bits of its operands, which walks up to the recursion depth of the operand DAG. That is cheap per call, but it is a real cost in a front end that emits millions of instructions, which is why Clang uses TargetFolder (constant folding only).
6. Variants and refinements¶
Direct construction¶
- Unlinked creation (
nullptrposition) followed byinsertInto/insertBefore(iterator): build a sequence first, then place it. This is useful when the position is not yet known. Instruction::clonecreates an unlinked copy with the same operands (Lesson 10.2's use lists grow accordingly), theninsertBeforeplaces it. Loop unrolling and inlining work this way.
Insertion points¶
SetInsertPointPastAllocas(F): the first position after the entry block's staticallocas. Use it for new allocas, so that they stay in the entry block andmem2regcan see them (Ch 16).InsertPointGuardandsaveIP/restoreIP: temporary detours, restored on scope exit (Proposition 10.3.10).moveBeforePreserving(iterator): moves an instruction and keeps its debug records with it [LLVM-DbgRecords].
Folders¶
TargetFolder(llvm/include/llvm/Analysis/TargetFolder.h): constant folding that also uses theDataLayout(pointer sizes, GEP offsets) throughConstantFoldBinaryOpOperands. Clang'sCGBuilderTyuses it.- Custom folders: subclass
IRBuilderFolderto add domain rules, or to make folding deterministic across LLVM versions in a test. - Folding later instead of now: build with
NoFolderorConstantFolderand runinstsimplify/instcombineafterwards. This is more robust and whole-function, but pays for the extra IR in the meantime.
Inserters¶
- Clang's
CGBuilderInserterforwards toCodeGenFunction::InsertHelper(clang/lib/CodeGen/CodeGenFunction.cpp), which callsLoopStack.InsertHelper(I)(loop metadata) and marks instructions emitted inside a sanitizer scope withsetNoSanitizeMetadata()[CLANG-CGBuilder]. - Worklist inserters: InstCombine's builder is
IRBuilder<TargetFolder, IRBuilderInstCombineInserter>(llvm/include/llvm/Transforms/InstCombine/InstCombiner.h), whoseInsertHelperadds each new instruction to InstCombine's worklist (and registers newllvm.assumes), so newly created code is itself combined.
Builder state¶
- Default operand bundles and
!fpmathtags (setDefaultOperandBundles,setDefaultFPMathTag). IRBuilder<>::CreateIntrinsic/CreateBinaryIntrinsic: builder-level access to intrinsics with the same folding and state (Lab 10.1 uses it forsmin).
7. In real compilers¶
Direct construction¶
LLVM
llvm/include/llvm/IR/InstrTypes.h BinaryOperator::Create, llvm/include/llvm/IR/Instruction.h InsertPosition (the Instruction * constructor is LLVM_DEPRECATED), llvm/lib/IR/Instruction.cpp Instruction::insertBefore(BasicBlock &, iterator) [LLVM-Instr].
- InstCombine returns a new, unlinked instruction from a visit method and lets the driver insert it and RAUW the old one: direct construction plus deferred insertion (
InstCombinerImpl::runinllvm/lib/Transforms/InstCombine/InstructionCombining.cpp).
Insertion points¶
LLVM
llvm/include/llvm/IR/BasicBlock.h begin() (sets the head bit), getFirstNonPHIIt, getFirstInsertionPt; llvm/lib/IR/Instruction.cpp Instruction::insertBefore (the head-bit logic and the phi assertion); llvm/include/llvm/IR/IRBuilder.h SetInsertPoint overloads [LLVM-IRB, LLVM-DbgRecords].
Find where LLVM does it. In Instruction.cpp, find the assertion text "Inserting PHI after debug-records!". Question: which bit of the insertion iterator must be clear for that assertion to be reachable? (Quiz llvm-where-headbit.)
Folders¶
LLVM
llvm/include/llvm/IR/ConstantFolder.h ConstantFolder::FoldBinOp (both operands must be Constant), llvm/include/llvm/IR/NoFolder.h, llvm/include/llvm/Analysis/InstSimplifyFolder.h (calls simplifyBinOp), llvm/include/llvm/Analysis/TargetFolder.h [LLVM-Folders].
- Clang
clang/lib/CodeGen/CGBuilder.h:typedef llvm::IRBuilder<llvm::TargetFolder, CGBuilderInserterTy> CGBuilderBaseTy;[CLANG-CGBuilder]. - Swift
lib/IRGen/IRBuilder.h(swift-6.1-RELEASE):using IRBuilderBase = llvm::IRBuilder<>;andclass IRBuilder : public IRBuilderBase, the default folder and inserter plus Swift-specific helpers.
Inserters¶
Clang's builder: TargetFolder plus its own inserter
Reproduce (Clang source at llvmorg-23.1.2):
curl -s https://raw.githubusercontent.com/llvm/llvm-project/llvmorg-23.1.2/clang/lib/CodeGen/CGBuilder.h \
| grep -n "class CGBuilderInserter" -A 18
Output (complete):
32:class CGBuilderInserter final : public llvm::IRBuilderDefaultInserter {
33- friend CGBuilderTy;
34-
35-public:
36- CGBuilderInserter() = default;
37- explicit CGBuilderInserter(CodeGenFunction *CGF) : CGF(CGF) {}
38-
39- /// This forwards to CodeGenFunction::InsertHelper.
40- void InsertHelper(llvm::Instruction *I, const llvm::Twine &Name,
41- llvm::BasicBlock::iterator InsertPt) const override;
42-
43-private:
44- CodeGenFunction *CGF = nullptr;
45-};
46-
47-typedef CGBuilderInserter CGBuilderInserterTy;
48-
49-typedef llvm::IRBuilder<llvm::TargetFolder, CGBuilderInserterTy>
50- CGBuilderBaseTy;
What to notice: Definition 10.3.5 in production. The inserter overrides InsertHelper(I, Name, InsertPt), and the builder type pairs it with TargetFolder, so every instruction Clang emits goes through CodeGenFunction::InsertHelper.
Builder state¶
LLVM
llvm/include/llvm/IR/IRBuilder.h: IRBuilderBase members StoredDL, FMF, IsFPConstrained, DefaultConstrainedRounding, and the nested InsertPointGuard, FastMathFlagGuard [LLVM-IRB].
- Clang sets the builder's constrained-FP state from the FP options in effect for each expression (
#pragma STDC FENV_ACCESS,-ffp-model) withCodeGenFunction::CGFPOptionsRAIIinclang/lib/CodeGen/CodeGenFunction.cpp, an RAII guard that restoressetDefaultConstrainedExcept/Roundingin its destructor, like Proposition 10.3.10.
8. Comparison¶
| Technique | Power / precision | Speed | Output / error quality | Implementation effort | Typical use |
|---|---|---|---|---|---|
| Direct construction | full control, no folding | \(O(k)\) per instruction | no help: you set flags, names, locations yourself | verbose | passes rewriting single instructions (Lab 10.2) |
| Insertion points | exact placement incl. debug-record side (Definition 10.3.1) | \(O(1)\) | wrong point = verifier error, or silently wrong debug info | remember to move it | every emitter |
| Folders | none / constants / InstSimplify identities (Proposition 10.3.8) | \(O(1)\) / \(O(s)\) / bounded recursion | less IR to clean up; NoFolder gives exact IR for tests |
choose a template argument | ConstantFolder default; InstSimplifyFolder in passes; NoFolder in tests |
| Inserters | a hook per created instruction (Theorem 10.3.9) | \(O(1)\) + callback | — | subclass or lambda | Clang bookkeeping, worklists, instrumentation |
| Builder state | flags, locations, strict FP applied uniformly | \(O(1)\) | forgotten state is silent (missing !dbg, wrong fast-math) |
setters + guards | front ends, FP-sensitive passes |
Choose direct construction for a one-off replacement whose position and flags you compute anyway. Choose IRBuilder<> for emitting sequences. Pick InstSimplifyFolder when you build in a pass and want the simplest IR immediately, and NoFolder when a test must see every instruction. Choose a custom inserter when every created instruction needs the same side effect. Always move the insertion point with iterators, not Instruction *, in LLVM 23.
9. Assessment¶
| Technique | Quiz ids (solutions/quizzes/ch10.yaml) |
Drill | Flashcard tag | Exercises |
|---|---|---|---|---|
| Direct construction | direct-construction-fold, direct-construction-position |
./course drill irbuilder-fold --difficulty hard (the NoFolder column is direct construction) |
direct-construction |
Lab 10.2 R6 |
| Insertion points | headbit-order, llvm-where-headbit, direct-construction-position |
./course drill irbuilder-fold |
insertion-points |
Lab 10.1 |
| Folders | folder-trace, direct-construction-fold |
./course drill irbuilder-fold |
folders |
Lab 10.1 R5 |
| Inserters | inserter-count, folder-trace |
./course drill irbuilder-fold (count the news) |
inserters |
— |
| Builder state | fmf-guard, builder-state-dbg |
— (see note) | builder-state |
— |
Builder state is a set of setters with one rule (Proposition 10.3.10), so a randomized drill would only repeat it. The quiz asks for a traced state instead.
Pitfall
B.CreateAdd(X, Y) does not always return an Instruction *. With any folder other than NoFolder it can return a constant or an existing value. Code that does cast<Instruction>(B.CreateAdd(…))->setHasNoSignedWrap() crashes the day an operand becomes constant. Pass the flags to CreateAdd(X, Y, "", /*HasNUW=*/…, /*HasNSW=*/…), or dyn_cast the result.
References¶
See the chapter references.