Skip to main content

listing/
listing.rs

1//! Every instruction this target writes, as bytes and as text, one per line.
2//!
3//! The input to the differential disassembly check `spec/11-asm-objects-debug.md` section 11.1
4//! asks for, which is `cargo xtask disasm`. Each line is the bytes we encode an instruction to,
5//! then a bar, then the assembly we print for the same instruction. The check reads an
6//! independent decoder's account of each half and holds the two accounts to being the same
7//! instruction.
8//!
9//! The listing is every instruction in the table crossed with enough operands to reach the cases
10//! the encoding turns on: a register the machine had from the start and one it gained later, an
11//! address of every shape, and an immediate of every width. Instructions naming a symbol or a
12//! label are left out, because what they encode to is not settled until something says where the
13//! symbol went.
14
15use rucc_target::x86_64::{
16    Addr, Arg, GPR, INSTS, R8, R9, R10, R11, R12, R13, RAX, RBP, RCX, RDX, RSI, RSP, Value, Width,
17    encode, gpr_high, gpr_name, written,
18};
19use rucc_target::{Constraint, PhysReg, RegClass};
20
21/// What we call a register in the assembly we print, which the decoder has to agree with.
22///
23/// The class is the operand's rather than a guess from the mnemonic, so an instruction that names
24/// one register from each file is written correctly and a new vector instruction needs nothing
25/// added here.
26fn name(reg: PhysReg, width: Width, class: RegClass) -> String {
27    if class == GPR {
28        format!("%{}", gpr_name(reg, width).expect("every width of a general register has a name"))
29    } else {
30        format!("%xmm{}", reg.number())
31    }
32}
33
34/// One address of every shape the encoding treats differently.
35///
36/// The stack pointer and the frame pointer are in here twice over, once as themselves and once as
37/// the two registers the machine gained later that are written the same way, because those four
38/// are the cases an address cannot be written plainly in.
39fn addresses() -> Vec<(Addr, String)> {
40    let at = |base, index, scale, disp| Addr { base, index, scale, disp, ..Addr::default() };
41    vec![
42        (at(Some(RCX), None, 0, 0), "(%rcx)".to_owned()),
43        (at(Some(RCX), None, 0, -16), "-16(%rcx)".to_owned()),
44        (at(Some(RCX), None, 0, 1000), "1000(%rcx)".to_owned()),
45        (at(Some(RSP), None, 0, 8), "8(%rsp)".to_owned()),
46        (at(Some(RBP), None, 0, 0), "0(%rbp)".to_owned()),
47        (at(Some(R12), None, 0, 8), "8(%r12)".to_owned()),
48        (at(Some(R13), None, 0, 0), "0(%r13)".to_owned()),
49        (at(Some(RCX), Some(RDX), 4, -16), "-16(%rcx,%rdx,4)".to_owned()),
50        (at(Some(R8), Some(R9), 8, 0), "(%r8,%r9,8)".to_owned()),
51        (at(None, Some(RDX), 2, 32), "32(,%rdx,2)".to_owned()),
52        (at(None, None, 0, 64), "64".to_owned()),
53    ]
54}
55
56fn main() {
57    let banks = [[RAX, RCX, RDX, RSI], [R8, R9, R10, R11]];
58    let immediates: [i64; 4] = [1, -1, 1000, 0x1_2345_6789];
59    let mut lines = Vec::new();
60
61    for &(opcode, form) in INSTS {
62        let operands = form.operands();
63        for inst in written(opcode).expect("every opcode in the table is written") {
64            if inst.args.iter().any(|arg| matches!(arg, Arg::Symbol | Arg::Label)) {
65                continue;
66            }
67            let has = |kind: fn(&Arg) -> bool| inst.args.iter().any(kind);
68            let mems = if has(|arg| matches!(arg, Arg::Mem)) {
69                addresses()
70            } else {
71                vec![(Addr::default(), String::new())]
72            };
73            let imms =
74                if has(|arg| matches!(arg, Arg::Imm)) { immediates.to_vec() } else { vec![0] };
75
76            for bank in banks {
77                for (addr, addr_text) in &mems {
78                    for &imm in &imms {
79                        let mut values = Vec::new();
80                        let mut text = Vec::new();
81                        let mut high = false;
82                        for arg in inst.args {
83                            match *arg {
84                                Arg::Reg(at, width) => {
85                                    // An operand pinned to a register is that register and
86                                    // nothing else, which is what makes every shift count %cl.
87                                    let desc = operands[usize::from(at)];
88                                    let reg = match desc.constraint {
89                                        Constraint::Fixed(fixed) => fixed,
90                                        _ => bank[usize::from(at) % bank.len()],
91                                    };
92                                    values.push(Value::Reg(reg, width));
93                                    text.push(name(reg, width, desc.class));
94                                }
95                                // A vector register, which is a whole register and has no
96                                // constraint on this machine: the one operand anything pins to a
97                                // vector register is the value a function gives back, and that is
98                                // written as nothing at all.
99                                Arg::Xmm(at) => {
100                                    let desc = operands[usize::from(at)];
101                                    let reg = match desc.constraint {
102                                        Constraint::Fixed(fixed) => fixed,
103                                        _ => bank[usize::from(at) % bank.len()],
104                                    };
105                                    values.push(Value::Xmm(reg));
106                                    text.push(name(reg, Width::Quad, desc.class));
107                                }
108                                // A call names no operand in the table, so there is no constraint
109                                // to read and any register at all is one it could go through.
110                                Arg::Through => {
111                                    let reg = bank[0];
112                                    values.push(Value::Reg(reg, Width::Quad));
113                                    text.push(format!("*{}", name(reg, Width::Quad, GPR)));
114                                }
115                                // The two halves of a word, which is the one instruction here that
116                                // names part of a register rather than an amount of one. Both ends
117                                // of it are pinned, so the bank has no say and the register is
118                                // whatever the description pinned them to.
119                                Arg::Low(at) => {
120                                    let desc = operands[usize::from(at)];
121                                    let reg = match desc.constraint {
122                                        Constraint::Fixed(fixed) => fixed,
123                                        _ => bank[usize::from(at) % bank.len()],
124                                    };
125                                    values.push(Value::Reg(reg, Width::Byte));
126                                    text.push(name(reg, Width::Byte, desc.class));
127                                }
128                                Arg::High(at) => {
129                                    let desc = operands[usize::from(at)];
130                                    let reg = match desc.constraint {
131                                        Constraint::Fixed(fixed) => fixed,
132                                        _ => bank[usize::from(at) % bank.len()],
133                                    };
134                                    high = true;
135                                    values.push(Value::High(reg));
136                                    text.push(format!("%{}", gpr_high(reg).unwrap_or("?")));
137                                }
138                                Arg::Named(named) => {
139                                    high = true;
140                                    values.push(Value::High(RAX));
141                                    text.push(format!("%{named}"));
142                                }
143                                // A depth on the x87 stack, which is the same for every bank
144                                // because it is not a register: nothing here picks it, the table
145                                // says which one it is, and the opcode already carries it.
146                                Arg::Stack(depth) => {
147                                    values.push(Value::Stack);
148                                    text.push(format!("%st({depth})"));
149                                }
150                                Arg::Imm => {
151                                    values.push(Value::Imm(imm));
152                                    text.push(format!("${imm}"));
153                                }
154                                // An immediate the table wrote, which is one number rather than
155                                // the four the loop tries, because it is part of the instruction.
156                                Arg::Lit(lane) => {
157                                    values.push(Value::Imm(i64::from(lane)));
158                                    text.push(format!("${lane}"));
159                                }
160                                Arg::Mem => {
161                                    values.push(Value::Mem(*addr));
162                                    text.push(addr_text.clone());
163                                }
164                                Arg::Symbol | Arg::Label => unreachable!("filtered above"),
165                            }
166                        }
167                        // The high half of a register cannot share an instruction with one of the
168                        // registers the machine gained later, so the second bank has nothing to
169                        // say about an instruction naming it.
170                        if high && bank[0] != RAX {
171                            continue;
172                        }
173                        let mut bytes = Vec::new();
174                        match encode(inst.mnemonic, &values, &mut bytes) {
175                            Ok(_) => {}
176                            Err(e) => {
177                                eprintln!("{}: {e}", inst.mnemonic);
178                                continue;
179                            }
180                        }
181                        let hex: Vec<String> =
182                            bytes.iter().map(|byte| format!("{byte:02x}")).collect();
183                        let written = match text.is_empty() {
184                            true => inst.mnemonic.to_owned(),
185                            false => format!("{} {}", inst.mnemonic, text.join(", ")),
186                        };
187                        lines.push(format!("{}|{written}", hex.join(" ")));
188                    }
189                }
190            }
191        }
192    }
193
194    println!("{}", lines.join("\n"));
195    eprintln!("{} instructions", lines.len());
196}