use crate::instruction::OpCode;
pub(crate) type Effect = (usize, usize);
#[must_use]
pub(crate) fn stack_effect(op: OpCode) -> Effect {
use OpCode::*;
match op {
Push0 | Push1 | Push2 | Push3 | Push4 | Push5 | Push6 | Push7 | Push8 | Push9 | Push10
| Push11 | Push12 | Push13 | Push14 | Push15 | Push16 | PushM1 | Pushint8 | Pushint16
| Pushint32 | Pushint64 | Pushint128 | Pushint256 | Pushdata1 | Pushdata2 | Pushdata4
| PushA | PushT | PushF | PushNull => (0, 1),
Add | Sub | Mul | Div | Mod | Pow | Shl | Shr | And | Or | Xor | Equal | Notequal | Lt
| Le | Gt | Ge | Booland | Boolor | Numequal | Numnotequal | Min | Max | Cat => (2, 1),
Within | Substr | Modmul | Modpow => (3, 1),
Left | Right => (2, 1),
Sqrt | Abs | Negate | Inc | Dec | Sign | Not | Nz | Invert | Isnull | Istype | Convert
| Size | Keys | Values => (1, 1),
Ldsfld0 | Ldsfld1 | Ldsfld2 | Ldsfld3 | Ldsfld4 | Ldsfld5 | Ldsfld6 | Ldsfld | Ldloc0
| Ldloc1 | Ldloc2 | Ldloc3 | Ldloc4 | Ldloc5 | Ldloc6 | Ldloc | Ldarg0 | Ldarg1
| Ldarg2 | Ldarg3 | Ldarg4 | Ldarg5 | Ldarg6 | Ldarg => (0, 1),
Stsfld0 | Stsfld1 | Stsfld2 | Stsfld3 | Stsfld4 | Stsfld5 | Stsfld6 | Stsfld | Stloc0
| Stloc1 | Stloc2 | Stloc3 | Stloc4 | Stloc5 | Stloc6 | Stloc | Starg0 | Starg1
| Starg2 | Starg3 | Starg4 | Starg5 | Starg6 | Starg => (1, 0),
Newarray0 | Newstruct0 | Newmap => (0, 1),
Newarray | Newstruct | NewarrayT | Newbuffer => (1, 1),
Popitem => (1, 1),
Pickitem => (2, 1), Haskey => (2, 1),
Append => (2, 0), Setitem => (3, 0), Remove => (2, 0), Clearitems => (1, 0), Reverseitems => (1, 0), Memcpy => (5, 0),
Jmpif | Jmpif_L | Jmpifnot | Jmpifnot_L => (1, 0),
JmpEq | JmpEq_L | JmpNe | JmpNe_L | JmpGt | JmpGt_L | JmpGe | JmpGe_L | JmpLt | JmpLt_L
| JmpLe | JmpLe_L => (2, 0),
Assert | Throw => (1, 0),
Assertmsg => (2, 0),
Abortmsg => (1, 0),
Nop | Jmp | Jmp_L | Call | Call_L | CallA | CallT | Abort | Try | TryL | Endtry
| EndtryL | Endfinally | Ret | Initsslot | Initslot => (0, 0),
Depth | Drop | Nip | Dup | Over | Tuck | Swap | Rot | Reverse3 | Reverse4 | Xdrop
| Pick | Roll | Reversen | Clear | Syscall | Pack | Packmap | Packstruct | Unpack => (0, 0),
Unknown(_) => (0, 0),
}
}
#[must_use]
pub(crate) fn is_stack_reorder(op: OpCode) -> bool {
use OpCode::*;
matches!(
op,
Dup | Over | Tuck | Swap | Rot | Reverse3 | Reverse4 | Depth | Drop | Nip
)
}
#[must_use]
pub(crate) fn is_stack_special(op: OpCode) -> bool {
use OpCode::*;
matches!(
op,
Pick | Roll | Xdrop | Reversen | Pack | Packmap | Packstruct | Unpack | Clear | Syscall
)
}
#[cfg(test)]
mod tests {
use super::{is_stack_reorder, is_stack_special, stack_effect};
use crate::instruction::OpCode;
#[test]
fn push_opcodes_produce_one_value() {
for op in [
OpCode::Push0,
OpCode::Push1,
OpCode::Push16,
OpCode::PushM1,
OpCode::Pushint64,
OpCode::Pushdata4,
OpCode::PushNull,
OpCode::PushA,
OpCode::PushT,
OpCode::PushF,
] {
assert_eq!(stack_effect(op), (0, 1), "{op:?}");
}
}
#[test]
fn slot_loads_push_one() {
for op in [
OpCode::Ldloc0,
OpCode::Ldloc3,
OpCode::Ldarg0,
OpCode::Ldsfld6,
OpCode::Ldloc,
] {
assert_eq!(stack_effect(op), (0, 1), "{op:?}");
}
}
#[test]
fn slot_stores_pop_one() {
for op in [
OpCode::Stloc0,
OpCode::Stloc,
OpCode::Starg2,
OpCode::Stsfld0,
] {
assert_eq!(stack_effect(op), (1, 0), "{op:?}");
}
}
#[test]
fn collection_ops_have_correct_effects() {
assert_eq!(stack_effect(OpCode::Newarray0), (0, 1));
assert_eq!(stack_effect(OpCode::Newarray), (1, 1));
assert_eq!(stack_effect(OpCode::Pickitem), (2, 1));
assert_eq!(stack_effect(OpCode::Append), (2, 0));
assert_eq!(stack_effect(OpCode::Setitem), (3, 0));
assert_eq!(stack_effect(OpCode::Popitem), (1, 1));
assert_eq!(stack_effect(OpCode::Memcpy), (5, 0));
}
#[test]
fn convert_is_unary() {
assert_eq!(stack_effect(OpCode::Convert), (1, 1));
}
#[test]
fn conditional_jumps_pop_conditions() {
assert_eq!(stack_effect(OpCode::Jmpif), (1, 0));
assert_eq!(stack_effect(OpCode::Jmpifnot_L), (1, 0));
assert_eq!(stack_effect(OpCode::JmpEq), (2, 0));
assert_eq!(stack_effect(OpCode::JmpLt_L), (2, 0));
}
#[test]
fn reorders_and_specials_are_neutral_in_the_table() {
for op in [
OpCode::Dup,
OpCode::Swap,
OpCode::Rot,
OpCode::Drop,
OpCode::Depth,
OpCode::Pick,
OpCode::Roll,
OpCode::Pack,
OpCode::Clear,
OpCode::Syscall,
] {
assert_eq!(stack_effect(op), (0, 0), "{op:?}");
assert!(
is_stack_reorder(op) || is_stack_special(op),
"{op:?} should be classified as reorder or special"
);
}
}
#[test]
fn control_flow_and_calls_are_neutral() {
for op in [
OpCode::Nop,
OpCode::Jmp,
OpCode::Call,
OpCode::CallT,
OpCode::Ret,
OpCode::Initslot,
OpCode::Try,
OpCode::Endfinally,
] {
assert_eq!(stack_effect(op), (0, 0), "{op:?}");
assert!(!is_stack_reorder(op));
assert!(!is_stack_special(op));
}
}
}