pub mod x86_64;
pub trait Subject {
type Node: Copy;
fn head(&self, node: Self::Node) -> Option<(&str, usize)>;
fn arg(&self, node: Self::Node, index: usize) -> Self::Node;
fn int(&self, node: Self::Node) -> Option<i128>;
}
#[derive(Debug)]
pub enum Test {
App {
head: &'static str,
arity: usize,
},
Int(i128),
}
#[derive(Debug)]
pub struct Node {
pub tests: &'static [(Test, u32)],
pub wildcard: Option<(&'static str, u32)>,
pub accept: Option<u32>,
}
#[derive(Debug)]
pub enum Piece {
Var {
name: &'static str,
index: usize,
},
Int(i128),
App {
head: &'static str,
arity: usize,
},
}
pub type Guard = fn(&[Option<i128>]) -> bool;
#[derive(Debug)]
pub struct Rule {
pub pattern: &'static str,
pub replacement: &'static [Piece],
pub guard: Option<Guard>,
pub line: u32,
}
impl Rule {
#[must_use]
pub fn head(&self) -> Option<&'static str> {
match self.replacement.first() {
Some(Piece::App { head, .. }) => Some(head),
_ => None,
}
}
}
#[derive(Debug)]
pub struct Table {
pub source: &'static str,
pub nodes: &'static [Node],
pub rules: &'static [Rule],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Match<N> {
pub rule: usize,
pub bindings: Vec<N>,
}
impl Table {
#[must_use]
pub fn find<S: Subject>(&self, subject: &S, term: S::Node) -> Option<Match<S::Node>> {
let mut bindings = Vec::new();
let rule = self.run(subject, 0, vec![term], &mut bindings)?;
Some(Match { rule, bindings })
}
#[must_use]
pub fn rule<N>(&self, found: &Match<N>) -> &Rule {
&self.rules[found.rule]
}
fn run<S: Subject>(
&self,
subject: &S,
at: usize,
mut left: Vec<S::Node>,
bindings: &mut Vec<S::Node>,
) -> Option<usize> {
let Some(term) = left.pop() else {
return self.accept(subject, at, bindings);
};
let node = &self.nodes[at];
let head = subject.head(term);
for (test, next) in node.tests {
let matched = match test {
Test::Int(want) => subject.int(term) == Some(*want),
Test::App { head: want, arity } => {
head.is_some_and(|(have, count)| have == *want && count == *arity)
}
};
if !matched {
continue;
}
let mut deeper = left.clone();
if let Some((_, arity)) = head {
for index in (0..arity).rev() {
deeper.push(subject.arg(term, index));
}
}
let depth = bindings.len();
if let Some(rule) = self.run(subject, *next as usize, deeper, bindings) {
return Some(rule);
}
bindings.truncate(depth);
}
let (_, next) = node.wildcard.as_ref()?;
let depth = bindings.len();
bindings.push(term);
if let Some(rule) = self.run(subject, *next as usize, left, bindings) {
return Some(rule);
}
bindings.truncate(depth);
None
}
fn accept<S: Subject>(&self, subject: &S, at: usize, bindings: &[S::Node]) -> Option<usize> {
let rule = self.nodes[at].accept? as usize;
if let Some(guard) = self.rules[rule].guard {
let values: Vec<Option<i128>> =
bindings.iter().map(|&node| subject.int(node)).collect();
if !guard(&values) {
return None;
}
}
Some(rule)
}
}
#[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 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_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);
}
}