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}