rucc-codegen 0.3.4

Instruction selection, scheduling, block layout, frames and prologue emission.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
# The rule sets

Instruction selection and the middle end's rewrites are written as rules rather than as code. This directory is where those rules live, one file per target, in the language `build-tools/rucc-rules` reads and `spec/10-backend.md` section 10.2 describes. They sit inside `rucc-codegen` because that is the crate whose build compiles them into the tables the selector matches with, and because a published crate has to build from its own source archive.

Every rule file has a model file beside it with the same name and a `.model` extension. The rules say what to match and what to put in its place, and the model says what the terms in them mean in bitvectors. The two are separate files because a rule is about a rewrite and a model is about a target, and because the model is the thing a reviewer reads when they want to know what the compiler believes an instruction does.

Every term in a rule is some number of bits wide. A head that ends in `.iN` is that many bits wide, anything else is as wide as the term it sits inside, and a name is as wide as the place in the pattern that bound it, so `(add.i32 (value.i64 x) (value.i64 y))` is a thirty two bit add of two sixty four bit registers. A rule that converts between widths writes the conversion out, `(sign_extend 32 64 x)` or `(extract 31 0 x)`, rather than leaving it to be inferred. Where the machine term is wider than the IR term it replaces, the two are asked to agree on the bits the IR term has and the `spec` clause is where the rest of the register gets its claim, which is the only place a target's extension rule is ever written down.

Nothing enters the rule set unverified. `cargo run -p rucc-verify -- crates/rucc-codegen/rules` reads every file here, asks a solver about every rule in it, and refuses the file if anything in it comes back as less than a proof. That is a required CI job. A rule the solver cannot settle at its own width may carry a `(bounded "...")` clause giving a reason to accept a proof at narrower widths instead, and the number of rules that needed one is printed on every run, because that number going up is the signal worth watching.

`x86-64.rules` is the integer core of that target: constants, arithmetic with a register and with an immediate, the address forms `lea` covers, negation and complement, division and remainder, shifts by a constant and by a register, the forty comparisons, and the conversions between widths. Nothing in it has an effect. Loads, stores, branches and calls are rules whose claim includes what they do to memory or to control, which is a question the rule language has to answer before those rules can be written, and they follow with the selector.

Two things are worth knowing before reading it. Every leaf says how wide it is, so no line needs the line above it to be understood, and a machine head is one instruction at one operand size, because `addl` and `addq` are two instructions with two encodings and a model whose entries do not stand one to one with instructions is a model nobody can check against a manual. The order the rules are written in is for reading rather than for matching: specificity is the shape of the pattern, so no rule here is reached only because another one is below it.