1#![allow(clippy::manual_range_contains)]
9
10include!(concat!(env!("OUT_DIR"), "/aarch64.rs"));
11
12pub static SELECTOR: super::Selector = super::Selector {
14 table: &TABLE,
15 shapes: &rucc_target::aarch64::MACHINE,
16 address: rucc_target::aarch64::address,
17 frame: &rucc_target::aarch64::FRAME,
18 branch: &rucc_target::aarch64::BRANCH,
19 gpr: rucc_target::aarch64::GPR,
20 fence: "fence",
21 trap: "trap",
22 abi: &crate::abi::aarch64::INSTS,
23 scratch: &crate::pipeline::AARCH64_SCRATCH,
24 symbols: &super::Symbols {
25 near: super::Reach::Own("addr_64"),
26 far: super::Reach::Own("got_64"),
27 slot: super::Reach::Own("slot_64"),
28 thread: super::Reach::Own("gottprel_64"),
29 pointer: super::Pointer::Own("thread_64"),
30 indexed: None,
31 teb: Some(super::Teb { index: "ldr_sym_32", array: "teb_64", block: "secrel_64" }),
32 },
33 jumps: &super::Jumps {
34 near: "adr_64",
35 cell: "ldrs_32_64",
36 add: "add_rr_64",
37 two_address: false,
38 },
39};
40
41#[cfg(test)]
42mod tests {
43 use rucc_target::aarch64;
44
45 use super::TABLE;
46 use crate::select::{Piece, Subject};
47
48 const PREFIX: &str = "a64.";
50
51 const AMODES: &[&str] = &["amode_base", "amode_base_offset"];
53
54 #[derive(Debug)]
56 enum Node {
57 Int(i128),
58 App(String, Vec<usize>),
59 }
60
61 #[derive(Debug, Default)]
62 struct Terms {
63 nodes: Vec<Node>,
64 }
65
66 impl Terms {
67 fn constant(&mut self, head: &str, value: i128) -> usize {
68 self.nodes.push(Node::Int(value));
69 let at = self.nodes.len() - 1;
70 self.app(head, &[at])
71 }
72
73 fn app(&mut self, head: &str, args: &[usize]) -> usize {
74 self.nodes.push(Node::App(head.to_owned(), args.to_vec()));
75 self.nodes.len() - 1
76 }
77
78 fn value(&mut self, width: &str, name: &str) -> usize {
79 let inner = self.app(name, &[]);
80 self.app(&format!("value.{width}"), &[inner])
81 }
82 }
83
84 impl Subject for Terms {
85 type Node = usize;
86
87 fn head(&self, node: usize) -> Option<(&str, usize)> {
88 match &self.nodes[node] {
89 Node::App(head, args) => Some((head.as_str(), args.len())),
90 Node::Int(_) => None,
91 }
92 }
93
94 fn arg(&self, node: usize, index: usize) -> usize {
95 match &self.nodes[node] {
96 Node::App(_, args) => args[index],
97 Node::Int(_) => unreachable!("a constant has no arguments"),
98 }
99 }
100
101 fn int(&self, node: usize) -> Option<i128> {
102 match self.nodes[node] {
103 Node::Int(value) => Some(value),
104 Node::App(..) => None,
105 }
106 }
107
108 fn same(&self, a: usize, b: usize) -> bool {
109 a == b
110 }
111 }
112
113 fn selects(terms: &Terms, term: usize) -> Option<&'static str> {
114 let found = TABLE.find(terms, term)?;
115 TABLE.rule(&found).head()
116 }
117
118 #[test]
119 fn the_table_holds_every_rule_the_file_writes() {
120 let text = include_str!("../../rules/aarch64.rules");
121 let written = text.lines().filter(|line| line.starts_with("(rule ")).count();
122 assert_eq!(TABLE.rules.len(), written, "the table and the rule file disagree");
123 assert_eq!(TABLE.source, "rules/aarch64.rules");
124 }
125
126 #[test]
127 fn every_instruction_the_table_writes_is_described() {
128 for rule in TABLE.rules {
129 for piece in rule.replacement {
130 let Piece::App { head, .. } = piece else { continue };
131 if AMODES.contains(head) {
132 continue;
133 }
134 let opcode = head.strip_prefix(PREFIX).unwrap_or_else(|| {
135 panic!("line {}: {head} is neither an AArch64 term nor an address", rule.line)
136 });
137 assert!(
138 aarch64::form(opcode).is_some(),
139 "line {}: {head} is selected and `rucc_target::aarch64` does not describe it",
140 rule.line
141 );
142 }
143 }
144 }
145
146 #[test]
149 fn narrow_arithmetic_is_the_thirty_two_bit_instruction() {
150 let mut terms = Terms::default();
151 for width in ["i8", "i16", "i32"] {
152 let x = terms.value(width, "v0");
153 let y = terms.value(width, "v1");
154 let add = terms.app(&format!("add.{width}"), &[x, y]);
155 assert_eq!(selects(&terms, add), Some("a64.add_rr_32"));
156 let mul = terms.app(&format!("mul.{width}"), &[x, y]);
157 assert_eq!(selects(&terms, mul), Some("a64.mul_rr_32"));
158 }
159 let x = terms.value("i8", "v0");
160 let y = terms.value("i8", "v1");
161 let shift = terms.app("lshr.i8", &[x, y]);
162 assert_eq!(selects(&terms, shift), None);
163 let x = terms.value("i64", "v2");
164 let y = terms.value("i64", "v3");
165 let add = terms.app("add.i64", &[x, y]);
166 assert_eq!(selects(&terms, add), Some("a64.add_rr_64"));
167 }
168
169 #[test]
172 fn a_narrow_right_shift_by_a_constant_is_the_instruction_that_widens_first() {
173 let mut terms = Terms::default();
174 for (width, bits) in [("i8", 7), ("i16", 15)] {
175 let x = terms.value(width, "v0");
176 let k = terms.constant(&format!("iconst.{width}"), 3);
177 let right = terms.app(&format!("lshr.{width}"), &[x, k]);
178 assert_eq!(selects(&terms, right), Some(&*format!("a64.lsr_ri_{}", &width[1..])));
179 let right = terms.app(&format!("ashr.{width}"), &[x, k]);
180 assert_eq!(selects(&terms, right), Some(&*format!("a64.asr_ri_{}", &width[1..])));
181 let far = terms.constant(&format!("iconst.{width}"), bits + 1);
182 let right = terms.app(&format!("lshr.{width}"), &[x, far]);
183 assert_eq!(selects(&terms, right), None);
184 }
185 }
186
187 #[test]
192 fn a_constant_is_one_mov_only_when_one_mov_can_build_it() {
193 let mut terms = Terms::default();
194 let wanted = [
195 (0, "a64.mov_ri_64"),
196 (65535, "a64.mov_ri_64"),
197 (-65536, "a64.mov_ri_64"),
198 (65536, "a64.mov_ri_64"),
199 (0x1_0000_0000, "a64.mov_ri_64"),
200 (0x4008_0000_0000_0000, "a64.mov_ri_64"),
201 (-0x1234_0000_0001, "a64.mov_ri_64"),
202 (-0x1_0001, "a64.mov_ri_64"),
203 (0x1_0001, "a64.movk_ri_16_64"),
204 (0xffff_ffff, "a64.movk_ri_16_64"),
205 (0x1_2345_6789, "a64.movk_ri_32_64"),
206 (0x1_0000_0001, "a64.movk_ri_32_64"),
207 (-0x1_0002, "a64.movk_ri_48_64"),
208 (0x1_0000_0000_0001, "a64.movk_ri_48_64"),
209 ];
210 for (value, want) in wanted {
211 let k = terms.constant("iconst.i64", value);
212 assert_eq!(selects(&terms, k), Some(want), "{value}");
213 }
214 let wanted = [
215 (0x1234_5678, "a64.movk_ri_16_32"),
216 (0x4040_0000, "a64.mov_ri_32"),
217 (0x1234_ffff, "a64.mov_ri_32"),
218 (i128::from(i32::MIN), "a64.mov_ri_32"),
219 ];
220 for (value, want) in wanted {
221 let k = terms.constant("iconst.i32", value);
222 assert_eq!(selects(&terms, k), Some(want), "{value}");
223 }
224 }
225
226 #[test]
228 fn every_widening_and_narrowing_has_an_instruction() {
229 let mut terms = Terms::default();
230 let wanted = [
231 ("sext", "i8", "i16", "a64.sxtb_16"),
232 ("zext", "i8", "i16", "a64.uxtb_16"),
233 ("zext", "i8", "i64", "a64.uxtb_64"),
234 ("zext", "i16", "i64", "a64.uxth_64"),
235 ("zext", "i1", "i8", "a64.bit_to_8"),
236 ("zext", "i1", "i64", "a64.bit_to_64"),
237 ("trunc", "i64", "i32", "a64.low_32"),
238 ("trunc", "i32", "i16", "a64.low_16"),
239 ("trunc", "i16", "i8", "a64.low_8"),
240 ("trunc", "i8", "i1", "a64.bit_of_32"),
241 ("trunc", "i64", "i1", "a64.bit_of_64"),
242 ];
243 for (op, from, to, want) in wanted {
244 let x = terms.value(from, "v0");
245 let term = terms.app(&format!("{op}.{from}.{to}"), &[x]);
246 assert_eq!(selects(&terms, term), Some(want), "{op}.{from}.{to}");
247 }
248 }
249
250 #[test]
251 fn an_immediate_is_taken_when_twelve_bits_hold_it() {
252 let mut terms = Terms::default();
253 let x = terms.value("i32", "v0");
254 let k = terms.constant("iconst.i32", 4095);
255 let add = terms.app("add.i32", &[x, k]);
256 assert_eq!(selects(&terms, add), Some("a64.add_ri_32"));
257 let k = terms.constant("iconst.i32", 4096);
258 let add = terms.app("add.i32", &[x, k]);
259 assert_eq!(selects(&terms, add), None);
260 }
261
262 #[test]
264 fn an_unsigned_comparison_is_the_condition_the_architecture_names() {
265 let mut terms = Terms::default();
266 let x = terms.value("i64", "v0");
267 let y = terms.value("i64", "v1");
268 for (ir, cc) in [("ult", "lo"), ("ule", "ls"), ("ugt", "hi"), ("uge", "hs")] {
269 let term = terms.app(&format!("icmp_{ir}.i1"), &[x, y]);
270 let want = format!("a64.cmp_set_{cc}_64");
271 assert_eq!(selects(&terms, term), Some(want.as_str()));
272 }
273 }
274
275 #[test]
278 fn a_comparison_against_a_constant_is_written_for_every_one_against_a_register() {
279 let mut against_register = Vec::new();
280 let mut against_constant = Vec::new();
281 let mut narrow = 0;
282 for rule in TABLE.rules {
283 let Some(rest) = rule.pattern.strip_prefix("(icmp_") else { continue };
284 let (condition, operands) = rest.split_once(".i1 ").expect("a comparison takes two");
285 let width = operands
286 .strip_prefix("(value.")
287 .and_then(|rest| rest.split_once(' '))
288 .map(|(width, _)| width)
289 .expect("a comparison reads a value first");
290 if matches!(width, "i8" | "i16") {
293 narrow += 1;
294 continue;
295 }
296 let named = format!("{condition}.{width}");
297 if operands.contains("(iconst.") {
298 against_constant.push(named);
299 } else {
300 against_register.push(named);
301 }
302 }
303 against_register.sort_unstable();
304 against_constant.sort_unstable();
305 assert_eq!(against_register, against_constant);
306 assert_eq!(against_register.len(), 20, "ten conditions at two widths");
307 assert_eq!(narrow, 20, "ten conditions at the two narrow widths");
308 }
309
310 #[test]
312 fn a_store_is_written_with_the_value_first() {
313 let mut seen = 0;
314 for rule in TABLE.rules {
315 let Some(rest) = rule.pattern.strip_prefix("(store.") else { continue };
316 let (width, operands) = rest.split_once(' ').expect("a store takes operands");
317 assert!(
318 operands.starts_with(&format!("(value.{width} ")),
319 "line {}: {} binds something other than the value first",
320 rule.line,
321 rule.pattern
322 );
323 seen += 1;
324 }
325 assert_eq!(seen, 16, "the store rules moved and this test did not follow them");
326 }
327
328 #[test]
331 fn a_small_offset_is_part_of_the_address() {
332 let mut terms = Terms::default();
333 let a = terms.value("i64", "v0");
334 let k = terms.constant("iconst.i64", -8);
335 let at = terms.app("add.i64", &[a, k]);
336 let load = terms.app("load.i32", &[at]);
337 let found = TABLE.find(&terms, load).expect("a rule fires");
338 assert_eq!(TABLE.rule(&found).head(), Some("a64.ldr_32"));
339 assert!(
340 TABLE
341 .rule(&found)
342 .replacement
343 .iter()
344 .any(|piece| matches!(piece, Piece::App { head: "amode_base_offset", .. }))
345 );
346 let k = terms.constant("iconst.i64", 256);
347 let at = terms.app("add.i64", &[a, k]);
348 let load = terms.app("load.i32", &[at]);
349 assert_eq!(selects(&terms, load), None);
350 }
351}