Skip to main content

rucc_target/
branch.rs

1//! The instructions a laid out branch is made of.
2//!
3//! Design: `spec/10-backend.md` sections 10.6 and 10.8.
4//!
5//! A lowering rule for a conditional branch says one thing, which is what the branch is on. Where
6//! its two arms go is on the block rather than in the instruction, and which of them the block
7//! falls through to is not knowable until every block of the function has been put in an order.
8//! So the instructions that actually branch are chosen by the block layout, after allocation, and
9//! they are named here for the same reason [`crate::FrameInsts`] names a push: the crate that
10//! writes them is a pipeline crate and `spec/10-backend.md` section 10.8 says a pipeline crate
11//! holds no target-specific code.
12//!
13//! # What each one has to be
14//!
15//! The shapes are fixed, because the code that writes them writes one shape each. The test reads
16//! one register and sets whatever the machine's condition state is. The three jumps read nothing
17//! and write nothing, and where each goes is the first successor of the block it ends, which is
18//! how every other arm is already carried.
19//!
20//! Two conditional jumps rather than one, because which one a block ends with depends on which
21//! arm the layout put next. A block that falls into the arm taken when the condition does not
22//! hold ends with the jump that is taken when it does, and a block that falls into the other arm
23//! ends with the other jump. Neither is more natural than the other and a target that could only
24//! name one would force the layout to lay every second branch out backwards.
25//!
26//! After the layout has run, a block that ends in a conditional jump has exactly two successors:
27//! the first is where the jump goes, and the second is the block laid out next, which is where it
28//! goes when the jump is not taken. There is never a second jump in the same block, because the
29//! layout makes a block for one rather than writing it.
30//!
31//! # The jump the layout did not write
32//!
33//! An `asm` template may write one itself, which is how a loop a program spelled out by hand
34//! reaches the machine IR: the lowering turns each label into a block and each jump into a block
35//! with two arms whose last instruction is already the jump. [`BranchInsts::conditional`] is the
36//! list the layout reads to tell one of those, and what it does then is nothing at all, beyond the
37//! block on the second arm that every two-armed block needs when neither arm is laid out next.
38//! The arms are in the order the jump means, taken first, so the shape is the one above already.
39//!
40//! # The condition state is not an operand
41//!
42//! Nothing here mentions the flags, on a machine that has them or on one that does not. What
43//! makes that sound is that the test and the jump that reads it are written next to each other,
44//! by one pass, after the allocator has finished, so there is nothing left in the compiler that
45//! could put an instruction between them.
46//!
47//! # The test a comparison makes unnecessary
48//!
49//! Almost every branch in a C program is on a comparison, and a comparison already sets the
50//! condition state. The byte a rule selects for it, the test of that byte against itself and the
51//! jump on the answer are three instructions where the machine wanted two, and the two it wanted
52//! are the comparison with nothing kept and a jump on the condition the comparison was asked
53//! about.
54//!
55//! [`Fusion`] is that pair written down, one entry per comparison a rule can select. The layout
56//! looks for one when the instruction in front of the branch is a comparison whose byte the
57//! branch is the whole of what reads, and writes the two instructions in the entry instead of the
58//! three it found. Which of the two jumps it writes is the same question as before and gets the
59//! same answer, so an entry names both.
60//!
61//! # The select a comparison makes unnecessary
62//!
63//! A rule selects a choice between two values as a test of a condition byte and a conditional move
64//! on the answer, because the byte is the only thing a rule can name. When the byte came from a
65//! comparison that is the same three instructions a branch was, a comparison keeping a byte, a
66//! test of it and an instruction that reads what the test left, and the same two do the work: the
67//! comparison keeping nothing and a move on the condition the comparison was asked about.
68//!
69//! [`Move`] is that pair written down, one entry per select and condition. The condition is named
70//! by the jump a [`Fusion`] takes when its comparison held, so a comparison has one name for what
71//! it asked whether a branch or a select reads it.
72//!
73//! It stays a table rather than becoming an operation on the names. `cmp_set_ae_ri_64` and
74//! `cmp_ri_64` and `jcc_ae` are strings a target chose and not a spelling anything here may
75//! derive, and a target whose comparisons are shaped differently, or which has no condition state
76//! at all, writes a shorter table or an empty one.
77
78/// Every instruction a laid out branch is made of.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct BranchInsts {
81    /// What a rule file and the machine IR put in front of this target's opcodes, such as `x64.`,
82    /// which says which target a term belongs to and is not part of the opcode.
83    pub prefix: &'static str,
84    /// What a lowering rule selects for a conditional branch, which is what the layout replaces.
85    ///
86    /// It reads the condition and does nothing, which is as much of a branch as a rule can say.
87    /// Naming it here is what lets the layout find one and be sure it has found one, rather than
88    /// assuming that whatever a two-armed block ends with must be the branch.
89    pub cond: &'static str,
90    /// Reads the register the branch is on and sets the condition state from whether it is zero.
91    pub test: &'static str,
92    /// Goes to the block's first successor when the condition held.
93    pub if_true: &'static str,
94    /// Goes to the block's first successor when the condition did not hold.
95    pub if_false: &'static str,
96    /// Goes to the block's first successor.
97    pub jump: &'static str,
98    /// Goes to the address in its one operand, which is one of the block's successors and which
99    /// of them is not known until the program runs.
100    ///
101    /// The one branch here the layout does not write. A computed `goto` is selected as this
102    /// instruction, because what it reads is a value and reading a value is what selection is for,
103    /// and the layout only has to know the name so that it can tell a block that already ends in
104    /// one from a block that still wants a jump.
105    pub indirect: &'static str,
106    /// Every jump that reads the condition state and goes to the block's first successor when what
107    /// it reads holds.
108    ///
109    /// [`Self::if_true`] and [`Self::if_false`] are two of these and the entries below name the
110    /// rest, since a condition and its opposite are both jumps of this kind. The layout writes
111    /// those two itself and reads this list for the other question: whether the block it is
112    /// looking at already ends in one. A block does when an `asm` template wrote the jump, which
113    /// is how a loop a program spelled out by hand arrives here, and the layout then writes
114    /// nothing in front of it. Empty is a target whose templates never end a block that way.
115    pub conditional: &'static [&'static str],
116    /// The comparisons a branch on their answer can be folded into, and what each pair becomes.
117    ///
118    /// Empty is a target that does not do this, and the layout then writes the test every time.
119    pub fused: &'static [Fusion],
120    /// The conditional moves a select on a comparison's answer can become.
121    ///
122    /// Empty is a target that does not do this, and every select keeps the test of its byte.
123    pub moves: &'static [Move],
124}
125
126/// A comparison, and the two instructions a branch on its answer becomes.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct Fusion {
129    /// The comparison a rule selects, which writes a byte saying what it found.
130    pub set: &'static str,
131    /// The same comparison with the byte gone, which sets the condition state and keeps nothing.
132    ///
133    /// Its operands are the ones the comparison read, in the same order, with the destination at
134    /// the front taken off. The layout rewrites nothing else about them.
135    pub cmp: &'static str,
136    /// Goes to the block's first successor when the comparison held.
137    pub if_true: &'static str,
138    /// Goes to the block's first successor when the comparison did not hold.
139    pub if_false: &'static str,
140}
141
142/// A select, a condition, and the move that makes the choice straight off that condition.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub struct Move {
145    /// What a rule selects for a choice on a byte, which tests the byte and then moves.
146    pub select: &'static str,
147    /// The condition, named by the jump a [`Fusion`] takes when its comparison held.
148    pub when: &'static str,
149    /// The same move reading the condition state a comparison left, with no test in front of it.
150    ///
151    /// Its operands are the select's without the byte at the end, in the same order.
152    pub cmov: &'static str,
153}