1use rucc_ir::{Def, Extra, Func, Inst, IntPred, Opcode, Type, Value};
34
35use crate::select::Subject;
36
37pub const MAX_ARGS: usize = 3;
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum Shown {
47 Reg,
49 Const,
51 Expand,
53}
54
55pub type Plan = [Shown; MAX_ARGS];
57
58pub const PLAIN: Plan = [Shown::Reg; MAX_ARGS];
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum Term {
68 Root,
70 Arg(u8),
72 Deep(u8, u8),
74 Reg(Value),
76 Num(i128),
78}
79
80#[derive(Debug)]
82pub struct Terms<'a> {
83 func: &'a Func,
84 root: Inst,
85 plan: Plan,
86}
87
88impl<'a> Terms<'a> {
89 #[must_use]
91 pub fn new(func: &'a Func, root: Inst, plan: Plan) -> Self {
92 Self { func, root, plan }
93 }
94
95 #[must_use]
97 pub fn root(&self) -> Inst {
98 self.root
99 }
100
101 #[must_use]
104 pub fn name(&self, inst: Inst) -> Option<&'static str> {
105 head_of(self.func, inst)
106 }
107
108 fn args(&self, inst: Inst) -> &[Value] {
110 &self.func[self.func[inst].args]
111 }
112
113 fn arg_value(&self, index: u8) -> Option<Value> {
115 self.args(self.root).get(usize::from(index)).copied()
116 }
117
118 fn def_of(&self, value: Value) -> Option<Inst> {
120 match self.func[value].def {
121 Def::Result { inst, .. } => Some(inst),
122 Def::Param { .. } => None,
123 }
124 }
125
126 #[must_use]
128 pub fn constant(&self, value: Value) -> Option<i128> {
129 let inst = self.def_of(value)?;
130 let data = &self.func[inst];
131 if data.opcode != Opcode::IConst {
132 return None;
133 }
134 let Extra::Imm(imm) = data.extra else { return None };
135 let ty = self.func[value].ty;
136 ty.is_int().then(|| self.func[imm].signed(ty))
137 }
138
139 fn leaf_head(&self, value: Value, shown: Shown) -> Option<(&'static str, usize)> {
142 let ty = self.func[value].ty;
143 let name = match shown {
144 Shown::Reg => value_head(ty)?,
145 Shown::Const => iconst_head(ty)?,
146 Shown::Expand => return None,
148 };
149 Some((name, 1))
150 }
151
152 fn leaf_arg(&self, value: Value, shown: Shown) -> Term {
155 match shown {
156 Shown::Const => self.constant(value).map_or(Term::Reg(value), Term::Num),
157 Shown::Reg | Shown::Expand => Term::Reg(value),
158 }
159 }
160
161 fn deep_shown(&self, value: Value) -> Shown {
168 if self.constant(value).is_some() { Shown::Const } else { Shown::Reg }
169 }
170
171 fn expansion(&self, index: u8) -> Option<(Inst, &[Value])> {
173 let value = self.arg_value(index)?;
174 let inst = self.def_of(value)?;
175 Some((inst, self.args(inst)))
176 }
177}
178
179impl Subject for Terms<'_> {
180 type Node = Term;
181
182 fn head(&self, node: Term) -> Option<(&str, usize)> {
183 match node {
184 Term::Root => {
185 let name = head_of(self.func, self.root)?;
186 let data = &self.func[self.root];
187 let arity =
190 if data.opcode == Opcode::IConst { 1 } else { self.args(self.root).len() };
191 Some((name, arity))
192 }
193 Term::Arg(index) => {
194 let value = self.arg_value(index)?;
195 match self.plan[usize::from(index)] {
196 Shown::Expand => {
197 let (inst, args) = self.expansion(index)?;
198 Some((head_of(self.func, inst)?, args.len()))
199 }
200 shown => self.leaf_head(value, shown),
201 }
202 }
203 Term::Deep(outer, inner) => {
204 let (_, args) = self.expansion(outer)?;
205 let value = *args.get(usize::from(inner))?;
206 self.leaf_head(value, self.deep_shown(value))
207 }
208 Term::Reg(_) | Term::Num(_) => None,
209 }
210 }
211
212 fn arg(&self, node: Term, index: usize) -> Term {
213 let index = u8::try_from(index).unwrap_or(u8::MAX);
214 match node {
215 Term::Root => {
216 let data = &self.func[self.root];
217 if data.opcode == Opcode::IConst {
218 let value = data.first_result.expect("a constant has a result");
219 return self.leaf_arg(value, Shown::Const);
220 }
221 Term::Arg(index)
222 }
223 Term::Arg(outer) => match self.plan[usize::from(outer)] {
224 Shown::Expand => Term::Deep(outer, index),
225 shown => {
226 self.arg_value(outer).map_or(Term::Num(0), |value| self.leaf_arg(value, shown))
227 }
228 },
229 Term::Deep(outer, inner) => {
230 let value = self
231 .expansion(outer)
232 .and_then(|(_, args)| args.get(usize::from(inner)).copied());
233 value.map_or(Term::Num(0), |value| self.leaf_arg(value, self.deep_shown(value)))
234 }
235 Term::Reg(_) | Term::Num(_) => node,
237 }
238 }
239
240 fn int(&self, node: Term) -> Option<i128> {
241 match node {
242 Term::Num(value) => Some(value),
243 _ => None,
244 }
245 }
246}
247
248fn head_of(func: &Func, inst: Inst) -> Option<&'static str> {
255 let data = &func[inst];
256
257 if data.opcode == Opcode::Store {
272 let value = *func[data.args].first()?;
273 return store_head(func[value].ty);
274 }
275
276 if data.opcode == Opcode::Return {
282 let [value] = &func[data.args] else { return None };
283 return ret_head(func[*value].ty);
284 }
285
286 if data.opcode == Opcode::BrIf {
291 let [cond] = &func[data.args] else { return None };
292 return (func[*cond].ty == Type::int(1)).then_some("brif.i1");
293 }
294
295 let result = data.first_result?;
296 let ty = func[result].ty;
297 match data.opcode {
298 Opcode::IConst => iconst_head(ty),
299 Opcode::Load => load_head(ty),
300 Opcode::ICmp => {
301 let Extra::IntPred(pred) = data.extra else { return None };
302 Some(icmp_head(pred))
303 }
304 Opcode::SExt | Opcode::ZExt | Opcode::Trunc => {
305 let from = func[*func[data.args].first()?].ty;
306 convert_head(data.opcode, from, ty)
307 }
308 Opcode::PtrAdd => binary_head(Opcode::Add, ty),
314 opcode => binary_head(opcode, ty),
315 }
316}
317
318const ADDRESS: u32 = 64;
332
333pub(crate) fn slot(ty: Type) -> Option<usize> {
339 if !ty.is_scalar() {
340 return None;
341 }
342 let bits = if ty.is_ptr() { ADDRESS } else { ty.is_int().then(|| ty.bits())? };
343 match bits {
344 8 => Some(0),
345 16 => Some(1),
346 32 => Some(2),
347 64 => Some(3),
348 _ => None,
349 }
350}
351
352fn value_head(ty: Type) -> Option<&'static str> {
359 if ty.is_scalar() && ty.is_int() && ty.bits() == 1 {
360 return Some("value.i1");
361 }
362 Some(["value.i8", "value.i16", "value.i32", "value.i64"][slot(ty)?])
363}
364
365fn iconst_head(ty: Type) -> Option<&'static str> {
371 if !ty.is_int() {
372 return None;
373 }
374 Some(["iconst.i8", "iconst.i16", "iconst.i32", "iconst.i64"][slot(ty)?])
375}
376
377fn load_head(ty: Type) -> Option<&'static str> {
379 Some(["load.i8", "load.i16", "load.i32", "load.i64"][slot(ty)?])
380}
381
382fn store_head(ty: Type) -> Option<&'static str> {
385 Some(["store.i8", "store.i16", "store.i32", "store.i64"][slot(ty)?])
386}
387
388fn ret_head(ty: Type) -> Option<&'static str> {
390 Some(["ret.i8", "ret.i16", "ret.i32", "ret.i64"][slot(ty)?])
391}
392
393fn icmp_head(pred: IntPred) -> &'static str {
396 match pred {
397 IntPred::Eq => "icmp_eq.i1",
398 IntPred::Ne => "icmp_ne.i1",
399 IntPred::Slt => "icmp_slt.i1",
400 IntPred::Sle => "icmp_sle.i1",
401 IntPred::Sgt => "icmp_sgt.i1",
402 IntPred::Sge => "icmp_sge.i1",
403 IntPred::Ult => "icmp_ult.i1",
404 IntPred::Ule => "icmp_ule.i1",
405 IntPred::Ugt => "icmp_ugt.i1",
406 IntPred::Uge => "icmp_uge.i1",
407 }
408}
409
410fn convert_head(opcode: Opcode, from: Type, to: Type) -> Option<&'static str> {
412 let table: &[[Option<&'static str>; 4]; 4] = match opcode {
413 Opcode::SExt => &SEXT,
414 Opcode::ZExt => &ZEXT,
415 Opcode::Trunc => &TRUNC,
416 _ => return None,
417 };
418 table[slot(from)?][slot(to)?]
419}
420
421fn binary_head(opcode: Opcode, ty: Type) -> Option<&'static str> {
423 let names: &[&'static str; 4] = match opcode {
424 Opcode::Add => &["add.i8", "add.i16", "add.i32", "add.i64"],
425 Opcode::Sub => &["sub.i8", "sub.i16", "sub.i32", "sub.i64"],
426 Opcode::Mul => &["mul.i8", "mul.i16", "mul.i32", "mul.i64"],
427 Opcode::SDiv => &["sdiv.i8", "sdiv.i16", "sdiv.i32", "sdiv.i64"],
428 Opcode::UDiv => &["udiv.i8", "udiv.i16", "udiv.i32", "udiv.i64"],
429 Opcode::SRem => &["srem.i8", "srem.i16", "srem.i32", "srem.i64"],
430 Opcode::URem => &["urem.i8", "urem.i16", "urem.i32", "urem.i64"],
431 Opcode::And => &["and.i8", "and.i16", "and.i32", "and.i64"],
432 Opcode::Or => &["or.i8", "or.i16", "or.i32", "or.i64"],
433 Opcode::Xor => &["xor.i8", "xor.i16", "xor.i32", "xor.i64"],
434 Opcode::Shl => &["shl.i8", "shl.i16", "shl.i32", "shl.i64"],
435 Opcode::LShr => &["lshr.i8", "lshr.i16", "lshr.i32", "lshr.i64"],
436 Opcode::AShr => &["ashr.i8", "ashr.i16", "ashr.i32", "ashr.i64"],
437 _ => return None,
438 };
439 Some(names[slot(ty)?])
440}
441
442static SEXT: [[Option<&str>; 4]; 4] = [
446 [None, Some("sext.i8.i16"), Some("sext.i8.i32"), Some("sext.i8.i64")],
447 [None, None, Some("sext.i16.i32"), Some("sext.i16.i64")],
448 [None, None, None, Some("sext.i32.i64")],
449 [None, None, None, None],
450];
451
452static ZEXT: [[Option<&str>; 4]; 4] = [
453 [None, Some("zext.i8.i16"), Some("zext.i8.i32"), Some("zext.i8.i64")],
454 [None, None, Some("zext.i16.i32"), Some("zext.i16.i64")],
455 [None, None, None, Some("zext.i32.i64")],
456 [None, None, None, None],
457];
458
459static TRUNC: [[Option<&str>; 4]; 4] = [
461 [None, None, None, None],
462 [Some("trunc.i16.i8"), None, None, None],
463 [Some("trunc.i32.i8"), Some("trunc.i32.i16"), None, None],
464 [Some("trunc.i64.i8"), Some("trunc.i64.i16"), Some("trunc.i64.i32"), None],
465];
466
467#[cfg(test)]
468mod tests {
469 use rucc_base::Interner;
470 use rucc_ir::{Builder, Flags, Signature};
471
472 use super::*;
473 use crate::select::Subject;
474
475 fn func() -> (Func, rucc_ir::Block) {
477 let mut names = Interner::new();
478 let mut func = Func::new(names.intern("f"), Signature::new());
479 let block = func.create_block();
480 (func, block)
481 }
482
483 fn inst_of(func: &Func, value: Value) -> Inst {
485 match func[value].def {
486 Def::Result { inst, .. } => inst,
487 Def::Param { .. } => unreachable!(),
488 }
489 }
490
491 #[test]
492 fn an_instruction_is_the_term_the_rule_file_names_it_by() {
493 let (mut func, block) = func();
494 let i32 = Type::int(32);
495 let mut build = Builder::new(&mut func, block);
496 let k = build.iconst(i32, 7);
497 let x = build.iconst(i32, 3);
498 let sum = build.binary(Opcode::Add, x, k, Flags::default());
499 let add = inst_of(&func, sum);
500
501 let terms = Terms::new(&func, add, PLAIN);
502 assert_eq!(terms.head(Term::Root), Some(("add.i32", 2)));
503 assert_eq!(terms.head(Term::Arg(0)), Some(("value.i32", 1)));
504 assert_eq!(terms.arg(Term::Arg(0), 0), Term::Reg(x));
505 assert_eq!(terms.head(Term::Reg(x)), None);
506 assert_eq!(terms.int(Term::Reg(x)), None);
507 }
508
509 #[test]
510 fn an_operand_shown_as_a_constant_gives_the_number_up() {
511 let (mut func, block) = func();
512 let i32 = Type::int(32);
513 let mut build = Builder::new(&mut func, block);
514 let x = build.iconst(i32, 3);
515 let k = build.iconst(i32, -7);
516 let sum = build.binary(Opcode::Add, x, k, Flags::default());
517 let add = inst_of(&func, sum);
518
519 let terms = Terms::new(&func, add, [Shown::Reg, Shown::Const, Shown::Reg]);
520 assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i32", 1)));
521 assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(-7));
522 assert_eq!(terms.int(Term::Num(-7)), Some(-7));
523 let plain = Terms::new(&func, add, PLAIN);
526 assert_eq!(plain.head(Term::Arg(1)), Some(("value.i32", 1)));
527 assert_eq!(plain.int(plain.arg(Term::Arg(1), 0)), None);
528 }
529
530 #[test]
531 fn a_constant_is_a_term_of_one_argument_and_has_no_operands() {
532 let (mut func, block) = func();
533 let mut build = Builder::new(&mut func, block);
534 let k = build.iconst(Type::int(64), 12);
535 let inst = inst_of(&func, k);
536
537 let terms = Terms::new(&func, inst, PLAIN);
538 assert_eq!(terms.head(Term::Root), Some(("iconst.i64", 1)));
539 assert_eq!(terms.arg(Term::Root, 0), Term::Num(12));
540 }
541
542 #[test]
543 fn an_expanded_operand_is_the_instruction_that_computed_it() {
544 let (mut func, block) = func();
545 let i64 = Type::int(64);
546 let y = func.append_param(block, i64);
548 let mut build = Builder::new(&mut func, block);
549 let x = build.iconst(i64, 1);
550 let four = build.iconst(i64, 4);
551 let scaled = build.binary(Opcode::Mul, y, four, Flags::default());
552 let sum = build.binary(Opcode::Add, x, scaled, Flags::default());
553 let add = inst_of(&func, sum);
554
555 let terms = Terms::new(&func, add, [Shown::Reg, Shown::Expand, Shown::Reg]);
556 assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
557 assert_eq!(terms.head(Term::Arg(1)), Some(("mul.i64", 2)));
558 assert_eq!(terms.head(Term::Deep(1, 0)), Some(("value.i64", 1)));
559 assert_eq!(terms.arg(Term::Deep(1, 0), 0), Term::Reg(y));
560 assert_eq!(terms.head(Term::Deep(1, 1)), Some(("iconst.i64", 1)));
562 assert_eq!(terms.arg(Term::Deep(1, 1), 0), Term::Num(4));
563 }
564
565 #[test]
566 fn a_comparison_says_which_one_it_is_and_a_conversion_says_both_widths() {
567 let (mut func, block) = func();
568 let mut build = Builder::new(&mut func, block);
569 let x = build.iconst(Type::int(32), 1);
570 let y = build.iconst(Type::int(32), 2);
571 let less = build.icmp(IntPred::Slt, x, y);
572 let wide = build.unary(Opcode::SExt, x, Type::int(64));
573 let narrow = build.unary(Opcode::Trunc, x, Type::int(8));
574 let cmp = inst_of(&func, less);
575 assert_eq!(Terms::new(&func, cmp, PLAIN).head(Term::Root), Some(("icmp_slt.i1", 2)));
576 let sext = inst_of(&func, wide);
577 assert_eq!(Terms::new(&func, sext, PLAIN).head(Term::Root), Some(("sext.i32.i64", 1)));
578 let trunc = inst_of(&func, narrow);
579 assert_eq!(Terms::new(&func, trunc, PLAIN).head(Term::Root), Some(("trunc.i32.i8", 1)));
580 }
581
582 #[test]
583 fn a_width_no_rule_is_written_at_has_no_name() {
584 let (mut func, block) = func();
585 let mut build = Builder::new(&mut func, block);
586 let x = build.iconst(Type::int(128), 1);
587 let inst = inst_of(&func, x);
588 assert_eq!(Terms::new(&func, inst, PLAIN).head(Term::Root), None);
589 }
590
591 #[test]
594 fn an_address_is_an_integer_as_wide_as_the_machine_addresses() {
595 assert_eq!(value_head(Type::PTR), Some("value.i64"));
596 assert_eq!(load_head(Type::PTR), Some("load.i64"));
597 assert_eq!(store_head(Type::PTR), Some("store.i64"));
598 assert_eq!(ret_head(Type::PTR), Some("ret.i64"));
599 assert_eq!(iconst_head(Type::PTR), None);
601 }
602
603 #[test]
607 fn a_vector_is_not_the_width_of_its_lane() {
608 let i32x4 = Type::vector(Type::int(32), 4);
609 assert_eq!(slot(i32x4), None);
610 assert_eq!(value_head(i32x4), None);
611 assert_eq!(binary_head(Opcode::Add, i32x4), None);
612 }
613
614 #[test]
617 fn address_arithmetic_is_an_add_at_the_address_width() {
618 let (mut func, block) = func();
619 let base = func.append_param(block, Type::PTR);
620 let mut build = Builder::new(&mut func, block);
621 let step = build.iconst(Type::int(64), 4);
622 let args = func.push_values(&[base, step]);
623 let next = Builder::new(&mut func, block)
624 .value(rucc_ir::InstData { args, ..rucc_ir::InstData::new(Opcode::PtrAdd) }, Type::PTR);
625 let inst = inst_of(&func, next);
626
627 let terms = Terms::new(&func, inst, [Shown::Reg, Shown::Const, Shown::Reg]);
628 assert_eq!(terms.head(Term::Root), Some(("add.i64", 2)));
629 assert_eq!(terms.head(Term::Arg(0)), Some(("value.i64", 1)));
630 assert_eq!(terms.head(Term::Arg(1)), Some(("iconst.i64", 1)));
631 assert_eq!(terms.arg(Term::Arg(1), 0), Term::Num(4));
632 }
633}