use std::fmt;
use crate::{
analysis::ssa::{SsaOp, SsaVarId},
assembly::{FlowType, Instruction, InstructionCategory, Operand, StackBehavior},
};
#[derive(Debug, Clone)]
pub struct SsaInstruction {
original: Instruction,
op: SsaOp,
}
impl SsaInstruction {
#[must_use]
pub fn new(original: Instruction, op: SsaOp) -> Self {
Self { original, op }
}
#[must_use]
pub fn synthetic(op: SsaOp) -> Self {
let dummy = Instruction {
rva: 0,
offset: 0,
size: 0,
opcode: 0,
prefix: 0,
mnemonic: "synthetic",
category: InstructionCategory::Misc,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops: 0,
pushes: 0,
net_effect: 0,
},
branch_targets: vec![],
};
Self {
original: dummy,
op,
}
}
#[must_use]
pub const fn original(&self) -> &Instruction {
&self.original
}
#[must_use]
pub const fn op(&self) -> &SsaOp {
&self.op
}
pub fn op_mut(&mut self) -> &mut SsaOp {
&mut self.op
}
pub fn set_op(&mut self, op: SsaOp) {
self.op = op;
}
#[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 mnemonic(&self) -> &'static str {
self.original.mnemonic
}
#[must_use]
pub const fn rva(&self) -> u64 {
self.original.rva
}
#[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 fmt::Display for SsaInstruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.op)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_instruction(mnemonic: &'static str, pops: u8, pushes: u8) -> Instruction {
Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode: 0x58, prefix: 0,
mnemonic,
category: InstructionCategory::Arithmetic,
flow_type: FlowType::Sequential,
operand: Operand::None,
stack_behavior: StackBehavior {
pops,
pushes,
net_effect: pushes as i8 - pops as i8,
},
branch_targets: vec![],
}
}
#[test]
fn test_ssa_instruction_new() {
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let cil = make_test_instruction("add", 2, 1);
let op = SsaOp::Add {
dest: v2,
left: v0,
right: v1,
};
let instr = SsaInstruction::new(cil, op);
assert_eq!(instr.uses().len(), 2);
assert_eq!(instr.def(), Some(v2));
assert!(instr.has_def());
assert!(!instr.has_no_uses());
}
#[test]
fn test_ssa_instruction_uses() {
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let cil = make_test_instruction("add", 2, 1);
let op = SsaOp::Add {
dest: v2,
left: v0,
right: v1,
};
let instr = SsaInstruction::new(cil, op);
let uses = instr.uses();
assert_eq!(uses.len(), 2);
assert!(uses.contains(&v0));
assert!(uses.contains(&v1));
}
#[test]
fn test_ssa_instruction_all_variables() {
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let cil = make_test_instruction("add", 2, 1);
let op = SsaOp::Add {
dest: v2,
left: v0,
right: v1,
};
let instr = SsaInstruction::new(cil, op);
let vars = instr.all_variables();
assert_eq!(vars.len(), 3);
assert!(vars.contains(&v0));
assert!(vars.contains(&v1));
assert!(vars.contains(&v2));
}
#[test]
fn test_ssa_instruction_all_variables_no_def() {
let v = SsaVarId::new();
let cil = make_test_instruction("pop", 1, 0);
let op = SsaOp::Pop { value: v };
let instr = SsaInstruction::new(cil, op);
let vars = instr.all_variables();
assert_eq!(vars.len(), 1);
assert!(vars.contains(&v));
}
#[test]
fn test_ssa_instruction_display() {
let v0 = SsaVarId::from_index(0);
let v1 = SsaVarId::from_index(1);
let v2 = SsaVarId::from_index(2);
let cil = make_test_instruction("add", 2, 1);
let op = SsaOp::Add {
dest: v2,
left: v0,
right: v1,
};
let instr = SsaInstruction::new(cil, op);
assert_eq!(format!("{instr}"), "v2 = add v0, v1");
}
#[test]
fn test_ssa_instruction_display_no_def() {
let v = SsaVarId::from_index(5);
let cil = make_test_instruction("pop", 1, 0);
let op = SsaOp::Pop { value: v };
let instr = SsaInstruction::new(cil, op);
assert_eq!(format!("{instr}"), "pop v5");
}
#[test]
fn test_ssa_instruction_display_const() {
use crate::analysis::ssa::ConstValue;
let v = SsaVarId::from_index(3);
let cil = make_test_instruction("ldc.i4", 0, 1);
let op = SsaOp::Const {
dest: v,
value: ConstValue::I32(42),
};
let instr = SsaInstruction::new(cil, op);
assert_eq!(format!("{instr}"), "v3 = 42");
}
#[test]
fn test_ssa_instruction_synthetic() {
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let op = SsaOp::Add {
dest: v2,
left: v0,
right: v1,
};
let instr = SsaInstruction::synthetic(op);
assert_eq!(instr.uses().len(), 2);
assert_eq!(instr.def(), Some(v2));
assert_eq!(instr.mnemonic(), "synthetic");
}
#[test]
fn test_ssa_instruction_set_op() {
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let v3 = SsaVarId::new();
let cil = make_test_instruction("add", 2, 1);
let op = SsaOp::Add {
dest: v2,
left: v0,
right: v1,
};
let mut instr = SsaInstruction::new(cil, op);
let new_op = SsaOp::Sub {
dest: v3,
left: v0,
right: v1,
};
instr.set_op(new_op);
assert_eq!(instr.def(), Some(v3));
assert!(matches!(instr.op(), SsaOp::Sub { .. }));
}
}