20. Perf contracts

A performance claim is a promise about execution: this function does not allocate, this loop does not trap, this language is faster than that one. Most such promises, in most codebases, are comments, and a comment’s defining property is that nothing fails when it stops being true. This chapter is about the promises in wolf that are not comments: who makes them, what enforces them, and the paper trail that lets you check any of them from your chair.

20.1 The promises the language keeps

You have been collecting perf contracts since Part 1 without the name. Arithmetic is checked in every profile: chapter 3 made the promise and chapter 21 audits its price, and a benchmark run against wolf is a benchmark run with the checks on. A bounds check is removed only when the compiler proves it cannot fire, never because it is inconvenient. A region’s teardown is one motion regardless of how much died. And the two machines agree: any program in this book means the same thing to the interpreter and to an optimized binary, which is what made chapter 19’s release tier safe to trust.

Each of these binds the implementation, not you. Where your intent differs from the default promise, you renegotiate in the signature. Here is arithmetic that wants to overflow, a checksum tag in the oldest style there is:

fn tag(s: str) -> wrapping[i32] {
    var h: wrapping[i32] = 7
    for b in s.bytes() {
        h = h * 31 + (b as int as wrapping[i32])
    }
    h
}
fn main() -> !int {
    let line = "the pack hunts at dusk"
    print("{tag(line)}")
    0
}
$ wolf build --release tag.lu
$ ./tag
1195395237
$ lupin tag.lu
1195395237

Multiply a hash by 31 per byte and it leaves i32’s range almost immediately, on purpose; written over plain i32 this loop is the trap chapter 3 printed. The double cast is the byte’s only bridge: a byte widens to int and every other width goes through that (§2.3), so b as int as wrapping[i32] is the spelling and there is no shorter one. wrapping[i32] moves the intent into the type, where the compiler can hold you to it and a reader of the signature can see it. That is the shape of every contract in this chapter: a promise is written where a machine can check it, or it is not a promise.

One scope statement, so the chapter’s title cannot oversell it: v1 attaches no performance contract to an individual function’s signature. There is no annotation that promises a function allocation-free or trap-free, and this book does not teach promises no machine checks. The compiler agrees: the names such annotations would take, #[noalloc] among them, are refused by name (E0817, “not implemented yet”) until a checker exists to hold them, because a promise the compiler accepts and never checks is worse than none. The contracts wolf makes are the language’s own, and the instrument that holds the largest of them (faster than C, on a named suite, with the checks on) is a set of CI gates in the compiler’s repository. The rest of this chapter reads those gates, because they are the pattern to steal for your own projects.

Exercise 20-1 (fingers · lupin). Change the line to "a" and predict the printed value by hand before running: the seed is 7, the multiplier 31, and "a" is one byte, 97. Then explain why this function’s signature would be a lie under plain i32.

20.2 The ledger and the clock

The suite’s verdict is not a press release; it is a line appended to a ledger, one per run, machine-written. Two consecutive lines from the ledger’s own history:

{"ts":"2026-08-22T03:53Z","commit":"857c796","geomean":1.099,"verdict":"HOLDS","consecutive":1,"note":"run 8, the clock's first counted hold"}
{"ts":"2026-08-22T04:11Z","commit":"857c796","geomean":1.107,"verdict":"HOLDS","consecutive":1,"note":"run 9; same-night re-render (18m after the counted hold); count unchanged"}

Read the anatomy first: a timestamp, the commit measured, the geometric mean against the C baseline, the verdict, and a counter. The counter is the interesting part. The project’s rule for declaring its performance milestone required the gate to hold on three consecutive nightly runs, and the ledger enforces what “nightly” means: a second hold counts only when at least twelve hours separate it from the last counted one. Run 9 landed eighteen minutes after run 8, on the other side of midnight. The local calendar rolled over; a night did not pass; two samples of one thermal state are one sample. The ledger’s header states the policy in a sentence this book has no improvement on: the instrument outvotes the narrative.

