Skip to content

Style guide for course authors

Twenty-five chapters written by different authors must read as one course. This guide covers how to write. DEPTH_CONTRACT.md covers what must be there. Start every chapter from CHAPTER_TEMPLATE/. Companion references: NOTATION.md (house notation), REFERENCES.md (bibliography format), LABS.md (lab specs), REVIEW_CHECKLIST.md (what the reviewer will check).

The primary reading surface is the course website (./course serve, §13): write for it. Everything must also stay readable as raw Markdown on GitHub.

1. Voice

  • Talk to the learner as "you". Use the active voice and the present tense. "The worklist holds every block whose IN set may change", not "It can be observed that…".
  • Assume a strong programmer who is new to compilers. Define every term the first time you use it, and link back to the chapter that introduced it when you reuse it.
  • Be precise before being friendly. Make exact claims ("Θ(n) passes on the ladder graph") rather than impressions ("can be slow").
  • Don't hype. Avoid "simply", "obviously", "just", "magic", "powerful". If a step is easy, the reader will notice.
  • Spelling:
  • Use American spelling and one word for "dataflow".
  • Write "reverse postorder (RPO)" at first use.
  • Write "phi" in prose, phi in LLVM IR, and φ in math.
  • Write "LLVM 23", not "latest LLVM".
  • Show, then name. Open a technique with the problem and a tiny example. Formal definitions come after.

2. Files and naming

chapters/NN-slug/
├── README.md                 technique map (template: CHAPTER_TEMPLATE/README.md)
├── lessons/
│   ├── 01-first-family.md    one file per technique family; NN = order within the chapter (01, 02, …)
│   └── 02-second-family.md   kebab-case topic, e.g. 02-dominance-frontiers.md
├── exercises.md              implementation tasks, written as specs (LABS.md) with collapsible hints
├── references.yaml           the annotated bibliography (REFERENCES.md) — you edit this
├── references.md             GENERATED by `./course refs build NN` — never edit by hand
├── flashcards.tsv            front<TAB>back<TAB>tags
├── quiz.yaml                 GENERATED from solutions/quizzes/chNN.yaml — never edit by hand
└── figures/                  only if Mermaid cannot express it (SVG, < 200 KB, source kept alongside)
  • Lesson file names must match NN-kebab-topic.md. ./course validate enforces this.
  • Link with relative paths: [Lesson 2](lessons/02-dominance-frontiers.md) and [Ch 14](../14-dataflow/README.md).
  • Code paths are repo-relative in backticks: pebble/lib/Analysis/Dominance.cpp.

