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, INSTS, R8, R9, R10, R11, R12, R13, RAX, RBP, RCX, RDX, RSI, RSP, Value, Width,
17    encode, gpr_name, written,
18};
19use rucc_target::{Constraint, PhysReg};
20
21/// What we call a register in the assembly we print, which the decoder has to agree with.
22fn name(reg: PhysReg, width: Width, gpr: bool) -> String {
23    if gpr {
24        format!("%{}", gpr_name(reg, width).expect("every width of a general register has a name"))
25    } else {
26        format!("%xmm{}", reg.number())
27    }
28}
29
30/// One address of every shape the encoding treats differently.
31///
32/// The stack pointer and the frame pointer are in here twice over, once as themselves and once as
33/// the two registers the machine gained later that are written the same way, because those four
34/// are the cases an address cannot be written plainly in.
35fn addresses() -> Vec<(Addr, String)> {
36    let at = |base, index, scale, disp| Addr { base, index, scale, disp, rip: false };
37    vec![
38        (at(Some(RCX), None, 0, 0), "(%rcx)".to_owned()),
39        (at(Some(RCX), None, 0, -16), "-16(%rcx)".to_owned()),
40        (at(Some(RCX), None, 0, 1000), "1000(%rcx)".to_owned()),
41        (at(Some(RSP), None, 0, 8), "8(%rsp)".to_owned()),
42        (at(Some(RBP), None, 0, 0), "0(%rbp)".to_owned()),
43        (at(Some(R12), None, 0, 8), "8(%r12)".to_owned()),
44        (at(Some(R13), None, 0, 0), "0(%r13)".to_owned()),
45        (at(Some(RCX), Some(RDX), 4, -16), "-16(%rcx,%rdx,4)".to_owned()),
46        (at(Some(R8), Some(R9), 8, 0), "(%r8,%r9,8)".to_owned()),
47        (at(None, Some(RDX), 2, 32), "32(,%rdx,2)".to_owned()),
48        (at(None, None, 0, 64), "64".to_owned()),
49    ]
50}
51
52fn main() {
53    let banks = [[RAX, RCX, RDX, RSI], [R8, R9, R10, R11]];
54    let immediates: [i64; 4] = [1, -1, 1000, 0x1_2345_6789];
55    let mut lines = Vec::new();
56
57    for &(opcode, form) in INSTS {
58        let operands = form.operands();
59        for inst in written(opcode).expect("every opcode in the table is written") {
60            if inst.args.iter().any(|arg| matches!(arg, Arg::Symbol | Arg::Label)) {
61                continue;
62            }
63            let gpr = !inst.mnemonic.starts_with("movaps");
64            let has = |kind: fn(&Arg) -> bool| inst.args.iter().any(kind);
65            let mems = if has(|arg| matches!(arg, Arg::Mem)) {
66                addresses()
67            } else {
68                vec![(Addr::default(), String::new())]
69            };
70            let imms =
71                if has(|arg| matches!(arg, Arg::Imm)) { immediates.to_vec() } else { vec![0] };
72
73            for bank in banks {
74                for (addr, addr_text) in &mems {
75                    for &imm in &imms {
76                        let mut values = Vec::new();
77                        let mut text = Vec::new();
78                        let mut high = false;
79                        for arg in inst.args {
80                            match *arg {
81                                Arg::Reg(at, width) => {
82                                    // An operand pinned to a register is that register and
83                                    // nothing else, which is what makes every shift count %cl.
84                                    let reg = match operands[usize::from(at)].constraint {
85                                        Constraint::Fixed(fixed) => fixed,
86                                        _ => bank[usize::from(at) % bank.len()],
87                                    };
88                                    values.push(Value::Reg(reg, width));
89                                    text.push(name(reg, width, gpr));
90                                }
91                                Arg::Named(named) => {
92                                    high = true;
93                                    values.push(Value::High(RAX));
94                                    text.push(format!("%{named}"));
95                                }
96                                Arg::Imm => {
97                                    values.push(Value::Imm(imm));
98                                    text.push(format!("${imm}"));
99                                }
100                                Arg::Mem => {
101                                    values.push(Value::Mem(*addr));
102                                    text.push(addr_text.clone());
103                                }
104                                Arg::Symbol | Arg::Label => unreachable!("filtered above"),
105                            }
106                        }
107                        // The high half of a register cannot share an instruction with one of the
108                        // registers the machine gained later, so the second bank has nothing to
109                        // say about an instruction naming it.
110                        if high && bank[0] != RAX {
111                            continue;
112                        }
113                        let mut bytes = Vec::new();
114                        match encode(inst.mnemonic, &values, &mut bytes) {
115                            Ok(_) => {}
116                            Err(e) => {
117                                eprintln!("{}: {e}", inst.mnemonic);
118                                continue;
119                            }
120                        }
121                        let hex: Vec<String> =
122                            bytes.iter().map(|byte| format!("{byte:02x}")).collect();
123                        let written = match text.is_empty() {
124                            true => inst.mnemonic.to_owned(),
125                            false => format!("{} {}", inst.mnemonic, text.join(", ")),
126                        };
127                        lines.push(format!("{}|{written}", hex.join(" ")));
128                    }
129                }
130            }
131        }
132    }
133
134    println!("{}", lines.join("\n"));
135    eprintln!("{} instructions", lines.len());
136}