So the count stayed at one, and the clock did its work on the real nights that followed: a hold at 1.066 the next night, 1.080 the night after, and at three consecutive holds the tool prints a banner. The banner is not the declaration. The declaration is a human’s act, made with the ledger in hand, and chapter 21 quotes the third night’s line where it prints the suite’s verdict in full. What you should steal from this is not the milestone but the bookkeeping: every measurement your project cites should have a line like these somewhere, with a date, a commit, and a rule that keeps enthusiasm from counting the same warm machine twice.

Exercise 20-2 (comprehension · prose). The ledger’s history for 2026-08-22 contains three HOLDS lines: 03:53 (counted, consecutive 1), 04:11 (eighteen minutes later), and 06:30 (139 minutes after the counted hold). The next holds land 2026-08-23 at 03:36 and 2026-08-24 at 03:39. State the consecutive count after each of the five lines, and name the rule that decides each step.

20.3 The floors and the ratchets

Wall-clock time is weather. It moves with the machine’s load, its temperature, and the phase of the allocator’s moon, which is why the gates that can fail a pull request in the compiler’s repository are deterministic counts: numbers that come out identical on a quiet machine every time, or fail. Wall time is reported nightly and read against its own spread; instruction counts, emitted-IR ratios, and vectorization reports gate merges. The gates come in two shapes, and each shape has a name.

A floor sits under something that must not fall. The suite records, per kernel, the minimum number of loops the backend must report vectorized. When the compiler first proved away the overflow check that had kept a reduction scalar, that kernel’s floor moved from zero to one, and the proof stopped being a happy accident: a later commit that breaks the range analysis now fails CI on a count, loudly, before any wall clock could have noticed through the noise. The stencil kernel’s carried window from chapter 19 is pinned the same way.

A ratchet sits over something that must not grow back. The handed-to-LLVM ratio of chapter 19 is gated as a ceiling set at the measured value: when the middle end earns a better number, the ceiling tightens to hold it; it never loosens silently. A ratchet is a ceiling, not a quota. Nothing rewards driving the number down, and nothing forgives it drifting up.

Between them, the floors and the ratchets encode a policy: progress is only progress once it cannot be quietly lost. A win that exists as a warm feeling about last Tuesday’s run is not yet a win; a win with a floor under it is part of the compiler’s definition.

Exercise 20-3 (comprehension · prose). The emitted-IR ratio’s original design target is half, and the recorded gate is a ceiling at the measured value instead of at the target. State what a hard gate at the unmet target would do to every pull request, what a team learns to do with a gate that is always red, and what the ratchet preserves that a red wall of shame would not.

20.4 Exceptions, written and capped

The suite’s gate allows a kernel to lose to C by more than ten percent only if a written exception documents it, and it caps the exceptions at three. The cap is enforced by the gate, which reads the exceptions file and counts. An entry is a price sheet, not an apology, and it has a required form: the kernel, the root cause, the decision that renounced the win, the measured cost, and the condition under which the entry is revisited.

The class discipline is the strict part. An exception is only available when the missing speed requires undefined behavior the language refuses or contradicts a semantic chosen on purpose. A missing container method, an unencoded fact, a metadata channel the backend dropped: none of these can be excepted, because they are bugs with owners, and the loss ledger chapter 19 read is where they go to be fixed. The exception file is reserved for prices, not embarrassments. Two of the three slots are spent as this book goes to press, both on prices, and chapter 21 reads both entries where it prints the verdict.

The form travels well beyond compilers. A service’s latency budget, a mobile app’s binary-size line, a build-time cap: any of them can be run as this chapter’s machinery, a dated ledger, floors under what must not fall, ratchets over what must not grow, and a short file of written exceptions with a cap that forces the argument into the open when someone wants a fourth. The gates are the contract. Everything else is a comment.

Exercise 20-4 (design). A teammate proposes an exception entry: your image-decoding kernel would match C if it could read eight bytes past the end of the input buffer, since the read provably stays in the same page. Draft the entry’s five fields, then rule on it: does this qualify for the exceptions file, and if it does, what revisit condition keeps it honest? Then rule on a second candidate: a kernel that loses because the standard library lacks a bulk-copy method.