use std::fmt;
use crate::{
ir::{ops::SsaOp, variable::SsaVarId},
target::Target,
};
#[derive(Debug, Clone)]
pub struct SsaInstruction<T: Target> {
original: T::OriginalInstruction,
op: SsaOp<T>,
result_type: Option<T::Type>,
}
impl<T: Target> SsaInstruction<T> {
#[must_use]
pub fn new(original: T::OriginalInstruction, op: SsaOp<T>) -> Self {
Self {
original,
op,
result_type: None,
}
}
#[must_use]
pub fn synthetic(op: SsaOp<T>) -> Self {
Self {
original: T::synthetic_instruction(),
op,
result_type: None,
}
}
#[must_use]
pub const fn original(&self) -> &T::OriginalInstruction {
&self.original
}
#[must_use]
pub const fn op(&self) -> &SsaOp<T> {
&self.op
}
pub fn op_mut(&mut self) -> &mut SsaOp<T> {
&mut self.op
}
pub fn set_op(&mut self, op: SsaOp<T>) {
self.op = op;
self.result_type = None;
}
#[must_use]
pub fn result_type(&self) -> Option<&T::Type> {
self.result_type.as_ref()
}
pub fn set_result_type(&mut self, ty: Option<T::Type>) {
self.result_type = ty;
}
#[must_use]
pub fn with_result_type(mut self, ty: T::Type) -> Self {
self.result_type = Some(ty);
self
}
#[must_use]
pub fn is_terminator(&self) -> bool {
self.op.is_terminator()
}
#[must_use]
pub fn may_throw(&self) -> bool {
self.op.may_throw()
}
#[must_use]
pub fn is_pure(&self) -> bool {
self.op.is_pure()
}
#[must_use]
pub fn uses(&self) -> Vec<SsaVarId> {
self.op.uses()
}
#[must_use]
pub fn def(&self) -> Option<SsaVarId> {
self.op.dest()
}
#[must_use]
pub fn has_def(&self) -> bool {
self.op.dest().is_some()
}
#[must_use]
pub fn has_no_uses(&self) -> bool {
self.op.uses().is_empty()
}
#[must_use]
pub fn all_variables(&self) -> Vec<SsaVarId> {
let mut vars = self.op.uses();
if let Some(def) = self.op.dest() {
vars.push(def);
}
vars
}
}
impl<T: Target> SsaInstruction<T> {
#[must_use]
pub fn mnemonic(&self) -> &'static str {
T::instruction_mnemonic(&self.original)
}
#[must_use]
pub fn rva(&self) -> u64 {
T::instruction_rva(&self.original)
}
}
impl<T: Target> fmt::Display for SsaInstruction<T>
where
SsaOp<T>: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.op)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ir::{ops::SsaOp, value::ConstValue},
testing::{MockTarget, MockType},
};
fn var(index: usize) -> SsaVarId {
SsaVarId::from_index(index)
}
#[test]
fn instruction_accessors_reflect_operation_shape() {
let instr = SsaInstruction::<MockTarget>::synthetic(SsaOp::Add {
dest: var(2),
left: var(0),
right: var(1),
flags: None,
});
assert_eq!(instr.original(), &());
assert!(matches!(instr.op(), SsaOp::Add { .. }));
assert!(instr.is_pure());
assert!(!instr.is_terminator());
assert!(!instr.may_throw());
assert_eq!(instr.uses(), vec![var(0), var(1)]);
assert_eq!(instr.def(), Some(var(2)));
assert!(instr.has_def());
assert!(!instr.has_no_uses());
assert_eq!(instr.all_variables(), vec![var(0), var(1), var(2)]);
assert_eq!(instr.mnemonic(), "<mock>");
assert_eq!(instr.rva(), 0);
}
#[test]
fn mutating_op_clears_result_type_until_reset() {
let mut instr = SsaInstruction::<MockTarget>::synthetic(SsaOp::Const {
dest: var(0),
value: ConstValue::I32(1),
})
.with_result_type(MockType::I32);
assert_eq!(instr.result_type(), Some(&MockType::I32));
if let SsaOp::Const { value, .. } = instr.op_mut() {
*value = ConstValue::I32(2);
}
assert_eq!(instr.result_type(), Some(&MockType::I32));
instr.set_op(SsaOp::Return {
value: Some(var(0)),
});
assert_eq!(instr.result_type(), None);
assert!(instr.is_terminator());
assert_eq!(instr.def(), None);
assert!(!instr.has_def());
assert_eq!(instr.uses(), vec![var(0)]);
instr.set_result_type(Some(MockType::Unknown));
assert_eq!(instr.result_type(), Some(&MockType::Unknown));
instr.set_result_type(None);
assert_eq!(instr.result_type(), None);
}
#[test]
fn no_operand_instructions_report_no_uses() {
let instr = SsaInstruction::<MockTarget>::new((), SsaOp::Return { value: None });
assert!(instr.has_no_uses());
assert_eq!(instr.all_variables(), Vec::<SsaVarId>::new());
}
}