pub mod x86_64;
pub use rucc_base::rules::{Guard, Match, Node, Piece, Rule, Subject, Table, Test};
#[cfg(test)]
mod tests {
use super::x86_64::TABLE;
use super::{Piece, Subject};
#[derive(Debug)]
enum Node {
Int(i128),
App(String, Vec<usize>),
}
#[derive(Debug, Default)]
struct Terms {
nodes: Vec<Node>,
}
impl Terms {
fn constant(&mut self, value: i128) -> usize {
self.nodes.push(Node::Int(value));
self.nodes.len() - 1
}
fn app(&mut self, head: &str, args: &[usize]) -> usize {
self.nodes.push(Node::App(head.to_owned(), args.to_vec()));
self.nodes.len() - 1
}
fn value(&mut self, width: u32, name: &str) -> usize {
let inner = self.app(name, &[]);
self.app(&format!("value.i{width}"), &[inner])
}
}
impl Subject for Terms {
type Node = usize;
fn head(&self, node: usize) -> Option<(&str, usize)> {
match &self.nodes[node] {
Node::App(head, args) => Some((head.as_str(), args.len())),
Node::Int(_) => None,
}
}
fn arg(&self, node: usize, index: usize) -> usize {
match &self.nodes[node] {
Node::App(_, args) => args[index],
Node::Int(_) => unreachable!("a constant has no arguments"),
}
}
fn int(&self, node: usize) -> Option<i128> {
match self.nodes[node] {
Node::Int(value) => Some(value),
Node::App(..) => None,
}
}
fn same(&self, a: usize, b: usize) -> bool {
a == b
}
}
fn selects(terms: &Terms, term: usize) -> Option<&'static str> {
let found = TABLE.find(terms, term)?;
TABLE.rule(&found).head()
}
#[test]
fn the_table_holds_every_rule_the_file_writes() {
let text = include_str!("../rules/x86-64.rules");
let written = text.lines().filter(|line| line.starts_with("(rule ")).count();
assert_eq!(TABLE.rules.len(), written, "the table and the rule file disagree");
assert_eq!(TABLE.source, "rules/x86-64.rules");
}
#[test]
fn an_addition_of_two_registers_is_the_register_form() {
let mut terms = Terms::default();
let x = terms.value(64, "v0");
let y = terms.value(64, "v1");
let add = terms.app("add.i64", &[x, y]);
assert_eq!(selects(&terms, add), Some("x64.add_rr_64"));
}
#[test]
fn a_match_gives_back_the_operands_the_pattern_named() {
let mut terms = Terms::default();
let first = terms.app("v0", &[]);
let second = terms.app("v1", &[]);
let x = terms.app("value.i32", &[first]);
let y = terms.app("value.i32", &[second]);
let sub = terms.app("sub.i32", &[x, y]);
let found = TABLE.find(&terms, sub).expect("a rule fires");
let rule = TABLE.rule(&found);
assert_eq!(rule.pattern, "(sub.i32 (value.i32 x) (value.i32 y))");
assert_eq!(found.bindings, vec![first, second]);
let names: Vec<&str> = rule
.replacement
.iter()
.filter_map(|piece| match piece {
Piece::Var { name, index } => {
assert_eq!(found.bindings[*index], if *index == 0 { first } else { second });
Some(*name)
}
_ => None,
})
.collect();
assert_eq!(names, ["x", "y"]);
}
#[test]
fn an_addition_of_an_immediate_that_fits_is_the_immediate_form() {
let mut terms = Terms::default();
let x = terms.value(64, "v0");
let k = terms.constant(4);
let k = terms.app("iconst.i64", &[k]);
let add = terms.app("add.i64", &[x, k]);
assert_eq!(selects(&terms, add), Some("x64.add_ri_64"));
}
#[test]
fn an_addition_of_an_immediate_too_wide_for_the_form_matches_nothing() {
let mut terms = Terms::default();
let x = terms.value(64, "v0");
let k = terms.constant(1 << 40);
let k = terms.app("iconst.i64", &[k]);
let add = terms.app("add.i64", &[x, k]);
assert_eq!(selects(&terms, add), None);
}
#[test]
fn a_shift_by_a_count_the_width_allows_is_the_immediate_form() {
let mut terms = Terms::default();
let x = terms.value(64, "v0");
let k = terms.constant(3);
let k = terms.app("iconst.i64", &[k]);
let shl = terms.app("shl.i64", &[x, k]);
assert_eq!(selects(&terms, shl), Some("x64.shl_ri_64"));
}
#[test]
fn a_shift_by_a_count_the_width_does_not_allow_matches_nothing() {
let mut terms = Terms::default();
let x = terms.value(64, "v0");
let k = terms.constant(64);
let k = terms.app("iconst.i64", &[k]);
let shl = terms.app("shl.i64", &[x, k]);
assert_eq!(selects(&terms, shl), None);
}
#[test]
fn a_truth_value_is_lowered_to_the_byte_instructions_that_keep_it_one() {
let mut terms = Terms::default();
let x = terms.value(1, "v0");
let y = terms.value(1, "v1");
let xor = terms.app("xor.i1", &[x, y]);
assert_eq!(selects(&terms, xor), Some("x64.xor_rr_8"));
let x = terms.value(1, "v2");
let wide = terms.app("zext.i1.i32", &[x]);
assert_eq!(selects(&terms, wide), Some("x64.bit_to_32"));
let x = terms.value(8, "v3");
let byte = terms.app("zext.i8.i32", &[x]);
assert_eq!(selects(&terms, byte), Some("x64.movzx_8_32"));
}
#[test]
fn a_term_no_rule_covers_finds_no_rule() {
let mut terms = Terms::default();
let x = terms.value(64, "v0");
let y = terms.value(64, "v1");
let odd = terms.app("no.such.opcode", &[x, y]);
assert_eq!(selects(&terms, odd), None);
}
}