use crate::instruction::{Instruction, OpCode, Operand};
use super::state::{StackEffect, MAX_SIMULATED_POPS};
pub(super) fn stack_effect_for_arg_inference(
instruction: &Instruction,
simulated_stack: &[Option<usize>],
) -> Option<StackEffect> {
use OpCode::*;
let literal_push = |value: usize| StackEffect {
pops: 0,
pushes: vec![Some(value)],
};
let unknown_push = || StackEffect {
pops: 0,
pushes: vec![None],
};
let unary_unknown = || StackEffect {
pops: 1,
pushes: vec![None],
};
let binary_unknown = || StackEffect {
pops: 2,
pushes: vec![None],
};
match instruction.opcode {
Push0 => Some(literal_push(0)),
Push1 => Some(literal_push(1)),
Push2 => Some(literal_push(2)),
Push3 => Some(literal_push(3)),
Push4 => Some(literal_push(4)),
Push5 => Some(literal_push(5)),
Push6 => Some(literal_push(6)),
Push7 => Some(literal_push(7)),
Push8 => Some(literal_push(8)),
Push9 => Some(literal_push(9)),
Push10 => Some(literal_push(10)),
Push11 => Some(literal_push(11)),
Push12 => Some(literal_push(12)),
Push13 => Some(literal_push(13)),
Push14 => Some(literal_push(14)),
Push15 => Some(literal_push(15)),
Push16 => Some(literal_push(16)),
Pushint8 | Pushint16 | Pushint32 | Pushint64 => match instruction.operand {
Some(Operand::I8(v)) if v >= 0 => Some(literal_push(v as usize)),
Some(Operand::I16(v)) if v >= 0 => Some(literal_push(v as usize)),
Some(Operand::I32(v)) if v >= 0 => Some(literal_push(v as usize)),
Some(Operand::I64(v)) if v >= 0 => Some(literal_push(v as usize)),
_ => Some(unknown_push()),
},
Pushint128 | Pushint256 | PushT | PushF | PushA | PushNull | Pushdata1 | Pushdata2
| Pushdata4 | PushM1 | Newarray0 | Newstruct0 | Newmap | Ldsfld0 | Ldsfld1 | Ldsfld2
| Ldsfld3 | Ldsfld4 | Ldsfld5 | Ldsfld6 | Ldsfld | Ldloc0 | Ldloc1 | Ldloc2 | Ldloc3
| Ldloc4 | Ldloc5 | Ldloc6 | Ldloc | Ldarg0 | Ldarg1 | Ldarg2 | Ldarg3 | Ldarg4
| Ldarg5 | Ldarg6 | Ldarg | Depth => Some(unknown_push()),
Nop | Initsslot | Initslot => Some(StackEffect {
pops: 0,
pushes: vec![],
}),
Drop | Stsfld0 | Stsfld1 | Stsfld2 | Stsfld3 | Stsfld4 | Stsfld5 | Stsfld6 | Stsfld
| Stloc0 | Stloc1 | Stloc2 | Stloc3 | Stloc4 | Stloc5 | Stloc6 | Stloc | Starg0
| Starg1 | Starg2 | Starg3 | Starg4 | Starg5 | Starg6 | Starg | Reverseitems
| Clearitems => Some(StackEffect {
pops: 1,
pushes: vec![],
}),
Newbuffer | Isnull | Istype | Convert | Keys | Values | Size | Sign | Abs | Negate
| Inc | Dec | Not | Nz | Sqrt | Newarray | NewarrayT | Newstruct | Invert => {
Some(unary_unknown())
}
Dup => {
let top = simulated_stack.last().copied().flatten();
Some(StackEffect {
pops: 1,
pushes: vec![top, top],
})
}
Nip => Some(StackEffect {
pops: 2,
pushes: vec![None],
}),
Over | Tuck => Some(StackEffect {
pops: 2,
pushes: vec![None, None, None],
}),
Swap => Some(StackEffect {
pops: 2,
pushes: vec![None, None],
}),
Rot | Reverse3 => Some(StackEffect {
pops: 3,
pushes: vec![None, None, None],
}),
Reverse4 => Some(StackEffect {
pops: 4,
pushes: vec![None, None, None, None],
}),
Cat | Left | Right | And | Or | Xor | Equal | Notequal | Add | Sub | Mul | Div | Mod
| Pow | Shl | Shr | Booland | Boolor | Numequal | Numnotequal | Lt | Le | Gt | Ge | Min
| Max | Haskey | Pickitem => Some(binary_unknown()),
Popitem => Some(unary_unknown()),
Append | Remove => Some(StackEffect {
pops: 2,
pushes: vec![],
}),
Substr | Modmul | Modpow | Within => Some(StackEffect {
pops: 3,
pushes: vec![None],
}),
Setitem => Some(StackEffect {
pops: 3,
pushes: vec![],
}),
Memcpy => Some(StackEffect {
pops: 5,
pushes: vec![],
}),
Pack | Packstruct => {
let count = simulated_stack.last().copied().flatten()?;
let pops = count.saturating_add(1).min(MAX_SIMULATED_POPS);
Some(StackEffect {
pops,
pushes: vec![None],
})
}
Packmap => {
let count = simulated_stack.last().copied().flatten()?;
let pops = count
.saturating_mul(2)
.saturating_add(1)
.min(MAX_SIMULATED_POPS);
Some(StackEffect {
pops,
pushes: vec![None],
})
}
Syscall => match instruction.operand {
Some(Operand::Syscall(hash)) => {
let info = crate::syscalls::lookup(hash);
let pops = info.map_or(0usize, |syscall| syscall.param_count as usize);
let pushes = if info.is_some_and(|syscall| syscall.returns_value) {
vec![None]
} else {
vec![]
};
Some(StackEffect { pops, pushes })
}
_ => None,
},
_ => None,
}
}