#![allow(clippy::cast_possible_truncation)]
use crate::{
analysis::ssa::{
ops::{CmpKind, SsaOp},
types::{FieldRef, MethodRef, SigRef, SsaType, TypeRef},
value::ConstValue,
SsaVarId,
},
assembly::{Immediate, Instruction, Operand},
metadata::{cilobject::CilObject, token::Token},
Error, Result,
};
pub fn decompose_instruction(
instr: &Instruction,
uses: &[SsaVarId],
def: Option<SsaVarId>,
successors: &[usize],
assembly: Option<&CilObject>,
) -> Result<SsaOp> {
if instr.prefix == 0xFE {
return decompose_fe_instruction(instr, uses, def);
}
decompose_standard_instruction(instr, uses, def, successors, assembly)
}
fn decompose_standard_instruction(
instr: &Instruction,
uses: &[SsaVarId],
def: Option<SsaVarId>,
successors: &[usize],
assembly: Option<&CilObject>,
) -> Result<SsaOp> {
let result = match instr.opcode {
0x14 => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::Null,
})
}
0x15 => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(-1),
})
}
0x16 => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(0),
})
}
0x17 => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(1),
})
}
0x18 => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(2),
})
}
0x19 => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(3),
})
}
0x1A => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(4),
})
}
0x1B => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(5),
})
}
0x1C => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(6),
})
}
0x1D => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(7),
})
}
0x1E => {
def.map(|dest| SsaOp::Const {
dest,
value: ConstValue::I32(8),
})
}
0x1F => {
def.and_then(|dest| {
extract_i32(&instr.operand).map(|v| SsaOp::Const {
dest,
value: ConstValue::I32(v),
})
})
}
0x20 => {
def.and_then(|dest| {
extract_i32(&instr.operand).map(|v| SsaOp::Const {
dest,
value: ConstValue::I32(v),
})
})
}
0x21 => {
def.and_then(|dest| {
extract_i64(&instr.operand).map(|v| SsaOp::Const {
dest,
value: ConstValue::I64(v),
})
})
}
0x22 => {
def.and_then(|dest| {
extract_f32(&instr.operand).map(|v| SsaOp::Const {
dest,
value: ConstValue::F32(v),
})
})
}
0x23 => {
def.and_then(|dest| {
extract_f64(&instr.operand).map(|v| SsaOp::Const {
dest,
value: ConstValue::F64(v),
})
})
}
0x25 => {
if let (Some(src), Some(dest)) = (uses.first(), def) {
Some(SsaOp::Copy { dest, src: *src })
} else {
None
}
}
0x26 => {
uses.first().map(|&value| SsaOp::Pop { value })
}
0x58 => binary_op(uses, def, |dest, left, right| SsaOp::Add {
dest,
left,
right,
}), 0x59 => binary_op(uses, def, |dest, left, right| SsaOp::Sub {
dest,
left,
right,
}), 0x5A => binary_op(uses, def, |dest, left, right| SsaOp::Mul {
dest,
left,
right,
}), 0x5B => binary_op(uses, def, |dest, left, right| SsaOp::Div {
dest,
left,
right,
unsigned: false,
}),
0x5C => binary_op(uses, def, |dest, left, right| SsaOp::Div {
dest,
left,
right,
unsigned: true,
}),
0x5D => binary_op(uses, def, |dest, left, right| SsaOp::Rem {
dest,
left,
right,
unsigned: false,
}),
0x5E => binary_op(uses, def, |dest, left, right| SsaOp::Rem {
dest,
left,
right,
unsigned: true,
}),
0x65 => unary_op(uses, def, |dest, operand| SsaOp::Neg { dest, operand }), 0x66 => unary_op(uses, def, |dest, operand| SsaOp::Not { dest, operand }),
0xD6 => binary_op(uses, def, |dest, left, right| SsaOp::AddOvf {
dest,
left,
right,
unsigned: false,
}),
0xD7 => binary_op(uses, def, |dest, left, right| SsaOp::AddOvf {
dest,
left,
right,
unsigned: true,
}),
0xD8 => binary_op(uses, def, |dest, left, right| SsaOp::MulOvf {
dest,
left,
right,
unsigned: false,
}),
0xD9 => binary_op(uses, def, |dest, left, right| SsaOp::MulOvf {
dest,
left,
right,
unsigned: true,
}),
0xDA => binary_op(uses, def, |dest, left, right| SsaOp::SubOvf {
dest,
left,
right,
unsigned: false,
}),
0xDB => binary_op(uses, def, |dest, left, right| SsaOp::SubOvf {
dest,
left,
right,
unsigned: true,
}),
0x5F => binary_op(uses, def, |dest, left, right| SsaOp::And {
dest,
left,
right,
}), 0x60 => binary_op(uses, def, |dest, left, right| SsaOp::Or {
dest,
left,
right,
}), 0x61 => binary_op(uses, def, |dest, left, right| SsaOp::Xor {
dest,
left,
right,
}), 0x62 => binary_op(uses, def, |dest, value, amount| SsaOp::Shl {
dest,
value,
amount,
}),
0x63 => binary_op(uses, def, |dest, value, amount| SsaOp::Shr {
dest,
value,
amount,
unsigned: false,
}),
0x64 => binary_op(uses, def, |dest, value, amount| SsaOp::Shr {
dest,
value,
amount,
unsigned: true,
}),
0x67 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I8,
overflow_check: false,
unsigned: false,
}),
0x68 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I16,
overflow_check: false,
unsigned: false,
}),
0x69 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I32,
overflow_check: false,
unsigned: false,
}),
0x6A => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I64,
overflow_check: false,
unsigned: false,
}),
0x6B => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::F32,
overflow_check: false,
unsigned: false,
}),
0x6C => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::F64,
overflow_check: false,
unsigned: false,
}),
0xD1 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U16,
overflow_check: false,
unsigned: true,
}),
0xD2 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U8,
overflow_check: false,
unsigned: true,
}),
0x6D => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U32,
overflow_check: false,
unsigned: true,
}),
0x6E => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U64,
overflow_check: false,
unsigned: true,
}),
0xD3 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::NativeInt,
overflow_check: false,
unsigned: false,
}),
0xE0 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::NativeUInt,
overflow_check: false,
unsigned: true,
}),
0x76 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::F64,
overflow_check: false,
unsigned: true,
}),
0xB3 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I8,
overflow_check: true,
unsigned: false,
}),
0x82 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I8,
overflow_check: true,
unsigned: true,
}),
0xB5 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I16,
overflow_check: true,
unsigned: false,
}),
0x83 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I16,
overflow_check: true,
unsigned: true,
}),
0xB7 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I32,
overflow_check: true,
unsigned: false,
}),
0x84 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I32,
overflow_check: true,
unsigned: true,
}),
0xB9 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I64,
overflow_check: true,
unsigned: false,
}),
0x85 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::I64,
overflow_check: true,
unsigned: true,
}),
0xD4 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::NativeInt,
overflow_check: true,
unsigned: false,
}),
0x8A => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::NativeInt,
overflow_check: true,
unsigned: true,
}),
0xB4 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U8,
overflow_check: true,
unsigned: false,
}),
0x86 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U8,
overflow_check: true,
unsigned: true,
}),
0xB6 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U16,
overflow_check: true,
unsigned: false,
}),
0x87 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U16,
overflow_check: true,
unsigned: true,
}),
0xB8 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U32,
overflow_check: true,
unsigned: false,
}),
0x88 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U32,
overflow_check: true,
unsigned: true,
}),
0xBA => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U64,
overflow_check: true,
unsigned: false,
}),
0x89 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::U64,
overflow_check: true,
unsigned: true,
}),
0xD5 => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::NativeUInt,
overflow_check: true,
unsigned: false,
}),
0x8B => unary_op(uses, def, |dest, operand| SsaOp::Conv {
dest,
operand,
target: SsaType::NativeUInt,
overflow_check: true,
unsigned: true,
}),
0x2A => Some(SsaOp::Return {
value: uses.first().copied(),
}),
0x2B | 0x38 => {
successors.first().map(|&target| SsaOp::Jump { target })
}
0x2C | 0x39 => {
uses.first().and_then(|&condition| {
if successors.len() >= 2 {
Some(SsaOp::Branch {
condition,
true_target: successors[1], false_target: successors[0], })
} else {
None
}
})
}
0x2D | 0x3A => {
uses.first().and_then(|&condition| {
if successors.len() >= 2 {
Some(SsaOp::Branch {
condition,
true_target: successors[0], false_target: successors[1], })
} else {
None
}
})
}
0x2E | 0x3B => {
comparison_branch(uses, successors, CmpKind::Eq, false)
}
0x2F | 0x3C => {
comparison_branch(uses, successors, CmpKind::Ge, false)
}
0x30 | 0x3D => {
comparison_branch(uses, successors, CmpKind::Gt, false)
}
0x31 | 0x3E => {
comparison_branch(uses, successors, CmpKind::Le, false)
}
0x32 | 0x3F => {
comparison_branch(uses, successors, CmpKind::Lt, false)
}
0x33 | 0x40 => {
comparison_branch(uses, successors, CmpKind::Ne, true)
}
0x34 | 0x41 => {
comparison_branch(uses, successors, CmpKind::Ge, true)
}
0x35 | 0x42 => {
comparison_branch(uses, successors, CmpKind::Gt, true)
}
0x36 | 0x43 => {
comparison_branch(uses, successors, CmpKind::Le, true)
}
0x37 | 0x44 => {
comparison_branch(uses, successors, CmpKind::Lt, true)
}
0x45 => {
uses.first().and_then(|&value| {
if successors.len() >= 2 {
let default = *successors.last().unwrap_or(&0);
let targets: Vec<usize> = successors[..successors.len() - 1].to_vec();
Some(SsaOp::Switch {
value,
targets,
default,
})
} else {
None
}
})
}
0xDD => {
successors.first().map(|&target| SsaOp::Leave { target })
}
0xDE => {
successors.first().map(|&target| SsaOp::Leave { target })
}
0x02..=0x05 | 0x0E => {
Some(SsaOp::Nop)
}
0x06..=0x09 | 0x11 => {
Some(SsaOp::Nop)
}
0x0A..=0x0D | 0x13 => {
match (def, uses.first()) {
(Some(dest), Some(&src)) => Some(SsaOp::Copy { dest, src }),
_ => None,
}
}
0x0F => {
def.and_then(|dest| {
extract_u16(&instr.operand).map(|arg_index| SsaOp::LoadArgAddr { dest, arg_index })
})
}
0x10 => {
match (def, uses.first()) {
(Some(dest), Some(&src)) => Some(SsaOp::Copy { dest, src }),
_ => None,
}
}
0x12 => {
def.and_then(|dest| {
extract_u16(&instr.operand)
.map(|local_index| SsaOp::LoadLocalAddr { dest, local_index })
})
}
0x6F => {
call_op(instr, uses, def, true)
}
0x28 => {
call_op(instr, uses, def, false)
}
0x29 => {
if let Some(signature) = extract_signature_token(&instr.operand) {
let (fptr, args) = if let Some(&fptr) = uses.last() {
let args = uses[..uses.len() - 1].to_vec();
(fptr, args)
} else {
(SsaVarId::from_index(0), vec![])
};
Some(SsaOp::CallIndirect {
dest: def,
fptr,
signature,
args,
})
} else {
None
}
}
0x73 => {
if let Some(ctor) = extract_method_token(&instr.operand) {
def.map(|dest| SsaOp::NewObj {
dest,
ctor,
args: uses.to_vec(),
})
} else {
None
}
}
0x8D => {
if let (Some(elem_type), Some(&length), Some(dest)) =
(extract_type_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::NewArr {
dest,
elem_type,
length,
})
} else {
None
}
}
0x74 => {
if let (Some(target_type), Some(&object), Some(dest)) =
(extract_type_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::CastClass {
dest,
object,
target_type,
})
} else {
None
}
}
0x75 => {
if let (Some(target_type), Some(&object), Some(dest)) =
(extract_type_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::IsInst {
dest,
object,
target_type,
})
} else {
None
}
}
0x8C => {
if let (Some(value_type), Some(&value), Some(dest)) =
(extract_type_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::Box {
dest,
value,
value_type,
})
} else {
None
}
}
0x79 => {
if let (Some(value_type), Some(&object), Some(dest)) =
(extract_type_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::Unbox {
dest,
object,
value_type,
})
} else {
None
}
}
0xA5 => {
if let (Some(value_type), Some(&object), Some(dest)) =
(extract_type_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::UnboxAny {
dest,
object,
value_type,
})
} else {
None
}
}
0x7B => {
if let (Some(field), Some(&object), Some(dest)) =
(extract_field_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::LoadField {
dest,
object,
field,
})
} else {
None
}
}
0x7D => {
if let (Some(field), Some(object), Some(value)) = (
extract_field_token(&instr.operand),
uses.first(),
uses.get(1),
) {
Some(SsaOp::StoreField {
object: *object,
field,
value: *value,
})
} else {
None
}
}
0x7C => {
if let (Some(field), Some(&object), Some(dest)) =
(extract_field_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::LoadFieldAddr {
dest,
object,
field,
})
} else {
None
}
}
0x7E => {
if let (Some(field), Some(dest)) = (extract_field_token(&instr.operand), def) {
Some(SsaOp::LoadStaticField { dest, field })
} else {
None
}
}
0x80 => {
if let (Some(field), Some(&value)) = (extract_field_token(&instr.operand), uses.first())
{
Some(SsaOp::StoreStaticField { field, value })
} else {
None
}
}
0x7F => {
if let (Some(field), Some(dest)) = (extract_field_token(&instr.operand), def) {
Some(SsaOp::LoadStaticFieldAddr { dest, field })
} else {
None
}
}
0x8E => {
if let (Some(&array), Some(dest)) = (uses.first(), def) {
Some(SsaOp::ArrayLength { dest, array })
} else {
None
}
}
0x8F => {
if let (Some(&array), Some(&index), Some(dest), Some(elem_type)) = (
uses.first(),
uses.get(1),
def,
extract_type_token(&instr.operand),
) {
Some(SsaOp::LoadElementAddr {
dest,
array,
index,
elem_type,
})
} else {
None
}
}
0xA3 => {
ldelem_op(uses, def, &instr.operand, assembly)
}
0x90 => ldelem_typed(uses, def, SsaType::I8), 0x91 => ldelem_typed(uses, def, SsaType::U8), 0x92 => ldelem_typed(uses, def, SsaType::I16), 0x93 => ldelem_typed(uses, def, SsaType::U16), 0x94 => ldelem_typed(uses, def, SsaType::I32), 0x95 => ldelem_typed(uses, def, SsaType::U32), 0x96 => ldelem_typed(uses, def, SsaType::I64), 0x97 => ldelem_typed(uses, def, SsaType::NativeInt), 0x98 => ldelem_typed(uses, def, SsaType::F32), 0x99 => ldelem_typed(uses, def, SsaType::F64), 0x9A => ldelem_typed(uses, def, SsaType::Object),
0xA4 => {
stelem_op(uses, &instr.operand, assembly)
}
0x9C => stelem_typed(uses, SsaType::I8), 0x9D => stelem_typed(uses, SsaType::I16), 0x9E => stelem_typed(uses, SsaType::I32), 0x9F => stelem_typed(uses, SsaType::I64), 0xA0 => stelem_typed(uses, SsaType::F32), 0xA1 => stelem_typed(uses, SsaType::F64), 0xA2 => stelem_typed(uses, SsaType::Object), 0x9B => stelem_typed(uses, SsaType::NativeInt),
0x46 => ldind_typed(uses, def, SsaType::I8), 0x47 => ldind_typed(uses, def, SsaType::U8), 0x48 => ldind_typed(uses, def, SsaType::I16), 0x49 => ldind_typed(uses, def, SsaType::U16), 0x4A => ldind_typed(uses, def, SsaType::I32), 0x4B => ldind_typed(uses, def, SsaType::U32), 0x4C => ldind_typed(uses, def, SsaType::I64), 0x4D => ldind_typed(uses, def, SsaType::NativeInt), 0x4E => ldind_typed(uses, def, SsaType::F32), 0x4F => ldind_typed(uses, def, SsaType::F64), 0x50 => ldind_typed(uses, def, SsaType::Object),
0x52 => stind_typed(uses, SsaType::I8), 0x53 => stind_typed(uses, SsaType::I16), 0x54 => stind_typed(uses, SsaType::I32), 0x55 => stind_typed(uses, SsaType::I64), 0x56 => stind_typed(uses, SsaType::F32), 0x57 => stind_typed(uses, SsaType::F64), 0x51 => stind_typed(uses, SsaType::Object), 0xDF => stind_typed(uses, SsaType::NativeInt),
0x71 => {
if let (Some(&src_addr), Some(dest)) = (uses.first(), def) {
let value_type = extract_type_token(&instr.operand)
.unwrap_or_else(|| TypeRef::new(Token::new(0)));
Some(SsaOp::LoadObj {
dest,
src_addr,
value_type,
})
} else {
None
}
}
0x81 => {
if let (Some(&dest_addr), Some(&value)) = (uses.first(), uses.get(1)) {
let value_type = extract_type_token(&instr.operand)
.unwrap_or_else(|| TypeRef::new(Token::new(0)));
Some(SsaOp::StoreObj {
dest_addr,
value,
value_type,
})
} else {
None
}
}
0x70 => {
if let (Some(&dest_addr), Some(&src_addr)) = (uses.first(), uses.get(1)) {
let value_type = extract_type_token(&instr.operand)
.unwrap_or_else(|| TypeRef::new(Token::new(0)));
Some(SsaOp::CopyObj {
dest_addr,
src_addr,
value_type,
})
} else {
None
}
}
0x7A => {
uses.first().map(|&exception| SsaOp::Throw { exception })
}
0xDC => Some(SsaOp::EndFinally),
0x00 => Some(SsaOp::Nop), 0x01 => Some(SsaOp::Break),
0x72 => {
def.and_then(|dest| {
extract_string_token(&instr.operand).map(|idx| SsaOp::Const {
dest,
value: ConstValue::String(idx),
})
})
}
0xD0 => {
def.and_then(|dest| {
extract_type_token(&instr.operand).map(|token| SsaOp::LoadToken { dest, token })
})
}
0xC3 => {
if let (Some(&operand), Some(dest)) = (uses.first(), def) {
Some(SsaOp::Ckfinite { dest, operand })
} else {
None
}
}
_ => {
return Err(Error::SsaError(format!(
"Unknown opcode 0x{:02X} ({}) at RVA 0x{:08X}",
instr.opcode, instr.mnemonic, instr.rva
)));
}
};
result.ok_or_else(|| {
Error::SsaError(format!(
"Failed to decompose instruction {} (0x{:02X}) at RVA 0x{:08X}: missing operands (uses={}, def={:?})",
instr.mnemonic, instr.opcode, instr.rva, uses.len(), def
))
})
}
fn decompose_fe_instruction(
instr: &Instruction,
uses: &[SsaVarId],
def: Option<SsaVarId>,
) -> Result<SsaOp> {
let result = match instr.opcode {
0x01 => binary_op(uses, def, |dest, left, right| SsaOp::Ceq {
dest,
left,
right,
}), 0x02 => binary_op(uses, def, |dest, left, right| SsaOp::Cgt {
dest,
left,
right,
unsigned: false,
}),
0x03 => binary_op(uses, def, |dest, left, right| SsaOp::Cgt {
dest,
left,
right,
unsigned: true,
}),
0x04 => binary_op(uses, def, |dest, left, right| SsaOp::Clt {
dest,
left,
right,
unsigned: false,
}),
0x05 => binary_op(uses, def, |dest, left, right| SsaOp::Clt {
dest,
left,
right,
unsigned: true,
}),
0x06 => {
if let (Some(method), Some(dest)) = (extract_method_token(&instr.operand), def) {
Some(SsaOp::LoadFunctionPtr { dest, method })
} else {
None
}
}
0x07 => {
if let (Some(method), Some(&object), Some(dest)) =
(extract_method_token(&instr.operand), uses.first(), def)
{
Some(SsaOp::LoadVirtFunctionPtr {
dest,
object,
method,
})
} else {
None
}
}
0x09 => {
Some(SsaOp::Nop)
}
0x0A => {
def.and_then(|dest| {
extract_u16(&instr.operand).map(|arg_index| SsaOp::LoadArgAddr { dest, arg_index })
})
}
0x0B => {
match (def, uses.first()) {
(Some(dest), Some(&src)) => Some(SsaOp::Copy { dest, src }),
_ => None,
}
}
0x0C => {
Some(SsaOp::Nop)
}
0x0D => {
def.and_then(|dest| {
extract_u16(&instr.operand)
.map(|local_index| SsaOp::LoadLocalAddr { dest, local_index })
})
}
0x0E => {
match (def, uses.first()) {
(Some(dest), Some(&src)) => Some(SsaOp::Copy { dest, src }),
_ => None,
}
}
0x15 => {
if let Some(&dest_addr) = uses.first() {
let value_type = extract_type_token(&instr.operand)
.unwrap_or_else(|| TypeRef::new(Token::new(0)));
Some(SsaOp::InitObj {
dest_addr,
value_type,
})
} else {
None
}
}
0x17 => {
if let (Some(&dest_addr), Some(&src_addr), Some(&size)) =
(uses.first(), uses.get(1), uses.get(2))
{
Some(SsaOp::CopyBlk {
dest_addr,
src_addr,
size,
})
} else {
None
}
}
0x18 => {
if let (Some(&dest_addr), Some(&value), Some(&size)) =
(uses.first(), uses.get(1), uses.get(2))
{
Some(SsaOp::InitBlk {
dest_addr,
value,
size,
})
} else {
None
}
}
0x1A => Some(SsaOp::Rethrow), 0x11 => {
uses.first().map(|&result| SsaOp::EndFilter { result })
}
0x0F => {
if let (Some(&size), Some(dest)) = (uses.first(), def) {
Some(SsaOp::LocalAlloc { dest, size })
} else {
None
}
}
0x1C => {
if let (Some(value_type), Some(dest)) = (extract_type_token(&instr.operand), def) {
Some(SsaOp::SizeOf { dest, value_type })
} else {
None
}
}
0x16 => extract_type_token(&instr.operand)
.map(|constraint_type| SsaOp::Constrained { constraint_type }),
_ => {
return Err(Error::SsaError(format!(
"Unknown FE-prefixed opcode 0xFE 0x{:02X} ({}) at RVA 0x{:08X}",
instr.opcode, instr.mnemonic, instr.rva
)));
}
};
result.ok_or_else(|| {
Error::SsaError(format!(
"Failed to decompose FE instruction {} (0xFE 0x{:02X}) at RVA 0x{:08X}: missing operands (uses={}, def={:?})",
instr.mnemonic, instr.opcode, instr.rva, uses.len(), def
))
})
}
fn binary_op<F>(uses: &[SsaVarId], def: Option<SsaVarId>, f: F) -> Option<SsaOp>
where
F: FnOnce(SsaVarId, SsaVarId, SsaVarId) -> SsaOp,
{
if let (Some(&left), Some(&right), Some(dest)) = (uses.first(), uses.get(1), def) {
Some(f(dest, left, right))
} else {
None
}
}
fn unary_op<F>(uses: &[SsaVarId], def: Option<SsaVarId>, f: F) -> Option<SsaOp>
where
F: FnOnce(SsaVarId, SsaVarId) -> SsaOp,
{
if let (Some(&operand), Some(dest)) = (uses.first(), def) {
Some(f(dest, operand))
} else {
None
}
}
fn comparison_branch(
uses: &[SsaVarId],
successors: &[usize],
cmp: CmpKind,
unsigned: bool,
) -> Option<SsaOp> {
if let (Some(&left), Some(&right)) = (uses.first(), uses.get(1)) {
if successors.len() >= 2 {
Some(SsaOp::BranchCmp {
left,
right,
cmp,
unsigned,
true_target: successors[0],
false_target: successors[1],
})
} else {
None
}
} else {
None
}
}
fn call_op(
instr: &Instruction,
uses: &[SsaVarId],
def: Option<SsaVarId>,
is_virtual: bool,
) -> Option<SsaOp> {
extract_method_token(&instr.operand).map(|method| {
if is_virtual {
SsaOp::CallVirt {
dest: def,
method,
args: uses.to_vec(),
}
} else {
SsaOp::Call {
dest: def,
method,
args: uses.to_vec(),
}
}
})
}
fn ldelem_op(
uses: &[SsaVarId],
def: Option<SsaVarId>,
operand: &Operand,
assembly: Option<&CilObject>,
) -> Option<SsaOp> {
let elem_type =
extract_type_token(operand).map_or(SsaType::Unknown, |tr| type_ref_to_ssa(tr, assembly));
ldelem_typed(uses, def, elem_type)
}
fn ldelem_typed(uses: &[SsaVarId], def: Option<SsaVarId>, elem_type: SsaType) -> Option<SsaOp> {
if let (Some(&array), Some(&index), Some(dest)) = (uses.first(), uses.get(1), def) {
Some(SsaOp::LoadElement {
dest,
array,
index,
elem_type,
})
} else {
None
}
}
fn stelem_op(uses: &[SsaVarId], operand: &Operand, assembly: Option<&CilObject>) -> Option<SsaOp> {
let elem_type =
extract_type_token(operand).map_or(SsaType::Unknown, |tr| type_ref_to_ssa(tr, assembly));
stelem_typed(uses, elem_type)
}
fn stelem_typed(uses: &[SsaVarId], elem_type: SsaType) -> Option<SsaOp> {
if let (Some(&array), Some(&index), Some(&value)) = (uses.first(), uses.get(1), uses.get(2)) {
Some(SsaOp::StoreElement {
array,
index,
value,
elem_type,
})
} else {
None
}
}
fn ldind_typed(uses: &[SsaVarId], def: Option<SsaVarId>, value_type: SsaType) -> Option<SsaOp> {
if let (Some(&addr), Some(dest)) = (uses.first(), def) {
Some(SsaOp::LoadIndirect {
dest,
addr,
value_type,
})
} else {
None
}
}
fn stind_typed(uses: &[SsaVarId], value_type: SsaType) -> Option<SsaOp> {
if let (Some(&addr), Some(&value)) = (uses.first(), uses.get(1)) {
Some(SsaOp::StoreIndirect {
addr,
value,
value_type,
})
} else {
None
}
}
fn extract_i32(operand: &Operand) -> Option<i32> {
match operand {
Operand::Immediate(Immediate::Int8(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::Int16(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::Int32(v)) => Some(*v),
Operand::Immediate(Immediate::UInt8(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::UInt16(v)) => Some(i32::from(*v)),
Operand::Immediate(Immediate::UInt32(v)) => i32::try_from(*v).ok(),
_ => None,
}
}
fn extract_i64(operand: &Operand) -> Option<i64> {
match operand {
Operand::Immediate(Immediate::Int64(v)) => Some(*v),
Operand::Immediate(Immediate::UInt64(v)) => i64::try_from(*v).ok(),
_ => None,
}
}
fn extract_f32(operand: &Operand) -> Option<f32> {
match operand {
Operand::Immediate(Immediate::Float32(v)) => Some(*v),
_ => None,
}
}
fn extract_f64(operand: &Operand) -> Option<f64> {
match operand {
Operand::Immediate(Immediate::Float64(v)) => Some(*v),
_ => None,
}
}
fn extract_u16(operand: &Operand) -> Option<u16> {
match operand {
Operand::Immediate(Immediate::UInt8(v)) => Some(u16::from(*v)),
Operand::Immediate(Immediate::UInt16(v)) | Operand::Argument(v) | Operand::Local(v) => {
Some(*v)
}
Operand::Immediate(Immediate::Int8(v)) => u16::try_from(*v).ok(),
Operand::Immediate(Immediate::Int16(v)) => u16::try_from(*v).ok(),
_ => None,
}
}
fn extract_method_token(operand: &Operand) -> Option<MethodRef> {
match operand {
Operand::Token(token) => Some(MethodRef::new(*token)),
_ => None,
}
}
fn extract_field_token(operand: &Operand) -> Option<FieldRef> {
match operand {
Operand::Token(token) => Some(FieldRef::new(*token)),
_ => None,
}
}
fn extract_type_token(operand: &Operand) -> Option<TypeRef> {
match operand {
Operand::Token(token) => Some(TypeRef::new(*token)),
_ => None,
}
}
fn extract_signature_token(operand: &Operand) -> Option<SigRef> {
match operand {
Operand::Token(token) => Some(SigRef::new(*token)),
_ => None,
}
}
fn extract_string_token(operand: &Operand) -> Option<u32> {
match operand {
Operand::Token(token) => Some(token.value() & 0x00FF_FFFF),
Operand::Immediate(Immediate::UInt32(v)) => Some(*v),
_ => None,
}
}
fn type_ref_to_ssa(type_ref: TypeRef, assembly: Option<&CilObject>) -> SsaType {
let Some(assembly) = assembly else {
return SsaType::Unknown;
};
SsaType::from_type_token(type_ref.token(), assembly)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assembly::{FlowType, InstructionCategory, StackBehavior};
fn make_instruction(
opcode: u8,
prefix: u8,
mnemonic: &'static str,
operand: Operand,
pops: u8,
pushes: u8,
) -> Instruction {
Instruction {
rva: 0x1000,
offset: 0,
size: 1,
opcode,
prefix,
mnemonic,
category: InstructionCategory::Arithmetic,
flow_type: FlowType::Sequential,
operand,
stack_behavior: StackBehavior {
pops,
pushes,
net_effect: i8::try_from(i16::from(pushes) - i16::from(pops)).unwrap_or(0),
},
branch_targets: vec![],
}
}
#[test]
fn test_decompose_add() {
let instr = make_instruction(0x58, 0, "add", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_ok());
if let Ok(SsaOp::Add { dest, left, right }) = op {
assert_eq!(dest, v2);
assert_eq!(left, v0);
assert_eq!(right, v1);
} else {
panic!("Expected SsaOp::Add");
}
}
#[test]
fn test_decompose_ldc_i4_0() {
let instr = make_instruction(0x16, 0, "ldc.i4.0", Operand::None, 0, 1);
let v0 = SsaVarId::new();
let uses = vec![];
let def = Some(v0);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_ok());
if let Ok(SsaOp::Const { dest, value }) = op {
assert_eq!(dest, v0);
assert_eq!(value, ConstValue::I32(0));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_ldc_i4_s() {
let instr = make_instruction(
0x1F,
0,
"ldc.i4.s",
Operand::Immediate(Immediate::Int8(42)),
0,
1,
);
let v0 = SsaVarId::new();
let uses = vec![];
let def = Some(v0);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_ok());
if let Ok(SsaOp::Const { dest, value }) = op {
assert_eq!(dest, v0);
assert_eq!(value, ConstValue::I32(42));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_ret_with_value() {
let instr = make_instruction(0x2A, 0, "ret", Operand::None, 1, 0);
let v = SsaVarId::new();
let uses = vec![v];
let def = None;
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_ok());
if let Ok(SsaOp::Return { value }) = op {
assert_eq!(value, Some(v));
} else {
panic!("Expected SsaOp::Return");
}
}
#[test]
fn test_decompose_ret_void() {
let instr = make_instruction(0x2A, 0, "ret", Operand::None, 0, 0);
let uses = vec![];
let def = None;
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_ok());
if let Ok(SsaOp::Return { value }) = op {
assert_eq!(value, None);
} else {
panic!("Expected SsaOp::Return");
}
}
#[test]
fn test_decompose_ceq() {
let instr = make_instruction(0x01, 0xFE, "ceq", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_ok());
if let Ok(SsaOp::Ceq { dest, left, right }) = op {
assert_eq!(dest, v2);
assert_eq!(left, v0);
assert_eq!(right, v1);
} else {
panic!("Expected SsaOp::Ceq");
}
}
#[test]
fn test_decompose_nop() {
let instr = make_instruction(0x00, 0, "nop", Operand::None, 0, 0);
let uses = vec![];
let def = None;
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert_eq!(op.unwrap(), SsaOp::Nop);
}
#[test]
fn test_decompose_conv_i4() {
let instr = make_instruction(0x69, 0, "conv.i4", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0];
let def = Some(v1);
let op = decompose_instruction(&instr, &uses, def, &[], None).unwrap();
if let SsaOp::Conv {
dest,
operand,
target,
overflow_check,
unsigned,
} = op
{
assert_eq!(dest, v1);
assert_eq!(operand, v0);
assert_eq!(target, SsaType::I32);
assert!(!overflow_check);
assert!(!unsigned);
} else {
panic!("Expected SsaOp::Conv");
}
}
#[test]
fn test_decompose_sub() {
let instr = make_instruction(0x59, 0, "sub", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Sub { .. })));
}
#[test]
fn test_decompose_mul() {
let instr = make_instruction(0x5A, 0, "mul", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Mul { .. })));
}
#[test]
fn test_decompose_div() {
let instr = make_instruction(0x5B, 0, "div", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0, v1];
let v2 = SsaVarId::new();
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Div { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::Div");
}
}
#[test]
fn test_decompose_div_un() {
let instr = make_instruction(0x5C, 0, "div.un", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Div { unsigned, .. }) = op {
assert!(unsigned);
} else {
panic!("Expected SsaOp::Div");
}
}
#[test]
fn test_decompose_rem() {
let instr = make_instruction(0x5D, 0, "rem", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Rem { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::Rem");
}
}
#[test]
fn test_decompose_rem_un() {
let instr = make_instruction(0x5E, 0, "rem.un", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Rem { unsigned, .. }) = op {
assert!(unsigned);
} else {
panic!("Expected SsaOp::Rem");
}
}
#[test]
fn test_decompose_neg() {
let instr = make_instruction(0x65, 0, "neg", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0];
let def = Some(v1);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Neg { .. })));
}
#[test]
fn test_decompose_add_ovf() {
let instr = make_instruction(0xD6, 0, "add.ovf", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::AddOvf { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::AddOvf");
}
}
#[test]
fn test_decompose_add_ovf_un() {
let instr = make_instruction(0xD7, 0, "add.ovf.un", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::AddOvf { unsigned, .. }) = op {
assert!(unsigned);
} else {
panic!("Expected SsaOp::AddOvf");
}
}
#[test]
fn test_decompose_mul_ovf() {
let instr = make_instruction(0xD8, 0, "mul.ovf", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::MulOvf { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::MulOvf");
}
}
#[test]
fn test_decompose_sub_ovf() {
let instr = make_instruction(0xDA, 0, "sub.ovf", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::SubOvf { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::SubOvf");
}
}
#[test]
fn test_decompose_and() {
let instr = make_instruction(0x5F, 0, "and", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::And { .. })));
}
#[test]
fn test_decompose_or() {
let instr = make_instruction(0x60, 0, "or", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Or { .. })));
}
#[test]
fn test_decompose_xor() {
let instr = make_instruction(0x61, 0, "xor", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Xor { .. })));
}
#[test]
fn test_decompose_not() {
let instr = make_instruction(0x66, 0, "not", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0];
let def = Some(v1);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Not { .. })));
}
#[test]
fn test_decompose_shl() {
let instr = make_instruction(0x62, 0, "shl", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(matches!(op, Ok(SsaOp::Shl { .. })));
}
#[test]
fn test_decompose_shr() {
let instr = make_instruction(0x63, 0, "shr", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Shr { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::Shr");
}
}
#[test]
fn test_decompose_shr_un() {
let instr = make_instruction(0x64, 0, "shr.un", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Shr { unsigned, .. }) = op {
assert!(unsigned);
} else {
panic!("Expected SsaOp::Shr");
}
}
#[test]
fn test_decompose_ldnull() {
let instr = make_instruction(0x14, 0, "ldnull", Operand::None, 0, 1);
let v0 = SsaVarId::new();
let uses = vec![];
let def = Some(v0);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::Null);
} else {
panic!("Expected SsaOp::Const with Null");
}
}
#[test]
fn test_decompose_ldc_i4_m1() {
let instr = make_instruction(0x15, 0, "ldc.i4.m1", Operand::None, 0, 1);
let v0 = SsaVarId::new();
let uses = vec![];
let def = Some(v0);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::I32(-1));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_ldc_i4_constants() {
for (opcode, expected_value) in [
(0x17u8, 1i32),
(0x18, 2),
(0x19, 3),
(0x1A, 4),
(0x1B, 5),
(0x1C, 6),
(0x1D, 7),
(0x1E, 8),
] {
let instr = make_instruction(opcode, 0, "ldc.i4.N", Operand::None, 0, 1);
let v0 = SsaVarId::new();
let op = decompose_instruction(&instr, &[], Some(v0), &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::I32(expected_value));
} else {
panic!("Expected SsaOp::Const for opcode {opcode:#x}");
}
}
}
#[test]
fn test_decompose_ldc_i4() {
let instr = make_instruction(
0x20,
0,
"ldc.i4",
Operand::Immediate(Immediate::Int32(0x12345678)),
0,
1,
);
let v0 = SsaVarId::new();
let op = decompose_instruction(&instr, &[], Some(v0), &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::I32(0x12345678));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_ldc_i8() {
let instr = make_instruction(
0x21,
0,
"ldc.i8",
Operand::Immediate(Immediate::Int64(0x123456789ABCDEF0)),
0,
1,
);
let v0 = SsaVarId::new();
let op = decompose_instruction(&instr, &[], Some(v0), &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::I64(0x123456789ABCDEF0));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_ldc_r4() {
let instr = make_instruction(
0x22,
0,
"ldc.r4",
Operand::Immediate(Immediate::Float32(std::f32::consts::PI)),
0,
1,
);
let v0 = SsaVarId::new();
let op = decompose_instruction(&instr, &[], Some(v0), &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::F32(std::f32::consts::PI));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_ldc_r8() {
let instr = make_instruction(
0x23,
0,
"ldc.r8",
Operand::Immediate(Immediate::Float64(std::f64::consts::E)),
0,
1,
);
let v0 = SsaVarId::new();
let op = decompose_instruction(&instr, &[], Some(v0), &[], None);
if let Ok(SsaOp::Const { value, .. }) = op {
assert_eq!(value, ConstValue::F64(std::f64::consts::E));
} else {
panic!("Expected SsaOp::Const");
}
}
#[test]
fn test_decompose_dup() {
let instr = make_instruction(0x25, 0, "dup", Operand::None, 1, 2);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0];
let def = Some(v1);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Copy { dest, src }) = op {
assert_eq!(dest, v1);
assert_eq!(src, v0);
} else {
panic!("Expected SsaOp::Copy");
}
}
#[test]
fn test_decompose_pop() {
let instr = make_instruction(0x26, 0, "pop", Operand::None, 1, 0);
let v0 = SsaVarId::new();
let uses = vec![v0];
let def = None;
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Pop { value }) = op {
assert_eq!(value, v0);
} else {
panic!("Expected SsaOp::Pop");
}
}
#[test]
fn test_decompose_br() {
let instr = make_instruction(0x38, 0, "br", Operand::None, 0, 0);
let successors = vec![5];
let op = decompose_instruction(&instr, &[], None, &successors, None);
if let Ok(SsaOp::Jump { target }) = op {
assert_eq!(target, 5);
} else {
panic!("Expected SsaOp::Jump");
}
}
#[test]
fn test_decompose_br_s() {
let instr = make_instruction(0x2B, 0, "br.s", Operand::None, 0, 0);
let successors = vec![3];
let op = decompose_instruction(&instr, &[], None, &successors, None);
if let Ok(SsaOp::Jump { target }) = op {
assert_eq!(target, 3);
} else {
panic!("Expected SsaOp::Jump");
}
}
#[test]
fn test_decompose_brfalse() {
let instr = make_instruction(0x39, 0, "brfalse", Operand::None, 1, 0);
let v0 = SsaVarId::new();
let uses = vec![v0];
let successors = vec![5, 2];
let op = decompose_instruction(&instr, &uses, None, &successors, None);
if let Ok(SsaOp::Branch {
condition,
true_target,
false_target,
}) = op
{
assert_eq!(condition, v0);
assert_eq!(true_target, 2); assert_eq!(false_target, 5); } else {
panic!("Expected SsaOp::Branch");
}
}
#[test]
fn test_decompose_brtrue() {
let instr = make_instruction(0x3A, 0, "brtrue", Operand::None, 1, 0);
let v0 = SsaVarId::new();
let uses = vec![v0];
let successors = vec![5, 2];
let op = decompose_instruction(&instr, &uses, None, &successors, None);
if let Ok(SsaOp::Branch {
condition,
true_target,
false_target,
}) = op
{
assert_eq!(condition, v0);
assert_eq!(true_target, 5); assert_eq!(false_target, 2); } else {
panic!("Expected SsaOp::Branch");
}
}
#[test]
fn test_decompose_switch() {
let instr = make_instruction(0x45, 0, "switch", Operand::None, 1, 0);
let v0 = SsaVarId::new();
let uses = vec![v0];
let successors = vec![10, 20, 30, 5];
let op = decompose_instruction(&instr, &uses, None, &successors, None);
if let Ok(SsaOp::Switch {
value,
targets,
default,
}) = op
{
assert_eq!(value, v0);
assert_eq!(targets, vec![10, 20, 30]);
assert_eq!(default, 5);
} else {
panic!("Expected SsaOp::Switch");
}
}
#[test]
fn test_decompose_cgt() {
let instr = make_instruction(0x02, 0xFE, "cgt", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Cgt { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::Cgt");
}
}
#[test]
fn test_decompose_cgt_un() {
let instr = make_instruction(0x03, 0xFE, "cgt.un", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Cgt { unsigned, .. }) = op {
assert!(unsigned);
} else {
panic!("Expected SsaOp::Cgt");
}
}
#[test]
fn test_decompose_clt() {
let instr = make_instruction(0x04, 0xFE, "clt", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Clt { unsigned, .. }) = op {
assert!(!unsigned);
} else {
panic!("Expected SsaOp::Clt");
}
}
#[test]
fn test_decompose_clt_un() {
let instr = make_instruction(0x05, 0xFE, "clt.un", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let v2 = SsaVarId::new();
let uses = vec![v0, v1];
let def = Some(v2);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::Clt { unsigned, .. }) = op {
assert!(unsigned);
} else {
panic!("Expected SsaOp::Clt");
}
}
#[test]
fn test_decompose_conv_variants() {
let test_cases = [
(0x67u8, SsaType::I8, false, false), (0x68, SsaType::I16, false, false), (0x6A, SsaType::I64, false, false), (0x6B, SsaType::F32, false, false), (0x6C, SsaType::F64, false, false), (0xD1, SsaType::U16, false, true), (0xD2, SsaType::U8, false, true), (0x6D, SsaType::U32, false, true), (0x6E, SsaType::U64, false, true), (0xD3, SsaType::NativeInt, false, false), (0xE0, SsaType::NativeUInt, false, true), ];
for (opcode, expected_type, expected_ovf, expected_unsigned) in test_cases {
let instr = make_instruction(opcode, 0, "conv.*", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let op = decompose_instruction(&instr, &[v0], Some(v1), &[], None);
if let Ok(SsaOp::Conv {
target,
overflow_check,
unsigned,
..
}) = op
{
assert_eq!(
target, expected_type,
"Type mismatch for opcode {opcode:#x}"
);
assert_eq!(
overflow_check, expected_ovf,
"Overflow mismatch for opcode {opcode:#x}"
);
assert_eq!(
unsigned, expected_unsigned,
"Unsigned mismatch for opcode {opcode:#x}"
);
} else {
panic!("Expected SsaOp::Conv for opcode {opcode:#x}");
}
}
}
#[test]
fn test_decompose_conv_ovf_variants() {
let test_cases = [
(0xB3u8, SsaType::I8, true, false), (0x82, SsaType::I8, true, true), (0xB5, SsaType::I16, true, false), (0xB7, SsaType::I32, true, false), (0xB9, SsaType::I64, true, false), (0xB4, SsaType::U8, true, false), (0xB6, SsaType::U16, true, false), (0xB8, SsaType::U32, true, false), (0xBA, SsaType::U64, true, false), ];
for (opcode, expected_type, expected_ovf, expected_unsigned) in test_cases {
let instr = make_instruction(opcode, 0, "conv.ovf.*", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let op = decompose_instruction(&instr, &[v0], Some(v1), &[], None);
if let Ok(SsaOp::Conv {
target,
overflow_check,
unsigned,
..
}) = op
{
assert_eq!(
target, expected_type,
"Type mismatch for opcode {opcode:#x}"
);
assert_eq!(
overflow_check, expected_ovf,
"Overflow mismatch for opcode {opcode:#x}"
);
assert_eq!(
unsigned, expected_unsigned,
"Unsigned mismatch for opcode {opcode:#x}"
);
} else {
panic!("Expected SsaOp::Conv for opcode {opcode:#x}");
}
}
}
#[test]
fn test_decompose_throw() {
let instr = make_instruction(0x7A, 0, "throw", Operand::None, 1, 0);
let v0 = SsaVarId::new();
let uses = vec![v0];
let op = decompose_instruction(&instr, &uses, None, &[], None);
if let Ok(SsaOp::Throw { exception }) = op {
assert_eq!(exception, v0);
} else {
panic!("Expected SsaOp::Throw");
}
}
#[test]
fn test_decompose_endfinally() {
let instr = make_instruction(0xDC, 0, "endfinally", Operand::None, 0, 0);
let op = decompose_instruction(&instr, &[], None, &[], None);
assert_eq!(op.unwrap(), SsaOp::EndFinally);
}
#[test]
fn test_decompose_rethrow() {
let instr = make_instruction(0x1A, 0xFE, "rethrow", Operand::None, 0, 0);
let op = decompose_instruction(&instr, &[], None, &[], None);
assert_eq!(op.unwrap(), SsaOp::Rethrow);
}
#[test]
fn test_decompose_break() {
let instr = make_instruction(0x01, 0, "break", Operand::None, 0, 0);
let op = decompose_instruction(&instr, &[], None, &[], None);
assert_eq!(op.unwrap(), SsaOp::Break);
}
#[test]
fn test_decompose_leave() {
let instr = make_instruction(0xDE, 0, "leave", Operand::None, 0, 0);
let successors = vec![10];
let op = decompose_instruction(&instr, &[], None, &successors, None);
if let Ok(SsaOp::Leave { target }) = op {
assert_eq!(target, 10);
} else {
panic!("Expected SsaOp::Leave");
}
}
#[test]
fn test_decompose_localloc() {
let instr = make_instruction(0x0F, 0xFE, "localloc", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0];
let def = Some(v1);
let op = decompose_instruction(&instr, &uses, def, &[], None);
if let Ok(SsaOp::LocalAlloc { dest, size }) = op {
assert_eq!(dest, v1);
assert_eq!(size, v0);
} else {
panic!("Expected SsaOp::LocalAlloc");
}
}
#[test]
fn test_decompose_binary_missing_operands() {
let instr = make_instruction(0x58, 0, "add", Operand::None, 2, 1);
let v0 = SsaVarId::new();
let v1 = SsaVarId::new();
let uses = vec![v0]; let def = Some(v1);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_err());
}
#[test]
fn test_decompose_unary_missing_operand() {
let instr = make_instruction(0x65, 0, "neg", Operand::None, 1, 1);
let v0 = SsaVarId::new();
let uses = vec![]; let def = Some(v0);
let op = decompose_instruction(&instr, &uses, def, &[], None);
assert!(op.is_err());
}
#[test]
fn test_decompose_const_missing_def() {
let instr = make_instruction(0x16, 0, "ldc.i4.0", Operand::None, 0, 1);
let op = decompose_instruction(&instr, &[], None, &[], None);
assert!(op.is_err());
}
#[test]
fn test_decompose_unknown_opcode() {
let instr = make_instruction(0xFF, 0, "unknown", Operand::None, 0, 0);
let op = decompose_instruction(&instr, &[], None, &[], None);
assert!(op.is_err());
}
}