Skip to content

Chapter 0 exercises

Chapter 0 has no Pebble component to implement. You set up the toolchain, explore it, write your first LLVM IR, and build the comparison lab. Run the tests after every step:

./course test 0                  # builds, then runs every test labelled ch00
ctest --test-dir build/<preset> -L ch00 -R first-ir      # one group while iterating

Before you start, every ch00 test except exec-errors.test fails with TODO(ch00): …; each message names the step below that fixes it.

Stuck? Work through the hints in order. Reference solutions are in solutions/labs/ch00-first-ir/ and solutions/labs/ch00-exec/src/, but only look after you have passed the tests, or after an honest hour.


E0 · Set up and check the environment

Contract: ./course doctor reports every required tool.

Follow docs/setup/ for your platform (macOS: macos.md), then run ./course doctor. It checks the LLVM 23 tools this chapter uses (clang, opt, llc, lli, FileCheck), CMake, Ninja, GoogleTest and the Python environment.

Requirements: - R1. ./course doctor reports no errors. - R2. cmake --preset <macos|linux> configures and ./course test 0 builds (the tests may still fail with TODO(ch00)).

Hint 1 — where to start

Read the doctor's output line by line: it names the missing tool and the setup page section that installs it. On macOS the LLVM tools come from Homebrew's llvm formula and are not on PATH by default.

Done when: ./course doctor is green.


E1 · Explore the toolchain

Contract: none tested; the quiz (./course quiz 0) asks about what you observe.

Reproduce, on your machine, the real-world boxes of Lesson 0.1 (driver phases, -print-pipeline-passes, -print-after-all), Lesson 0.3 (-O0 vs -O2 IR) and Lesson 0.6 (llvm-readelf, llvm-objdump, a link error). Then answer for yourself:

Requirements: - R1. Which clang -cc1 flag in clang -### output selects "produce an object file"? Which one would produce LLVM IR instead? - R2. In clang -O0 -S -emit-llvm, why is every local an alloca, and which pass removes them? (Run opt -passes=mem2reg -S.) - R3. In llvm-readelf -r main.o, compute by hand the bytes the linker writes for sum, given the final addresses from llvm-objdump -d prog (Lesson 0.6 §3), and check them in the disassembly. - R4. Run opt -passes=dot-cfg -disable-output on your -O0 IR and render .sum.dot with Graphviz (dot -Tsvg); identify the loop's back edge. - R5. Paste sum.c into Compiler Explorer (godbolt.org) and compare clang and GCC at -O2 for x86-64 and AArch64.

Hint 1 — where to start

Work in a scratch directory; every box creates its inputs with a heredoc. Your paths and versions will differ in -### output; the structure will not.

Hint 2 — the key idea for R3

Definition 0.6.4: the value is S + A - P, where P is the address of the 4-byte field (one byte after the e8 opcode) and A = -4.

Done when: you can answer the quiz questions tagged multi-pass, aot, assembler and linker.


L1 · Your first LLVM IR file

Spec: labs/ch00-first-ir/SPEC.md · Your code: labs/ch00-first-ir/first.ll · Tests: ch00.lit (first-ir-*.test)

Write @answer, @sum_to (with a loop) and @main by hand and run them with lli. The spec gives the exact requirements, the contract, what each test checks, and hints.


L2–L5 · Comparison lab: tree walker vs stack VM (switch, threaded) vs LLVM JIT

Spec: labs/ch00-exec/SPEC.md · Your code: labs/ch00-exec/src/ (any files you like) · Tests: ch00.Engines/*, ch00.lit (exec-*.test), ch00.lab.bench-smoke

Implement tiny::prepare (labs/ch00-exec/include/tiny/Engines.h) for four engines — Algorithm 0.2.3, Algorithms 0.2.5 + switch dispatch, Algorithm 0.2.10, and an LLVM IR generator + ORC LLJIT (ORC docs) — then run ch00-bench and compare your numbers with Lesson 0.2 §7 and Lesson 0.3 §5.

★ Optional: a register-VM engine, superinstructions, or a lazy JIT with a counter threshold (stretch goals in the spec).