use crate::ops::{BinaryOp, UnaryOp};
use crate::{Expr, ExprNode};
use radiate_utils::AnyValue;
impl Expr {
pub fn compile(self) -> Expr {
let name = self.name;
let kind = compile_kind(self.node);
let id = self.id;
Expr {
name,
id,
node: kind,
}
}
}
fn compile_kind(kind: ExprNode) -> ExprNode {
match kind {
ExprNode::Literal(_) | ExprNode::Selector(_) | ExprNode::Schedule(_) => kind,
ExprNode::Unary { child: u, op } => {
let child = u;
let child = child.compile();
match op {
UnaryOp::Affine { scale, bias } => fuse_affine(child, scale, bias).node,
other_op => ExprNode::Unary {
child: Box::new(child),
op: other_op,
},
}
}
ExprNode::Trinary {
first,
second,
third,
op,
} => ExprNode::Trinary {
first: Box::new((*first).compile()),
second: Box::new((*second).compile()),
third: Box::new((*third).compile()),
op,
},
ExprNode::Binary {
lhs: lhs_box,
rhs: rhs_box,
op,
} => {
let lhs = (*lhs_box).compile();
let rhs = (*rhs_box).compile();
reduce_binary(lhs, rhs, op).node
}
ExprNode::Reduce { mut child, rollup } => {
let old_child = std::mem::replace(
&mut child,
Box::new(Expr::new(ExprNode::Literal(AnyValue::Null))),
);
let new_child = old_child.compile();
ExprNode::Reduce {
child: Box::new(new_child),
rollup,
}
}
ExprNode::Rolling { mut child, buffer } => {
let old_child = std::mem::replace(
&mut child,
Box::new(Expr::new(ExprNode::Literal(AnyValue::Null))),
);
let new_child = old_child.compile();
ExprNode::Rolling {
child: Box::new(new_child),
buffer,
}
}
}
}
fn reduce_binary(lhs: Expr, rhs: Expr, op: BinaryOp) -> Expr {
if let (ExprNode::Literal(l), ExprNode::Literal(r)) = (&lhs.node, &rhs.node)
&& let Some(folded) = fold_literals(l, r, op)
{
return Expr::new(ExprNode::Literal(folded));
}
match op {
BinaryOp::Add => {
if let ExprNode::Literal(v) = &rhs.node
&& let Some(k) = v.extract::<f32>()
{
return fuse_affine(lhs, 1.0, k);
}
if let ExprNode::Literal(v) = &lhs.node
&& let Some(k) = v.extract::<f32>()
{
return fuse_affine(rhs, 1.0, k);
}
}
BinaryOp::Sub => {
if let ExprNode::Literal(v) = &rhs.node
&& let Some(k) = v.extract::<f32>()
{
return fuse_affine(lhs, 1.0, -k);
}
if let ExprNode::Literal(v) = &lhs.node
&& let Some(k) = v.extract::<f32>()
{
return fuse_affine(rhs, -1.0, k);
}
}
BinaryOp::Mul => {
if let ExprNode::Literal(v) = &rhs.node
&& let Some(s) = v.extract::<f32>()
{
return fuse_affine(lhs, s, 0.0);
}
if let ExprNode::Literal(v) = &lhs.node
&& let Some(s) = v.extract::<f32>()
{
return fuse_affine(rhs, s, 0.0);
}
}
BinaryOp::Div => {
if let ExprNode::Literal(v) = &rhs.node
&& let Some(d) = v.extract::<f32>()
&& d != 0.0
&& d.is_finite()
{
return fuse_affine(lhs, 1.0 / d, 0.0);
}
}
_ => {}
}
Expr::new(ExprNode::Binary {
lhs: Box::new(lhs),
rhs: Box::new(rhs),
op,
})
}
fn fold_literals(
l: &AnyValue<'static>,
r: &AnyValue<'static>,
op: BinaryOp,
) -> Option<AnyValue<'static>> {
let a = l.extract::<f32>()?;
let b = r.extract::<f32>()?;
let result = match op {
BinaryOp::Add => a + b,
BinaryOp::Sub => a - b,
BinaryOp::Mul => a * b,
BinaryOp::Div if b != 0.0 => a / b,
_ => return None,
};
if result.is_finite() {
Some(AnyValue::Float32(result))
} else {
None
}
}
fn fuse_affine(child: Expr, scale: f32, bias: f32) -> Expr {
if let ExprNode::Unary { child: inner, op } = child.node {
if matches!(op, UnaryOp::Affine { .. }) {
let UnaryOp::Affine {
scale: s2,
bias: b2,
} = op
else {
unreachable!()
};
return Expr::new(ExprNode::Unary {
child: inner,
op: UnaryOp::Affine {
scale: scale * s2,
bias: scale * b2 + bias,
},
});
}
return Expr::new(ExprNode::Unary {
child: Box::new(Expr::new(ExprNode::Unary { child: inner, op })),
op: UnaryOp::Affine { scale, bias },
});
}
Expr::new(ExprNode::Unary {
child: Box::new(child),
op: UnaryOp::Affine { scale, bias },
})
}