#![allow(clippy::single_call_fn)]
use std::collections::HashMap;
use std::sync::Arc;
use crate::dag::builder::DagBuilder;
use crate::dag::node::DagNodeId;
use crate::dag::symbol::{FnId, SymbolKind};
use crate::heuristic::rule_registry::{RuleFn, RuleRegistry};
pub type EvalFn1 = extern "C" fn(f64) -> f64;
pub type EvalFn2 = extern "C" fn(f64, f64) -> f64;
pub type EvalFn3 = extern "C" fn(f64, f64, f64) -> f64;
#[derive(Clone, Copy)]
pub enum EvalFn {
Arity1(EvalFn1),
Arity2(EvalFn2),
Arity3(EvalFn3),
}
impl EvalFn {
#[must_use]
pub const fn arity(self) -> u8 {
match self {
Self::Arity1(_) => 1,
Self::Arity2(_) => 2,
Self::Arity3(_) => 3,
}
}
#[must_use]
pub fn call(self, args: &[f64]) -> f64 {
debug_assert_eq!(args.len(), self.arity() as usize, "EvalFn arity mismatch");
match self {
Self::Arity1(f) => f(args[0]),
Self::Arity2(f) => f(args[0], args[1]),
Self::Arity3(f) => f(args[0], args[1], args[2]),
}
}
}
pub type SimplifyRuleArc =
Arc<dyn Fn(&mut DagBuilder, SymbolKind, &[DagNodeId]) -> Option<DagNodeId> + Send + Sync>;
pub type EGraphRuleArc =
Arc<dyn Fn(&mut DagBuilder, &SymbolKind, &[DagNodeId]) -> Option<DagNodeId> + Send + Sync>;
pub struct SimplifyRule {
pub name: String,
pub priority: i32,
pub rule: SimplifyRuleArc,
}
impl Clone for SimplifyRule {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
priority: self.priority,
rule: Arc::clone(&self.rule),
}
}
}
pub struct EGraphRule {
pub after_builtins: bool,
pub rule: EGraphRuleArc,
}
impl Clone for EGraphRule {
fn clone(&self) -> Self {
Self {
after_builtins: self.after_builtins,
rule: Arc::clone(&self.rule),
}
}
}
pub struct CustomOpDescriptor {
pub fn_id: FnId,
pub name: String,
pub eval_fn: EvalFn,
pub vectorizable: bool,
pub simplify_rules: Vec<SimplifyRule>,
pub egraph_rules: Vec<EGraphRule>,
pub cost: f64,
}
impl Clone for CustomOpDescriptor {
fn clone(&self) -> Self {
Self {
fn_id: self.fn_id,
name: self.name.clone(),
eval_fn: self.eval_fn,
vectorizable: self.vectorizable,
simplify_rules: self.simplify_rules.clone(),
egraph_rules: self.egraph_rules.clone(),
cost: self.cost,
}
}
}
impl CustomOpDescriptor {
#[must_use]
pub fn builder(
fn_id: impl Into<FnId>,
name: impl Into<String>,
eval_fn: EvalFn,
) -> CustomOpDescriptorBuilder {
CustomOpDescriptorBuilder::new(fn_id.into(), name.into(), eval_fn)
}
#[must_use]
pub const fn arity(&self) -> u8 {
self.eval_fn.arity()
}
}
pub struct CustomOpDescriptorBuilder {
fn_id: FnId,
name: String,
eval_fn: EvalFn,
vectorizable: bool,
simplify_rules: Vec<SimplifyRule>,
egraph_rules: Vec<EGraphRule>,
cost: f64,
}
impl CustomOpDescriptorBuilder {
#[must_use]
pub const fn new(fn_id: FnId, name: String, eval_fn: EvalFn) -> Self {
Self {
fn_id,
name,
eval_fn,
vectorizable: false,
simplify_rules: Vec::new(),
egraph_rules: Vec::new(),
cost: 2.0,
}
}
#[must_use]
pub const fn vectorizable(mut self) -> Self {
self.vectorizable = true;
self
}
pub fn simplify_rule<F>(mut self, name: impl Into<String>, priority: i32, rule: F) -> Self
where
F: Fn(&mut DagBuilder, SymbolKind, &[DagNodeId]) -> Option<DagNodeId>
+ Send
+ Sync
+ 'static,
{
self.simplify_rules.push(SimplifyRule {
name: name.into(),
priority,
rule: Arc::new(rule),
});
self
}
pub fn egraph_rule<F>(mut self, after_builtins: bool, rule: F) -> Self
where
F: Fn(&mut DagBuilder, &SymbolKind, &[DagNodeId]) -> Option<DagNodeId>
+ Send
+ Sync
+ 'static,
{
self.egraph_rules.push(EGraphRule {
after_builtins,
rule: Arc::new(rule),
});
self
}
#[must_use]
pub const fn cost(mut self, c: f64) -> Self {
self.cost = c;
self
}
#[must_use]
pub fn build(self) -> CustomOpDescriptor {
CustomOpDescriptor {
fn_id: self.fn_id,
name: self.name,
eval_fn: self.eval_fn,
vectorizable: self.vectorizable,
simplify_rules: self.simplify_rules,
egraph_rules: self.egraph_rules,
cost: self.cost,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CustomOpError {
DuplicateFnId(FnId),
DuplicateName(String),
}
impl std::fmt::Display for CustomOpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DuplicateFnId(id) => write!(f, "custom op fn_id {id:?} already registered"),
Self::DuplicateName(name) => {
write!(f, "custom op name {name:?} already registered")
}
}
}
}
impl std::error::Error for CustomOpError {}
pub struct CustomOpRegistry {
ops: HashMap<FnId, CustomOpDescriptor>,
name_to_id: HashMap<String, FnId>,
}
impl Default for CustomOpRegistry {
fn default() -> Self {
Self::new()
}
}
impl CustomOpRegistry {
#[must_use]
pub fn new() -> Self {
Self {
ops: HashMap::new(),
name_to_id: HashMap::new(),
}
}
pub fn register(&mut self, desc: CustomOpDescriptor) -> Result<(), CustomOpError> {
if self.ops.contains_key(&desc.fn_id) {
return Err(CustomOpError::DuplicateFnId(desc.fn_id));
}
if self.name_to_id.contains_key(&desc.name) {
return Err(CustomOpError::DuplicateName(desc.name));
}
self.name_to_id.insert(desc.name.clone(), desc.fn_id);
self.ops.insert(desc.fn_id, desc);
Ok(())
}
#[must_use]
pub fn get(&self, fn_id: FnId) -> Option<&CustomOpDescriptor> {
self.ops.get(&fn_id)
}
#[must_use]
pub fn get_by_name(&self, name: &str) -> Option<&CustomOpDescriptor> {
self.name_to_id.get(name).and_then(|id| self.ops.get(id))
}
pub fn get_mut(&mut self, fn_id: FnId) -> Option<&mut CustomOpDescriptor> {
self.ops.get_mut(&fn_id)
}
#[must_use]
pub fn is_vectorizable(&self, fn_id: FnId) -> bool {
self.ops.get(&fn_id).is_some_and(|d| d.vectorizable)
}
pub fn ops_iter(&self) -> impl Iterator<Item = &CustomOpDescriptor> {
self.ops.values()
}
pub fn register_with_builder(&self, builder: &mut DagBuilder) {
for desc in self.ops.values() {
let _id = builder.intern_function(&desc.name);
}
}
#[cfg(feature = "cranelift-jit")]
pub fn apply_to_jit(
self: &std::sync::Arc<Self>,
compiler: &mut crate::jit::compiler::JitCompiler,
) {
compiler.set_custom_op_registry(std::sync::Arc::clone(self));
}
#[must_use]
pub fn build_rule_registry(&self) -> RuleRegistry {
let mut registry = RuleRegistry::new();
for desc in self.ops.values() {
for rule in &desc.simplify_rules {
let arc = Arc::clone(&rule.rule);
let trampoline: RuleFn = Box::new(move |b, k, c| arc(b, k, c));
registry.register_named(&rule.name, trampoline, rule.priority, None);
}
}
registry
}
pub fn apply_to_egraph(&self, egraph: &mut crate::egraph::egraph::EGraph<'_>) {
for desc in self.ops.values() {
for rule in &desc.egraph_rules {
let arc = Arc::clone(&rule.rule);
if rule.after_builtins {
egraph.add_rule_after_builtins(move |b, k, c| arc(b, k, c));
} else {
egraph.add_rule(move |b, k, c| arc(b, k, c));
}
}
}
}
}