falcon-raptor 0.5.2

Higher-level semantics over Falcon IL
use crate::ir::*;
use falcon::il;
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub enum Operation<V: Value> {
    Assign {
        dst: Variable,
        src: Expression<V>,
    },
    Store {
        index: Expression<V>,
        src: Expression<V>,
    },
    Load {
        dst: Variable,
        index: Expression<V>,
    },
    Branch {
        target: Expression<V>,
    },
    Call(Call<V>),
    Intrinsic(il::Intrinsic),
    Return(Option<Expression<V>>),
    Nop(Option<Box<Operation<V>>>),
}

impl<V: Value> Operation<V> {
    pub fn from_il(operation: &il::Operation) -> Operation<Constant> {
        match operation {
            il::Operation::Assign { dst, src } => Operation::Assign {
                dst: dst.clone().into(),
                src: Expression::from_il(src),
            },
            il::Operation::Store { index, src } => Operation::Store {
                index: Expression::from_il(index),
                src: Expression::from_il(src),
            },
            il::Operation::Load { dst, index } => Operation::Load {
                dst: dst.clone().into(),
                index: Expression::from_il(index),
            },
            il::Operation::Branch { target } => Operation::Branch {
                target: Expression::from_il(target),
            },
            il::Operation::Intrinsic { intrinsic } => Operation::Intrinsic(intrinsic.clone()),
            il::Operation::Nop { placeholder } => Operation::Nop(
                placeholder
                    .as_ref()
                    .map(|operation| Box::new(Operation::<Constant>::from_il(operation))),
            ),
        }
    }

    pub fn is_assign(&self) -> bool {
        matches!(self, Operation::Call { .. })
    }

    pub fn is_load(&self) -> bool {
        matches!(self, Operation::Load { .. })
    }

    pub fn is_return(&self) -> bool {
        matches!(self, Operation::Return(_))
    }

    pub fn is_nop(&self) -> bool {
        matches!(self, Operation::Nop(_))
    }

    pub fn is_branch(&self) -> bool {
        matches!(self, Operation::Branch { .. })
    }

    pub fn is_call(&self) -> bool {
        matches!(self, Operation::Call(_))
    }

    pub fn src(&self) -> Option<&Expression<V>> {
        match self {
            Operation::Assign { src, .. } | Operation::Store { src, .. } => Some(src),
            _ => None,
        }
    }

    pub fn dst(&self) -> Option<&Variable> {
        match self {
            Operation::Assign { dst, .. } | Operation::Load { dst, .. } => Some(dst),
            _ => None,
        }
    }

    pub fn index(&self) -> Option<&Expression<V>> {
        match self {
            Operation::Store { index, .. } | Operation::Load { index, .. } => Some(index),
            _ => None,
        }
    }

    pub fn target(&self) -> Option<&Expression<V>> {
        match self {
            Operation::Branch { target } => Some(target),
            Operation::Call(call) => call.target().expression(),
            _ => None,
        }
    }

    pub fn call(&self) -> Option<&Call<V>> {
        match self {
            Operation::Call(call) => Some(call),
            _ => None,
        }
    }

    pub fn result(&self) -> Option<&Expression<V>> {
        match self {
            Operation::Return(result) => result.as_ref(),
            _ => None,
        }
    }

    pub fn expressions(&self) -> Vec<&Expression<V>> {
        match self {
            Operation::Assign { src, .. } => vec![src],
            Operation::Store { index, src } => vec![index, src],
            Operation::Load { index, .. } => vec![index],
            Operation::Branch { target } => vec![target],
            Operation::Call(call) => call
                .target()
                .expression()
                .map(|e| vec![e])
                .unwrap_or_default(),
            Operation::Intrinsic(_) | Operation::Return(_) | Operation::Nop(_) => Vec::new(),
        }
    }

    pub fn expressions_mut(&mut self) -> Vec<&mut Expression<V>> {
        match self {
            Operation::Assign { src, .. } => vec![src],
            Operation::Store { index, src } => vec![index, src],
            Operation::Load { index, .. } => vec![index],
            Operation::Branch { target } => vec![target],
            Operation::Call(call) => call
                .target_mut()
                .expression_mut()
                .map(|e| vec![e])
                .unwrap_or_default(),
            Operation::Intrinsic(_) | Operation::Return(_) | Operation::Nop(_) => Vec::new(),
        }
    }

    pub fn variables_written(&self) -> Option<Vec<&Variable>> {
        match self {
            Operation::Assign { dst, .. } | Operation::Load { dst, .. } => Some(vec![dst]),
            Operation::Call(call) => call.variables_written().map(|vw| vw.iter().collect()),
            Operation::Branch { .. } | Operation::Intrinsic(_) => None,
            Operation::Store { .. } | Operation::Return(_) | Operation::Nop(_) => Some(Vec::new()),
        }
    }

    pub fn variables_read(&self) -> Option<Vec<&Variable>> {
        match self {
            Operation::Assign { src, .. } => Some(src.variables()),
            Operation::Store { index, src } => Some(
                index
                    .variables()
                    .into_iter()
                    .chain(src.variables().into_iter())
                    .collect(),
            ),
            Operation::Load { index, .. } => Some(index.variables()),
            Operation::Call(call) => call.variables_read(),
            Operation::Branch { .. } | Operation::Intrinsic(_) => None,
            Operation::Return(result) => result.as_ref().map(|e| e.variables()),
            Operation::Nop(_) => Some(Vec::new()),
        }
    }

    pub fn variables(&self) -> Option<Vec<&Variable>> {
        let mut variables = self.variables_written()?;
        variables.append(&mut self.variables_read()?);
        variables.sort();
        variables.dedup();
        Some(variables)
    }
}

impl<V: Value> fmt::Display for Operation<V> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Operation::Assign { dst, src } => write!(f, "{} = {}", dst, src),
            Operation::Store { index, src } => write!(f, "[{}] = {}", index, src),
            Operation::Load { dst, index } => write!(f, "{} = [{}]", dst, index),
            Operation::Branch { target } => write!(f, "branch {}", target),
            Operation::Call(call) => call.fmt(f),
            Operation::Intrinsic(intrinsic) => intrinsic.fmt(f),
            Operation::Return(result) => match result {
                Some(result) => write!(f, "return {}", result),
                None => write!(f, "return ???"),
            },
            Operation::Nop(_) => write!(f, "nop"),
        }
    }
}