Skip to main content

rucc_codegen/select/
x86_64.rs

1//! The x86-64 lowering table.
2//!
3//! Everything below the module comment is generated from `rules/x86-64.rules` by `rucc-rules`
4//! when this crate is built, and none of it is in the repository. The rule file is the only
5//! place the rules are written, which is what makes the table that is matched with and the
6//! table `rucc-verify` proves things about the same table.
7//!
8//! To read the rules, read the rule file. To read the automaton they compile into, build the
9//! crate and read `x86-64.rs` under the build directory, which is a file worth looking at once
10//! for the shape of it and never again.
11
12// A guard is emitted as the comparison the rule writes, so a rule saying a shift count is at
13// least zero and less than the width comes out as two comparisons rather than as a range. That
14// is deliberate: the generated line and the rule it came from should read the same, and the
15// suggestion to write it another way is advice for somebody editing code, which nobody here is.
16#![allow(clippy::manual_range_contains)]
17
18include!(concat!(env!("OUT_DIR"), "/x86-64.rs"));
19
20#[cfg(test)]
21mod tests {
22    use rucc_target::x86_64;
23
24    use super::TABLE;
25    use crate::select::Piece;
26
27    /// The prefix a rule file puts in front of a machine term, which is how it says which target
28    /// the term belongs to. It is not part of the opcode.
29    const PREFIX: &str = "x64.";
30
31    /// The two address constructors, which are not instructions. An addressing mode is an
32    /// argument to `lea` and to every memory operand after it, so it is written as a term in the
33    /// rule file and built by the selector into the instruction that takes it.
34    const AMODES: &[&str] = &["amode_base_index_scale", "amode_index_scale"];
35
36    /// Every head this table can write, in and under the replacements.
37    fn heads() -> Vec<&'static str> {
38        let mut found: Vec<&'static str> = TABLE
39            .rules
40            .iter()
41            .flat_map(|rule| rule.replacement.iter())
42            .filter_map(|piece| match piece {
43                Piece::App { head, .. } => Some(*head),
44                _ => None,
45            })
46            .collect();
47        found.sort_unstable();
48        found.dedup();
49        found
50    }
51
52    #[test]
53    fn every_instruction_the_table_writes_is_described() {
54        for head in heads() {
55            if AMODES.contains(&head) {
56                continue;
57            }
58            let opcode = head.strip_prefix(PREFIX).unwrap_or_else(|| {
59                panic!("{head} is neither an x86-64 term nor an addressing mode")
60            });
61            assert!(
62                x86_64::form(opcode).is_some(),
63                "{head} is selected by a rule and `rucc_target::x86_64` does not say what it \
64                 does with its operands"
65            );
66        }
67    }
68
69    #[test]
70    fn every_described_instruction_is_reachable_from_a_rule() {
71        let written = heads();
72        for &(opcode, _) in x86_64::INSTS {
73            let head = format!("{PREFIX}{opcode}");
74            assert!(
75                written.contains(&head.as_str()),
76                "{opcode} is described and no rule in {} selects it",
77                TABLE.source
78            );
79        }
80    }
81}