Skip to main content

rucc_codegen/
lib.rs

1//! Instruction selection, scheduling, block layout, frames and prologue emission.
2//!
3//! Design: `spec/10-backend.md`. Layer rank 12, see `spec/18-package-layout.md`.
4//!
5//! # Status
6//!
7//! The lowering tables are here. `rules/x86-64.rules` is compiled into a matching automaton when
8//! this crate is built, and [`select`] is the walk over it: hand it a term and it gives back the
9//! rule that fires and what the pattern bound. No lowering is written as `match` arms in this
10//! crate and none ever will be, which is the settled decision `spec/10-backend.md` section 10.2
11//! records.
12//!
13//! The selector is here too. [`lower`] walks a function and builds machine IR out of what the
14//! table gives back, and [`term`] is how an IR instruction is shown to the matcher. Between them
15//! they cover the arithmetic the rule file covers, which is every integer operation at every
16//! width the machine has one for.
17//!
18//! Loads and stores are covered too, and they are the first rules with an effect. What one of
19//! those claims is settled the same way everything else is: a term may compute a memory rather
20//! than a value, and the two halves of a rule have to agree about which they computed. A return
21//! is covered as well, and it is the first rule about the calling convention: what it claims is
22//! that the value comes through unchanged, and which register it comes through is a target fact
23//! [`rucc_target::x86_64`] states and a test there checks against both conventions.
24//!
25//! The branches are covered, and they are the rules with the least in them. Where a block goes is
26//! on the block in machine IR rather than on its terminator, so a rule for a branch never names a
27//! block and an unconditional jump is not a rule at all: the edge is the whole of it. What is
28//! left of a conditional branch is the condition, which is what its rule is about.
29//!
30//! [`split`] is what has to run between lowering and allocation now that there are branches. An
31//! edge that carries values into a block arrived at more than one way, out of a block that leaves
32//! more than one way, has nowhere to put the moves those values turn into, so it is split into two
33//! edges that do.
34//!
35//! [`abi`] is the other side of the same convention and the one part of it that is not a rule at
36//! all. Which register an argument arrives in depends on its position and on the classification
37//! of every argument before it, and a rule matches one term and can see none of that, so the
38//! arguments are built from what [`rucc_target::CallRegs`] says. A function's parameters are
39//! bound to the registers they arrived in before its first instruction is looked at, which is
40//! what makes a function that takes arguments one this can compile at all: the allocator refuses
41//! an entry block with parameters on it, because there is no edge into an entry block for the
42//! moves that give a block parameter its value to go on.
43//!
44//! The calls are built there too, and for the same reason: a rule pattern sees one term and a
45//! call's operands are whatever the signature made them. What the callee is free to destroy is
46//! written into the call as a definition of each of those registers, which is the whole of what
47//! the allocator needs to keep a value that outlives the call somewhere else. What passes on the
48//! stack is refused rather than passed wrongly, on this side as on the other.
49//!
50//! The addresses are the other thing [`lower`] builds by name rather than by rule, and there are
51//! two of them. The address of a local is a `lea` off the stack pointer with a displacement the
52//! frame fills in later, and the address of a name at file scope is a `lea` off the instruction
53//! pointer with the name on it. Neither is a rule because neither is a claim about bitvectors: one
54//! of them is waiting on a number nothing knows yet and the other is right because of what the
55//! linker does with a relocation. A cast between a pointer and an integer as wide as one is here
56//! for the opposite reason, which is that it is no instruction at all.
57//!
58//! [`frame`] is what a function's stack looks like while it runs: which registers the prologue has
59//! to put back, where every spilled value went, and how many bytes the stack pointer moves. It is
60//! worked out after allocation because the largest area in most frames is the spill slots and
61//! nothing knows how many of those there are until the allocator has finished running out of
62//! registers.
63//!
64//! [`slots`] is what tells it which of those areas are the same bytes. A local and a spilled value
65//! that are never both wanted can share a run of the frame, which makes the frame the most either
66//! of them needs at once rather than the sum of the two, and what says they are never both wanted
67//! is the liveness the allocator already worked out. `spec/optimizer/36-lowering-and-isel.md`
68//! section 36.7 asks for the one slot allocator rather than two that cannot see each other.
69//!
70//! [`finish`] writes that frame into the function: the prologue that takes it, the moves the
71//! allocator handed back as edits, and the epilogue at the end of every block the function
72//! returns from. After it every register is physical and every offset into the frame is a
73//! constant, which is the point at which a function is one an encoder could read.
74//!
75//! [`copies`] is the one thing that runs between those two and it makes the function shorter and
76//! cheaper rather than longer. The allocator decides one value at a time, so it writes moves that
77//! put a value where the machine has it already: a word written out and read straight back into
78//! the register it came out of, a slot read twice into the same register with nothing writing
79//! either in between, a copy of a register into one that already holds what it holds. Those go.
80//! The near miss of the same thing, a slot read into one register while another already holds that
81//! word, stays an instruction and becomes a copy between the two registers, which is cheaper than
82//! going to the frame for a word that never left. Only the allocator's own moves are touched,
83//! which is why [`finish`] hands back which instruction each of them became.
84//!
85//! [`layout`] runs last and is what makes a function something a machine could run rather than
86//! something a printer could print. It puts the blocks in the order they are laid out in and then
87//! writes the jumps that order needs, which is where a conditional branch finally becomes a test
88//! and a jump and where an edge to the next block becomes nothing at all. Where the branch is on
89//! a comparison and nothing else wanted the byte, there is no test: the comparison already set the
90//! flags and the jump names the condition it was asked about. That has to happen there rather than
91//! in a pass of its own, because the flags between the two are live and are not a register, so
92//! nothing may come between them and after the layout nothing can.
93//!
94//! [`pipeline`] is the order all of that runs in, which is the only thing about the back end a
95//! caller outside this crate has to know and now the only thing it has to say. It is one function
96//! from an IR function to a machine one, and a [`pipeline::Machine`] describing what is being
97//! compiled for. The driver's `--emit=mir-final` is a call to it per definition in the module.
98//!
99//! [`coverage`] is what says whether all of that adds up to a back end. Every IR opcode is lowered
100//! by a rule, or somewhere a rule cannot reach and the reason is written down, or nowhere and the
101//! issue that closes it is written down. Which of the three each one is is checked rather than
102//! believed, and the count of the third is one of the numbers `spec/15-testing.md` says we keep
103//! about ourselves. It is not zero yet.
104//!
105//! The other coverage question is the one only a corpus can answer, which is which of the rules
106//! that are written anything ever fires. [`coverage::Fired`] is what records that as the selector
107//! goes, and `-Zrule-coverage=FILE` is how a run of the compiler is asked for it.
108//!
109//! [`pressure`] is the third thing a compilation can be asked to record about itself, after the
110//! rules that fired and the opcodes nothing lowers. It is how much of the frame the allocator had
111//! to use, which `spec/safe-memory/13-performance.md` section 13.1 wants a number for because a
112//! capability in flight is four words and the risk is that materializing one pushes something else
113//! onto the stack. `-Zregister-pressure=FILE` is how a run of the compiler is asked for it.
114//!
115//! [`fold`] is the first peephole and the first thing here that exists to make the code better
116//! rather than to make it correct. The rules build an address into a `lea` and then a separate
117//! instruction reads through the register that `lea` wrote, because a rule matches one term and
118//! the two of them are at the root of two. So the pair is put back together afterwards, where an
119//! address is an [`rucc_mir::Amode`] and composing two of them is arithmetic rather than a case
120//! analysis. `spec/optimizer/37-machine-level-optimization.md` section 37.4 is the entry it comes
121//! from and says what is still left of it.
122//!
123//! [`combine`] is the entry in that section the section names first, and it is the other half of
124//! what the fold above does. The fold takes an address the rules built on its own and puts it back
125//! inside the instruction that reads through it; this takes a load the rules built on its own and
126//! puts it inside the arithmetic that reads what it loaded. Both exist because a rule matches one
127//! term and both of these are two terms, and both are the same question about whether the value
128//! could have changed in between, answered here by the load having exactly one reader and by
129//! nothing between the two writing memory or calling anything.
130//!
131//! [`bits`] is the other entry in that section, and it is the same question asked about a register
132//! rather than about an address: how much of one anything reads. C promotes every narrow operand
133//! to `int` before doing anything with it, so a program full of `char` arithmetic is a program
134//! full of moves between widths, and a move whose result nothing reads more of than its source
135//! already held is a move that can go. What the rewrite rules take is the pair that sits next to
136//! itself in one block; what this takes is the rest, which is the ones with a block boundary in
137//! the middle and the ones the selector wrote itself.
138//!
139//! [`compare`] is the last thing that runs and the third entry in that section. A comparison on
140//! this machine produces no value: it sets a few bits nobody named and the instruction behind it
141//! reads them, so one that sets the bits that are already there is one nothing could tell had run.
142//! Either the same comparison was made a few instructions ago, or the comparison is against zero
143//! and arithmetic worked the value out and set the same bits on its way past. It runs after the
144//! layout because the layout is the other pass about a pair of instructions with nothing allowed
145//! between them, and after it there is nothing left that could put something there.
146//!
147//! What is not here yet is the rest of the optimizing path: no scheduling, and a block order from
148//! the shape of the control flow rather than from how often each block runs.
149//!
150//! Every crate in the workspace is published, and publishing implies a promise. This one is
151//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
152//! Depend on the `rucc` binary's behaviour, not on this.
153
154#![doc(html_root_url = "https://docs.rs/rucc-codegen/0.10.46")]
155
156pub mod abi;
157pub mod bits;
158pub mod changes;
159pub mod combine;
160pub mod compare;
161pub mod copies;
162pub mod coverage;
163pub mod elsewhere;
164pub mod expand;
165pub mod finish;
166pub mod fold;
167pub mod frame;
168pub mod layout;
169pub mod lower;
170pub mod pipeline;
171pub mod pressure;
172pub mod quad;
173pub mod retry;
174pub mod schedule;
175pub mod select;
176pub mod slots;
177pub mod split;
178pub mod switch;
179pub mod varargs;
180pub mod weights;
181pub mod wide;
182pub mod widths;
183
184/// The IR as something a rule can match against, which is [`rucc_ir::term`].
185///
186/// Re-exported rather than reached for through `rucc_ir`, because this crate had it first and
187/// every caller here says `crate::term`. It moved down when `rucc-opt` became the second crate
188/// to match a rule set against the IR, and where it lives is not something a caller of it has
189/// any reason to know.
190pub use rucc_ir::term;
191
192/// The milestone in `spec/17-milestones.md` that fills this crate in.
193pub const MILESTONE: &str = "M3";
194
195#[cfg(test)]
196mod tests {
197    #[test]
198    fn milestone_is_recorded() {
199        assert!(super::MILESTONE.starts_with('M'));
200    }
201}