use std::collections::HashMap;
use walrus::ir::{self, BinaryOp, UnaryOp};
use walrus::LocalId;
use super::{AnalyzedModule, MemBase, StackValue};
pub fn merge_locals(
cons_locals: &mut HashMap<LocalId, StackValue>,
alt_locals: &HashMap<LocalId, StackValue>,
) {
let snapshot = cons_locals.clone();
for (lid, cons_val) in &snapshot {
if let Some(alt_val) = alt_locals.get(lid) {
if format!("{cons_val:?}") != format!("{alt_val:?}") {
cons_locals.remove(lid);
}
}
}
for (lid, alt_val) in alt_locals {
if !snapshot.contains_key(lid) {
cons_locals.insert(*lid, alt_val.clone());
}
}
}
pub fn contains_unknown(sv: &StackValue) -> bool {
match sv {
StackValue::Unknown => true,
StackValue::BinOp { left, right, .. } => {
contains_unknown(left) || contains_unknown(right)
}
StackValue::UnOp { operand, .. } => {
contains_unknown(operand)
}
StackValue::Global(_) => false,
_ => false,
}
}
pub fn decompose_address(
addr: &StackValue,
) -> Option<(MemBase, i64)> {
match addr {
StackValue::Local(lid) => Some((MemBase::Local(*lid), 0)),
StackValue::Global(gid) => Some((MemBase::Global(*gid), 0)),
StackValue::BinOp {
op: crate::ir::BinOp::Add,
left,
right,
} => {
if let Some((base_id, base)) = decompose_address(left) {
if let Some(rv) = eval_const_i64(right) {
return Some((base_id, base + rv));
}
}
if let Some((base_id, base)) = decompose_address(right) {
if let Some(lv) = eval_const_i64(left) {
return Some((base_id, base + lv));
}
}
None
}
StackValue::BinOp {
op: crate::ir::BinOp::Sub,
left,
right,
} => {
if let Some((base_id, base)) = decompose_address(left) {
if let Some(rv) = eval_const_i64(right) {
return Some((base_id, base - rv));
}
}
None
}
_ => None,
}
}
fn eval_const_i64(sv: &StackValue) -> Option<i64> {
use crate::ir::BinOp as B;
match sv {
StackValue::Const(ir::Value::I32(n)) => Some(*n as i64),
StackValue::Const(ir::Value::I64(n)) => Some(*n),
StackValue::BinOp { op: B::Add, left, right } => {
Some(eval_const_i64(left)? + eval_const_i64(right)?)
}
StackValue::BinOp { op: B::Sub, left, right } => {
Some(eval_const_i64(left)? - eval_const_i64(right)?)
}
StackValue::BinOp { op: B::Mul, left, right } => {
Some(eval_const_i64(left)? * eval_const_i64(right)?)
}
_ => None,
}
}
pub fn try_decode_vec_elements(
args: &[StackValue],
memory_state: &HashMap<(MemBase, i64), StackValue>,
) -> Option<Vec<StackValue>> {
let ptr_raw = args.get(0)?;
let len_raw = args.get(1)?;
let ptr_stripped = crate::pattern_recognizer::strip_val_boilerplate(ptr_raw);
let (base_mem, base_offset) = decompose_address(&ptr_stripped)?;
let len = crate::pattern_recognizer::extract_u32_val(len_raw)?;
if len > 16 { return None; }
let mut elements = Vec::new();
let mut all_found = true;
for i in 0..len {
let offset = base_offset + (i as i64) * 8;
if let Some(val) = memory_state.get(&(base_mem, offset)) {
elements.push(val.clone());
} else {
all_found = false;
elements.push(StackValue::Unknown);
}
}
if all_found && !elements.iter().all(|v| matches!(v, StackValue::Unknown)) {
return Some(elements);
}
for spill_base in &[0i64, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 96, 112, 128] {
if *spill_base == base_offset { continue; }
let mut spill_elements = Vec::new();
let mut spill_ok = true;
for i in 0..len {
let offset = *spill_base + (i as i64) * 8;
if let Some(val) = memory_state.get(&(base_mem, offset)) {
spill_elements.push(val.clone());
} else {
spill_ok = false;
break;
}
}
if spill_ok && !spill_elements.iter().all(|v| matches!(v, StackValue::Unknown)) {
return Some(spill_elements);
}
}
let mut known_offsets: Vec<i64> = memory_state.keys()
.filter(|(lid, _)| *lid == base_mem)
.map(|(_, off)| *off)
.collect();
known_offsets.sort();
known_offsets.dedup();
for &start in &known_offsets {
if start == base_offset { continue; }
let mut scan_elements = Vec::new();
let mut scan_ok = true;
for i in 0..len {
let offset = start + (i as i64) * 8;
if let Some(val) = memory_state.get(&(base_mem, offset)) {
scan_elements.push(val.clone());
} else {
scan_ok = false;
break;
}
}
if scan_ok && !scan_elements.iter().all(|v| matches!(v, StackValue::Unknown)) {
return Some(scan_elements);
}
}
if elements.iter().any(|v| !matches!(v, StackValue::Unknown)) {
return Some(elements);
}
None
}
pub fn try_decode_map_elements(
args: &[StackValue],
memory_state: &HashMap<(MemBase, i64), StackValue>,
analyzed: &AnalyzedModule,
) -> Option<(Vec<String>, Vec<StackValue>)> {
let keys_ptr_raw = args.get(0)?;
let vals_ptr_raw = args.get(1)?;
let len_raw = args.get(2)?;
let len = crate::pattern_recognizer::extract_u32_val(len_raw)?;
if len > 32 { return None; }
let keys_ptr = crate::pattern_recognizer::extract_u32_val(keys_ptr_raw)?;
let keys = crate::pattern_recognizer::decode_keys_from_linear_memory(keys_ptr, len, analyzed)?;
let vals_stripped = crate::pattern_recognizer::strip_val_boilerplate(vals_ptr_raw);
let mut values = Vec::new();
if let Some((base_mem, base_offset)) = decompose_address(&vals_stripped) {
for i in 0..len {
let offset = base_offset + (i as i64) * 8;
let val = memory_state.get(&(base_mem, offset))
.cloned()
.unwrap_or(StackValue::Unknown);
values.push(val);
}
} else {
values = vec![StackValue::Unknown; len as usize];
}
Some((keys, values))
}
pub fn map_binop(op: &BinaryOp) -> Option<crate::ir::BinOp> {
use crate::ir::BinOp as B;
use BinaryOp::*;
match op {
I32Add | I64Add | F32Add | F64Add => Some(B::Add),
I32Sub | I64Sub | F32Sub | F64Sub => Some(B::Sub),
I32Mul | I64Mul | F32Mul | F64Mul => Some(B::Mul),
I32DivS | I32DivU | I64DivS | I64DivU
| F32Div | F64Div => Some(B::Div),
I32RemS | I32RemU | I64RemS | I64RemU => Some(B::Rem),
I32And | I64And => Some(B::BitAnd),
I32Or | I64Or => Some(B::BitOr),
I32Xor | I64Xor => Some(B::BitXor),
I32Shl | I64Shl => Some(B::Shl),
I32ShrS | I32ShrU | I64ShrS | I64ShrU => Some(B::Shr),
I32Eq | I64Eq | F32Eq | F64Eq => Some(B::Eq),
I32Ne | I64Ne | F32Ne | F64Ne => Some(B::Ne),
I32LtS | I32LtU | I64LtS | I64LtU
| F32Lt | F64Lt => Some(B::Lt),
I32LeS | I32LeU | I64LeS | I64LeU
| F32Le | F64Le => Some(B::Le),
I32GtS | I32GtU | I64GtS | I64GtU
| F32Gt | F64Gt => Some(B::Gt),
I32GeS | I32GeU | I64GeS | I64GeU
| F32Ge | F64Ge => Some(B::Ge),
_ => None,
}
}
pub fn map_unop(op: &UnaryOp) -> Option<crate::ir::UnOp> {
use crate::ir::UnOp as U;
match op {
UnaryOp::F32Neg | UnaryOp::F64Neg => Some(U::Neg),
UnaryOp::I32Eqz | UnaryOp::I64Eqz => Some(U::Not),
_ => None,
}
}