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