3. Markdown conventions

  • Headings:
  • Use one # title per file. In lessons it's # Lesson NN.k — <Family>, e.g. # Lesson 15.1 — Dominator algorithms.
  • The nine sections are ## with numbers (DEPTH_CONTRACT §2), and techniques are ###.
  • Line breaks: don't hard-wrap at a fixed column. Put one paragraph on one line, or use a line break after each sentence (easier diffs). Both are fine, but pick one per file.
  • Boxes (admonitions) carry every formal statement and every callout. The syntax is !!! <type> "Title" followed by a blank line and the body indented by four spaces (see §5 for the theory boxes). Callout types:
  • !!! note: a useful aside.
  • !!! pitfall: a common mistake. Every lesson has at least one.
  • !!! info "LLVM": how LLVM does it, with a source pointer.
  • !!! invariant: the claim a proof relies on (when it isn't already a numbered lemma).
  • !!! tip "Try it": a drill or command to run.
  • ??? <type> "Title" makes any box collapsible (use it for long real-world output).

The older blockquote callouts (> **Note** …) still render and are accepted in chapters that have not been upgraded yet; new text uses boxes. - Tables hold anything with ≥ 3 parallel facts. Left-align text; right-align numbers when a column is purely numeric. - Collapsible hints and spoilers (exercises only):

<details markdown="1"><summary>Hint 1 — where to start</summary>

Text of the hint (blank line after <summary> so Markdown renders inside).

</details>
- Emphasis: use bold for terms at their definition and italics for stress. Don't write whole sentences in bold. - No emoji in course text. The CLI's ✓/✗ are the only symbols.

4. Worked examples: trace tables

Every worked example shows every iteration (DEPTH_CONTRACT item 3). The course standard formats are below. The drill worked solutions (./course drill <name> --solution) print these same formats, so a lesson's tables and the drills look the same.

Fixed-point over nodes or symbols: rows are the items, columns are passes. Include the initial state and the final "no change" pass.

node init pass 1 pass 2
A {A} {A} {A}
B ⊤ {A,B} {A,B}
C ⊤ {A,B,C} {A,B,C}

Say what changed in each pass as a bullet list under the table: "pass 1: Dom(B) shrank from ⊤ to {A,B}…".

Worklist or stack algorithms: one row per step, and every data structure gets a column.

step action worklist (front→back) changed IN/OUT or stack
1 pop B C D OUT(B) = …

Rules: - Sets are written {a, b}, sorted: nodes in RPO or alphabetical, terminals alphabetical, $ and ε last. Write {} for the empty set in tables; ∅ is fine in math. Compact {A,B,C} without spaces is fine inside wide tables. - Highlight changes with bold. - When a drill exists, generate the example with the drill (fixed seed) or check it through the oracle functions in tools/course/lib/. Never hand-compute a table that the course can compute.

5. Mathematics

Theory is written in LaTeX inside Markdown, rigorously, in the house notation of NOTATION.md. The website renders it with KaTeX; GitHub renders it with MathJax.

  • Inline math: $\mathrm{idom}(n)$. Display math: $$ … $$ on lines of its own, with a blank line before and after. Multi-line derivations use \begin{aligned} … \end{aligned} inside $$:
$$
\begin{aligned}
\mathrm{Dom}(r) &= \{r\} \\
\mathrm{Dom}(n) &= \{n\} \cup \bigcap_{p \in \mathrm{preds}(n)} \mathrm{Dom}(p) \qquad (n \neq r)
\end{aligned}
$$
  • Use math for every formula, set, relation and bound — $O(e \log n)$, not "O(e log n)". Keep prose words out of math, or wrap them in \text{…}.
  • Only standard LaTeX, no \newcommand or custom macros (they break one of the two renderers). Multi-letter names are upright: \mathrm{FIRST}(\alpha).
  • Literal dollar signs outside math must be code (`$`) or escaped (\$); a bare $ starts a formula on the website. In math, the end marker is \$.
  • In tables, a | inside math breaks the row: use \mid, \lvert … \rvert, \Vert.
  • Math inside a box is indented with the box body like any other text.

Numbered statements

Formal content goes in boxes numbered N.k.m (chapter, lesson, one shared counter per lesson): definitions, theorems/propositions/lemmas/corollaries, algorithms. Every result is followed by a proof box. ./course validate checks the numbering and that each result has a proof.

!!! definition "Definition 15.1.1 (Dominance)"

    Let $G = (N, E, r)$ be a flowgraph. A node $d$ **dominates** $n$, written
    $d \mathrel{\mathrm{dom}} n$, if every path $r \leadsto n$ contains $d$.
    $\mathrm{Dom}(n) \triangleq \{\, d \in N \mid d \mathrel{\mathrm{dom}} n \,\}$.

!!! theorem "Theorem 15.1.2 (Dominator tree)"

    For every reachable $n \neq r$, the strict dominators of $n$ are totally ordered by
    $\mathrel{\mathrm{dom}}$; hence $\mathrm{idom}(n)$ is unique and the edges
    $\mathrm{idom}(n) \to n$ form a tree rooted at $r$.

!!! proof

    Let $a, b$ strictly dominate $n$ and take any path $P : r \leadsto n$. …
    Hence $a \mathrel{\mathrm{dom}} b$ or $b \mathrel{\mathrm{dom}} a$.

!!! proof "Proof sketch (full proof: [LT79, §2])"

    …  (a sketch must name where the complete proof is)

!!! algorithm "Algorithm 15.1.3 (Cooper–Harvey–Kennedy)"

    - **Input:** a flowgraph $G = (N, E, r)$, every node reachable.
    - **Output:** $\mathrm{idom}(n)$ for every $n \in N$ (with $\mathrm{idom}(r) = r$).
    - **Precondition:** nodes numbered in postorder of one DFS from $r$.
    - **Postcondition:** `doms[n]` $= \mathrm{idom}(n)$ for all $n$.
    - **Invariant:** after every pass, for every processed $n$, the path `doms`$^{*}(n)$ is a
    superset of $\mathrm{Dom}(n)$ (Lemma 15.1.4).

    ```text
    function ComputeIdoms(G = (N, E, r)):
        ...complete pseudo-code, §6...
    ```

!!! example "Dominance on the running example"

    …a small instance of the definition or theorem…

Proofs are checked line by line by the reviewer: each step must follow from a definition, an earlier numbered result, or a cited fact. Name the proof technique ("by induction on the length of the path", "by contradiction", "exchange argument"). A lesson that genuinely has no theory (a survey of tools) marks itself <!-- theory: none — <reason> -->.

5a. Real-world examples

Theory comes with evidence. Every technique gets at least one !!! real-world box (DEPTH_CONTRACT §2a R6), under that technique's ### heading, showing the concept in an actual system: real clang -S -emit-llvm output, opt -passes='print<domtree>' or print<loops>, -debug-pass-manager, Bison/ANTLR conflict reports, rustc -Z unpretty=mir, swiftc -emit-sil, GCC -fdump-tree-* dumps, V8/Cranelift flags. Format:

!!! real-world "LLVM's dominator tree for a loop nest"

    **Reproduce** (clang 23.1.2, opt 23.1.2; any OS):

    ```sh
    cat > nest.c <<'EOF'
    int f(int n) { int s = 0; for (int i = 0; i < n; i++) for (int j = 0; j < i; j++) s += j; return s; }
    EOF
    clang -O0 -Xclang -disable-O0-optnone -fno-discard-value-names -S -emit-llvm nest.c -o nest.ll
    opt -passes='sroa,print<domtree>' -disable-output nest.ll
    ```

    **Output** (complete):

    ```text
    DominatorTree for function: f
    =============================--------------------------------
    Inorder Dominator Tree:
      [1] %entry {0,9} [0]
        [2] %for.cond {1,9} [1]
          [3] %for.body {2,8} [2]
            [4] %for.cond1 {3,8} [3]
              [5] %for.body3 {4,6} [4]
                [6] %for.inc {5,6} [5]
              [5] %for.end {6,8} [4]
                [6] %for.inc4 {7,8} [5]
          [3] %for.end6 {8,9} [2]
    Roots: %entry
    ```

    **What to notice:** each `[d]` is the depth in the tree and `{in,out}` the DFS interval used for
    O(1) dominance queries; the inner header `%for.cond1` is dominated by the outer body, and
    neither latch (`%for.inc`, `%for.inc4`) dominates anything: Theorem 15.1.2 on a real CFG.
  • Exact commands in an sh block, runnable as pasted, deterministic (-fno-discard-value-names, fixed flags, no timestamps). Create input files inline with a heredoc.
  • Tool versions in the Reproduce line; LLVM is always the pinned 23.1.2 (STYLE §8). Other tools: the version you actually ran (bison 3.8.2, antlr4 4.13.2, rustc 1.9x.y, swiftc 6.x).
  • Captured output in a text block, pasted from a real run. Mark any abridgment ("abridged: …", … lines). Never hand-write output.
  • What to notice connects the output back to the numbered definitions/theorems.
  • Use ??? real-world (collapsed) when the output is longer than ~30 lines.
  • The reviewer re-runs every box (REVIEW_CHECKLIST.md step 5).

6. Pseudo-code

Put pseudo-code in a ```text fenced block with these conventions:

function ComputeIdoms(G = (N, E, entry)):
    order ← ReversePostorder(G)            # comments start with #
    doms[entry] ← entry
    changed ← true
    while changed:
        changed ← false
        for b in order, b ≠ entry:
            new ← first processed predecessor of b
            for p in preds(b), p ≠ new:
                if doms[p] is defined:
                    new ← Intersect(p, new)
            if doms[b] ≠ new:
                doms[b] ← new
                changed ← true
    return doms
  • Use ← for assignment and =/≠ for comparison. Keywords are lowercase (function, for, while, if, return), and indentation means nesting.
  • Name every helper and define it in the same lesson, or link where it's defined.
  • Line-number long algorithms (1: … n:) when the correctness proof refers to lines.

7. Citations

  • The chapter's bibliography lives in references.yaml (format, kinds, minimums and annotations: REFERENCES.md); ./course refs build NN generates references.md.
  • Cite inline with the key in brackets: [CHK01], [Geo05, GTW06], [LT79, §3], [EaC3 §9.2]. ./course validate checks that every citation resolves; the website links each one to its entry.
  • Keys: author initials or first surname + two-digit year for papers (CHK01, Tar74), a mnemonic for books (EaC3) and PROJECT-Topic for source/docs entries (LLVM-GDTC).
  • Prefer the original paper, then a DOI, then the authors' PDF. Books always name the chapter/section/pages to read.
  • Never cite a blog post as the origin of a technique. Blog posts are fine as extra reading, marked as such (kind: blog).
  • Lessons don't keep their own reference lists; they may end with ## References → See the [chapter references](../references.md).

8. Pointing into real compilers

The pinned versions are: - LLVM 23.1.2 (tag llvmorg-23.1.2) - GCC 15 - rustc stable 1.9x at the time of writing. Say which. - Swift 6.x - V8 and Cranelift at a named release

Write each pointer as repository path + symbol, relative to the repository root:

LLVM llvm/include/llvm/Support/GenericDomTreeConstruction.h — SemiNCAInfo::runSemiNCA builds the tree; llvm/lib/IR/Dominators.cpp instantiates it for IR (LLVM 23.1.2).

  • Don't use line numbers, because they rot. When linking, link the tag rather than main: https://github.com/llvm/llvm-project/blob/llvmorg-23.1.2/<path>.
  • Other repositories:
  • GCC gcc/tree-ssa-*.cc (gcc-15 branch)
  • rustc compiler/rustc_*/src/...
  • Swift lib/SILOptimizer/...
  • V8 src/compiler/...
  • Cranelift cranelift/codegen/src/...
  • tree-sitter lib/src/...
  • Every chapter has ≥ 3 "Find where LLVM does X" tasks. Each gives the task, the starting file, and a question about what the reader finds. That question appears in the quiz with type text, single or set.

9. Diagrams (Mermaid)

Use Mermaid for every graph. GitHub and the course website both render it, and it stays diffable.

flowchart TD
  A([A]) --> B[B]
  B --> C[C]
  C --> B
  B --> D[D]
  classDef hl fill:#fde68a,stroke:#b45309;
  class B hl;
  • CFGs use flowchart TD. The entry node is a stadium ([A]), other blocks are [B], and edges are drawn in the successor order used by the text. That order matters for DFS and RPO.
  • Node names in diagrams must match the text and the trace tables exactly. Generate the diagram from the same data where possible: CFG.to_mermaid() or ./course drill <name> --seed N --mermaid.
  • Highlight with one classDef hl (as above). Name back edges or special edges in the text too, because colour alone isn't accessible.
  • Grammars, lattices and trees use flowchart TD or graph BT for lattices (⊥ at the bottom). Pipelines use flowchart LR.
  • Keep each diagram to ≤ 15 nodes. Split larger ones.

10. Code

Course C++ (skeletons, solutions, labs, lesson excerpts)

  • C++23. Use std::expected<T, pebble::Error> or llvm::Expected<T> for errors and never throw. Also use std::print/std::format, std::span, std::string_view and ranges where they make code clearer. No C++20 modules. RTTI matches LLVM's setting.
  • Style: follow the LLVM Coding Standards and the repository's .clang-format when present.
  • UpperCamelCase types and variables, lowerCamelCase functions.
  • Early returns.
  • auto only when the type is obvious from the right-hand side.
  • LLVM idioms in LLVM-facing code:
  • isa<>/cast<>/dyn_cast<>, not dynamic_cast
  • SmallVector, DenseMap, SetVector, StringRef/ArrayRef at API boundaries
  • raw_ostream for IR printing
  • the new pass manager with PassInfoMixin
  • PreservedAnalyses returned precisely
  • no legacy pass manager
  • Pure algorithm code (for example a grammar toolkit) uses the standard library. Say why when you pick one over the other.
  • Excerpts in lessons must compile as shown, or be labelled // excerpt with the file they come from. Keep them under 40 lines. Longer code belongs in the repository, with a link.
  • Skeleton TODOs: PEBBLE_TODO("chNN", "what to implement, in one line");. The message names the exercise number from exercises.md.

Other code

  • Shell commands go in sh blocks without a $ prompt, so they copy-paste cleanly. Show expected output in a separate text block.
  • LLVM IR uses llvm fences. It is opaque-pointer IR only, with ptr and never i32*. The IR must pass opt -passes=verify.
  • Pebble examples use pebble fences and follow docs/language/pebble-spec.md.

11. Spoilers and solutions

  • Lessons teach the algorithms in full, pseudo-code included. They never contain the course's solution code for an exercise.
  • exercises.md gives graded hints in <details> blocks: where to start → the key idea → a near-complete sketch. Never give the finished code.
  • Quiz answers live only in solutions/quizzes/chNN.yaml. The built quiz.yaml is hashed. Don't repeat a quiz answer verbatim in the lesson right next to the question's topic, and write the quiz so it tests understanding rather than recall of one sentence.
  • solutions/ is a spoiler zone. Lessons may say "if you are stuck, compare with solutions/..." only at the end of an exercise.

12. Before you submit a chapter

./course refs build NN            # references.yaml -> references.md
./course validate --strict        # structure, rigor boxes, citations, labs, drills self-test, quizzes, flashcards
./course quiz build NN && ./course quiz verify --all
./course test NN                  # skeleton: must compile, tests fail with TODO(chNN)
./course test NN --solution       # solutions: all tests pass
./course site build               # the website builds in strict mode
./course serve                    # then read every page of the chapter in the browser
uv run python -m unittest discover tools/course/tests

Then fill in the rubric table from DEPTH_CONTRACT §5 and hand the chapter to the reviewer (REVIEW_CHECKLIST.md).

13. The course website

The course is read as a local website: ./course serve (http://127.0.0.1:8000, reloads on every save) or ./course site build (static files in build/site/, strict). It is MkDocs + Material, built in place from the repository's Markdown by tools/site/hooks.py and tools/course/site.py:

  • Pages: README.md (home), chapters/**, docs/** (except the chapter template), labs/**.md. Navigation comes from tools/course/chapters.yaml; planned chapters get a "coming soon" page with their syllabus entry.
  • Generated per chapter: Flashcards (from flashcards.tsv), Quiz (questions only — answers are never rendered), References (from references.yaml, with "cited in" links). Generated globally: the Bibliography.
  • Links: write ordinary relative links to .md files (they work on GitHub and on the site). Links to anything that isn't a page (C++ sources, YAML, directories) are rewritten to GitHub automatically. Heading anchors are GitHub-compatible.
  • Rendering: $…$/$$…$$ math (KaTeX), mermaid fences, the box types of §3 and §5, syntax highlighting for cpp, llvm, sh, text, ebnf/grammar, and pebble/pir (Rust-like highlighting).
  • The strict build fails on broken links between pages. It does not catch math that failed to render or a swallowed $: look at the page (REVIEW_CHECKLIST step 9).