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}