Lab 0.L1 · Your first LLVM IR file¶
Chapter: 0 · Compiler Architectures & Your Toolchain · Lessons: 0.4 (LLVM IR as a shared IR), 0.6 (calling libc) · Time: 1–2 hours · Tests: ./course test 0 (the first-ir-* lit tests)
Goal¶
Write one LLVM IR module by hand — no C, no clang — that defines a constant function, a loop, and a main that prints through libc's printf, and run it with lli. You will meet the parts of LLVM IR every later chapter uses: functions, basic blocks, terminators, phi nodes, integer types and globals. The LLVM Language Reference is the specification; Chapter 9 teaches IR properly.
Requirements¶
Your file is labs/ch00-first-ir/first.ll (replace the placeholder).
- R1.
define i32 @answer()returns the constant42. - R2.
define i64 @sum_to(i64 %n)returns \(0 + 1 + \dots + n\) for \(n \ge 0\) and0for \(n < 0\), computed by a loop (LLVM'sLoopInfomust find a natural loop in@sum_to);sum_to(100000)= 5000050000 does not fit in 32 bits. - R3.
define i32 @main()prints exactly these two lines to standard output withprintf, and returns0:
opt -passes=verify accepts it, and lli first.ll runs it. Use opaque pointers (ptr), no target triple is needed.
The contract¶
Black-box: the three function signatures above, the output of @main, and "contains a loop". The tests never look inside your functions otherwise.
lli labs/ch00-first-ir/first.ll # R3
opt -passes=verify -disable-output labs/ch00-first-ir/first.ll # R4
opt -passes='print<loops>' -disable-output labs/ch00-first-ir/first.ll # R2: "Loop at depth 1"
What the tests check¶
Test (tests/ch00/lit/) |
Checks |
|---|---|
first-ir-verify.test |
R4: lli runs the file; opt -passes=verify accepts it |
first-ir-main.test |
R3: the output is exactly the two lines (FileCheck --match-full-lines, nothing else printed) |
first-ir-functions.test |
R1, R2: llvm-links your file with tests/ch00/lit/Inputs/first-ir-driver.ll and runs lli --entry-function=ch00_driver, which calls @answer() and @sum_to on 0, 1, 10, 100000 and −5 |
first-ir-loop.test |
R2: print<loops> reports Loop at depth 1 in @sum_to |
Until you replace the placeholder, every test fails with TODO(ch00): L1: write labs/ch00-first-ir/first.ll. ./course test 0 --solution (or -DPEBBLE_USE_SOLUTION=first-ir) runs the same tests on the reference solution.
Milestones¶
@answerand a@mainthat justret i32 0—lli first.ll; echo $?prints 0.@maincallsprintfwith a global format string —first-ir-main.testpasses for the first line.@sum_towith a loop —ctest --test-dir build/<preset> -R ch00.litpasses.
Hints¶
Hint 1 — where to start
Declare printf as declare i32 @printf(ptr, ...) and call it with the variadic syntax call i32 (ptr, ...) @printf(ptr @fmt, i32 %x). A string constant is a global array: @fmt = private constant [N x i8] c"...\0A\00" — N must count every byte including \0A and the final \00. If you get it wrong, the parser tells you ("got type '[14 x i8]' but expected '[13 x i8]'").
Hint 2 — the key idea
A loop in SSA form needs a header block with one phi per loop-carried value (%i and %s), each with one incoming value from the entry block and one from the block that jumps back (the latch). Compare %i with %n using icmp sgt or icmp slt (signed) so that negative %n exits immediately. Lesson 0.1's MLIR box and Lesson 0.4's sumloop.ll show the shape.
Hint 3 — a design sketch
Blocks entry → loop (two phis, compare, conditional branch) → body (two adds, branch back to loop) and exit (ret the sum phi). Use i64 throughout @sum_to; print it with %lld. The most common bug: a phi whose incoming block label is not an actual predecessor ("PHI node entries do not match predecessors" from the verifier).
Stretch goals ★¶
- Add
-O2:opt -passes='default<O2>' -S first.ll— what happens to your loop? (Compare with Lesson 0.3's closed-form box.) - Compile your file for three targets with
llc -mtriple=...(Lesson 0.4) and link the x86-64 one withclang first.s -o first.