//! Representation of the instructions.
//!
//! Instruction at a sligthly higher level than
//! <https://source.android.com/docs/core/runtime/dalvik-bytecode>

use serde::{Deserialize, Serialize};

use crate::hashmap_vectorize;
use crate::{
    DexString, DexValue, FieldIdCollector, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
    MethodHandleCollector, MethodIdCollector, MethodTypeCollector, Result, StringCollector,
    TypeCollector, Visitable, VisitableMut, Visitor, VisitorMut,
};
use androscalpel_serializer::Instruction as InsFormat;
use androscalpel_serializer::Serializable;

use anyhow::{anyhow, bail};
#[cfg(feature = "python")]
use pyo3::prelude::*;

use std::collections::{HashMap, HashSet};

const I8_MIN_AS_I16: i16 = i8::MIN as i16;
const I8_MAX_AS_I16: i16 = i8::MAX as i16;
const I8_MIN_AS_I32: i32 = i8::MIN as i32;
const I8_MAX_AS_I32: i32 = i8::MAX as i32;
const I16_MIN_AS_I32: i32 = i16::MIN as i32;
const I16_MAX_AS_I32: i32 = i16::MAX as i32;
const I16_MIN_AS_I64: i64 = i16::MIN as i64;
const I16_MAX_AS_I64: i64 = i16::MAX as i64;
const I32_MIN_AS_I64: i64 = i32::MIN as i64;
const I32_MAX_AS_I64: i64 = i32::MAX as i64;
const U16_MAX_AS_USIZE: usize = u16::MAX as usize;

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum Instruction {
    /// Waste a cycle.
    Nop {},
    /// Move contents of a non object register to another.
    /// Can represent several dalvik instructions :
    /// move vA, vB
    /// move/from16 vAA, vBBBB
    /// move/16 vAAAA, vBBBB
    Move { from: u16, to: u16 },
    /// Move contents of a register pair to another.
    /// Can represent several dalvik instructions :
    /// move-wide vA, vB
    /// move-wide/from16 vAA, vBBBB
    /// move-wide/16 vAAAA, vBBBB
    MoveWide { from: u16, to: u16 },
    /// Move contents of an object bearing register to another.
    /// Can represent several dalvik instructions :
    /// move-object vA, vB
    /// move-object/from16 vAA, vBBBB
    /// move-object/16 vAAAA, vBBBB
    MoveObject { from: u16, to: u16 },
    /// Move the single word non object result of the preciding invoke-kind into a register.
    MoveResult { to: u8 },
    /// Move the double word non object result of the preciding invoke-kind into a register pair.
    MoveResultWide { to: u8 },
    /// Move the just caught exception into a register.
    MoveException { to: u8 },
    /// Move the object result of the preciding invoke-kind or filled-new-array into a register.
    MoveResultObject { to: u8 },
    /// Return a void method
    ReturnVoid {},
    /// Return the 32 bits non object value from the method.
    Return { reg: u8 },
    /// Return the 64 bits non object value from the method.
    ReturnWide { reg: u8 },
    /// Return the object value from the method.
    ReturnObject { reg: u8 },
    /// Move a literal to a register.
    /// Can represent several dalvik instructions :
    /// const/4 vA #+B
    /// const/16 vAA #+BBBB
    /// const vAA #+BBBBBBBB
    /// const/high 16 vAA #+BBBB0000
    Const { reg: u8, lit: i32 },
    /// Move a literal to a register pair (64bits).
    /// Can represent several dalvik instructions :
    /// const-wide/16 vAA #+BBBB
    /// const-wide/32 vAA #+BBBBBBBB
    /// const-wide vAA #+BBBBBBBBBBBBBBBB
    /// const-wide/hight 16 vAA #+BBBB000000000000
    ConstWide { reg: u8, lit: i64 },
    /// Move a reference to a string in a register.
    /// Can represent several dalvik instructions :
    /// const-string vAA string@BBBB
    /// const-string/jumbo vAA string@BBBBBBBB
    ConstString { reg: u8, lit: DexString },
    /// Move a reference to a class in a register.
    ConstClass { reg: u8, lit: IdType },
    /// Acquire the monitor for the object in the register.
    MonitorEnter { reg: u8 },
    /// Release the monitor for the object in the register.
    MonitorExit { reg: u8 },
    /// Check if the object in the register can be cast to the type.
    /// (Raise a `ClassCastException` if not)
    CheckCast { reg: u8, lit: IdType },
    /// Check if an object if an instance of a type.
    /// (put 1 in the dest register if yes, else 0)
    InstanceOf {
        /// 4 bits register
        dest: u8,
        /// 4 bits register
        obj: u8,
        lit: IdType,
    },
    /// Get the number of item in an array.
    /// (put the lenght in the dest register)
    ArrayLength {
        /// 4 bits register
        dest: u8,
        /// 4 bits register
        arr: u8,
    },
    /// Construct a new instance of the indicated type and store a reference to it.
    NewInstance { reg: u8, lit: IdType },
    /// Construct a new array of the indicated type and size in size_reg and store a reference to it.
    NewArray {
        /// 4 bits register
        reg: u8,
        /// 4 bits register
        size_reg: u8,
        lit: IdType,
    },
    /// Construct a new array of the indicated type and size fill it with the values of the
    /// given registers.
    ///
    /// The elements of the array must fit in 32bits.
    ///
    /// The registers must either be less to 5 and their number must fit on 4 bits each, or
    /// be less than 256, their the number of the smallest must fit on 16 bits and they must
    /// be sorted an directly adjascent.
    ///
    /// The newly created array can be retreived with a move-result-object instruction.
    FilledNewArray { type_: IdType, reg_values: Vec<u16> },
    FillArrayData {
        arr: u8,
        elt_width: u16,
        data: Vec<u8>,
    },
    /// Throws the exception in the register.
    Throw { reg: u8 },
    /// Jump to the label.
    Goto { label: String },
    /// Jump to a label depending on the value of a register. If the value
    /// is not matched, continue the extecution at the next instruction.
    Switch {
        reg: u8,
        #[serde(with = "hashmap_vectorize")]
        branches: HashMap<i32, String>,
    },
    /// Store the result of the comparison between the registers.
    ///
    /// - b < c: a = -1
    /// - b == c: a = 0
    /// - b > c : a = 1
    /// - b == c == Nan: a = -1
    CmpLFloat { dest: u8, b: u8, c: u8 },
    /// Store the result of the comparison between the registers.
    ///
    /// - b < c: a = -1
    /// - b == c: a = 0
    /// - b > c : a = 1
    /// - b == c == Nan: a = -1
    CmpGFloat { dest: u8, b: u8, c: u8 },
    /// Store the result of the comparison between the registers.
    ///
    /// - b < c: a = -1
    /// - b == c: a = 0
    /// - b > c : a = 1
    /// - b == c == Nan: a = -1
    CmpLDouble { dest: u8, b: u8, c: u8 },
    /// Store the result of the comparison between the registers.
    ///
    /// - b < c: a = -1
    /// - b == c: a = 0
    /// - b > c : a = 1
    /// - b == c == Nan: a = -1
    CmpGDouble { dest: u8, b: u8, c: u8 },
    /// Store the result of the comparison between the registers.
    ///
    /// - b < c: a = -1
    /// - b == c: a = 0
    /// - b > c : a = 1
    /// - b == c == Nan: a = -1
    CmpLong { dest: u8, b: u8, c: u8 },
    /// Jump to the label if a == b
    IfEq { a: u8, b: u8, label: String },
    /// Jump to the label if a != b
    IfNe { a: u8, b: u8, label: String },
    /// Jump to the label if a < b
    IfLt { a: u8, b: u8, label: String },
    /// Jump to the label if a >= b
    IfGe { a: u8, b: u8, label: String },
    /// Jump to the label if a > b
    IfGt { a: u8, b: u8, label: String },
    /// Jump to the label if a <= b
    IfLe { a: u8, b: u8, label: String },
    /// Jump to the label if a == 0
    IfEqZ { a: u8, label: String },
    /// Jump to the label if a != 0
    IfNeZ { a: u8, label: String },
    /// Jump to the label if a < 0
    IfLtZ { a: u8, label: String },
    /// Jump to the label if a >= 0
    IfGeZ { a: u8, label: String },
    /// Jump to the label if a > 0
    IfGtZ { a: u8, label: String },
    /// Jump to the label if a <= 0
    IfLeZ { a: u8, label: String },
    /// Put the value at `arr[idx]` in register dest (all values are in registers)
    AGet { dest: u8, arr: u8, idx: u8 },
    /// Put the value at `arr[idx]` in register pair dest (all values are in registers)
    AGetWide { dest: u8, arr: u8, idx: u8 },
    /// Put the reference at `arr[idx]` in register (all values are in registers)
    AGetObject { dest: u8, arr: u8, idx: u8 },
    /// Put the boolean at `arr[idx]` in register dest (all values are in registers)
    AGetBoolean { dest: u8, arr: u8, idx: u8 },
    /// Put the byte at `arr[idx]` in register dest (all values are in registers)
    AGetByte { dest: u8, arr: u8, idx: u8 },
    /// Put the char at `arr[idx]` in register dest (all values are in registers)
    AGetChar { dest: u8, arr: u8, idx: u8 },
    /// Put the short at `arr[idx]` in register dest (all values are in registers)
    AGetShort { dest: u8, arr: u8, idx: u8 },
    /// Put the value of register 'from' in `arr[idx]` (all values are in registers)
    APut { from: u8, arr: u8, idx: u8 },
    /// Put the value of the register pair 'from' in `arr[idx]` (all values are in registers)
    APutWide { from: u8, arr: u8, idx: u8 },
    /// Put the object reference in 'from' in `arr[idx]` (all values are in registers)
    APutObject { from: u8, arr: u8, idx: u8 },
    /// Put the boolean in 'from' in `arr[idx]` (all values are in registers)
    APutBoolean { from: u8, arr: u8, idx: u8 },
    /// Put the byte in 'from' in `arr[idx]` (all values are in registers)
    APutByte { from: u8, arr: u8, idx: u8 },
    /// Put the char in 'from' in `arr[idx]` (all values are in registers)
    APutChar { from: u8, arr: u8, idx: u8 },
    /// Put the short in 'from' in `arr[idx]` (all values are in registers)
    APutShort { from: u8, arr: u8, idx: u8 },
    /// Put the value in 'to' in the instance field 'field' of 'obj' ('to' and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGet { to: u8, obj: u8, field: IdField },
    /// Put the value in the register pair 'to' in the instance field 'field' of 'obj' ('to'
    /// and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGetWide { to: u8, obj: u8, field: IdField },
    /// Put the object reference 'to' in the instance field 'field' of 'obj' ('to' and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGetObject { to: u8, obj: u8, field: IdField },
    /// Put the boolean in 'to' in the instance field 'field' of 'obj' ('to' and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGetBoolean { to: u8, obj: u8, field: IdField },
    /// Put the byte in 'to' in the instance field 'field' of 'obj' ('to' and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGetByte { to: u8, obj: u8, field: IdField },
    /// Put the char in 'to' in the instance field 'field' of 'obj' ('to' and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGetChar { to: u8, obj: u8, field: IdField },
    /// Put the short in 'to' in the instance field 'field' of 'obj' ('to' and 'obj' are register)
    /// The registers 'to' and 'obj' are idexed on 4 bits.
    IGetShort { to: u8, obj: u8, field: IdField },
    /// Put the value in the instance field 'field' of 'obj' in 'from' ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPut { from: u8, obj: u8, field: IdField },
    /// Put the value in the instance field 'field' of 'obj' in the register pair 'from'
    /// ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPutWide { from: u8, obj: u8, field: IdField },
    /// Put the object reference in the instance field 'field' of 'obj' in 'from' ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPutObject { from: u8, obj: u8, field: IdField },
    /// Put the boolean in the instance field 'field' of 'obj' in 'from' ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPutBoolean { from: u8, obj: u8, field: IdField },
    /// Put the byte in the instance field 'field' of 'obj' in 'from' ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPutByte { from: u8, obj: u8, field: IdField },
    /// Put the char in the instance field 'field' of 'obj' in 'from' ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPutChar { from: u8, obj: u8, field: IdField },
    /// Put the short in the instance field 'field' of 'obj' in 'from' ('from' and 'obj' are register)
    /// The registers 'from' and 'obj' are idexed on 4 bits.
    IPutShort { from: u8, obj: u8, field: IdField },
    /// Put the value in 'to' in the static field 'field' ('to' is a register)
    SGet { to: u8, field: IdField },
    /// Put the value in the register pair 'to' in the static field 'field' ('to' is a register)
    SGetWide { to: u8, field: IdField },
    /// Put the object reference 'to' in the static field 'field' ('to' is a register)
    SGetObject { to: u8, field: IdField },
    /// Put the boolean in 'to' in the static field 'field' ('to' is a register)
    SGetBoolean { to: u8, field: IdField },
    /// Put the byte in 'to' in the static field 'field' ('to' is a register)
    SGetByte { to: u8, field: IdField },
    /// Put the char in 'to' in the static field 'field' ('to' is a register)
    SGetChar { to: u8, field: IdField },
    /// Put the short in 'to' in the static field 'field' ('to' is a register)
    SGetShort { to: u8, field: IdField },
    /// Put the value in the static field 'field' in 'from' ('from' is a register)
    SPut { from: u8, field: IdField },
    /// Put the value in the static field 'field' in the register pair 'from'
    /// ('from' is a register)
    SPutWide { from: u8, field: IdField },
    /// Put the object reference in the static field 'field' in 'from' ('from' is a register)
    SPutObject { from: u8, field: IdField },
    /// Put the boolean in the static field 'field' in 'from' ('from' is a register)
    SPutBoolean { from: u8, field: IdField },
    /// Put the byte in the static field 'field' in 'from' ('from' is a register)
    SPutByte { from: u8, field: IdField },
    /// Put the char in the static field 'field' in 'from' ('from' is a register)
    SPutChar { from: u8, field: IdField },
    /// Put the short in the static field 'field' in 'from' ('from' is a register)
    SPutShort { from: u8, field: IdField },
    /// Call a normal virtual method.
    InvokeVirtual { method: IdMethod, args: Vec<u16> },
    /// Call the closest superclass's virtual method of a non interface class.
    InvokeSuper { method: IdMethod, args: Vec<u16> },
    /// Call a direct method (non static non overridable, like private).
    InvokeDirect { method: IdMethod, args: Vec<u16> },
    /// Call a static method.
    InvokeStatic { method: IdMethod, args: Vec<u16> },
    /// Call a interface method (method from an interface on an object whose class is unknown)
    InvokeInterface { method: IdMethod, args: Vec<u16> },
    /// Put -val in dest.
    /// `dest` and `val` are registered indexed on 4 bits.
    NegInt { dest: u8, val: u8 },
    /// Put ~val in dest
    /// `dest` and `val` are registered indexed on 4 bits.
    NotInt { dest: u8, val: u8 },
    /// Put -val in dest. dest and val are pair registers.
    /// `dest` and `val` are registered indexed on 4 bits.
    NegLong { dest: u8, val: u8 },
    /// Put ~val in dest. dest and val are pair registers.
    /// `dest` and `val` are registered indexed on 4 bits.
    NotLong { dest: u8, val: u8 },
    /// Put -val in dest.
    /// `dest` and `val` are registered indexed on 4 bits.
    NegFloat { dest: u8, val: u8 },
    /// Put -val in dest.
    /// `dest` and `val` are registered indexed on 4 bits.
    NegDouble { dest: u8, val: u8 },
    /// Convert copy val to dest an convert to long.
    /// TODO: dest is probably a pair register, but is val to?
    /// `dest` and `val` are registered indexed on 4 bits.
    IntToLong { dest: u8, val: u8 },
    /// Copy val to dest and convert the integer value to a float.
    /// `dest` and `val` are registered indexed on 4 bits.
    IntToFloat { dest: u8, val: u8 },
    /// Copy val to dest and convert the integer value to a double
    /// TODO: dest is probably a pair register, but is val to?
    /// `dest` and `val` are registered indexed on 4 bits.
    IntToDouble { dest: u8, val: u8 },
    /// Copy val to dest and convert the long value to an integer.
    /// TODO: val is probably a pair register, but is dest to?
    /// `dest` and `val` are registered indexed on 4 bits.
    LongToInt { dest: u8, val: u8 },
    /// Copy val to dest and convert the long value to a float.
    /// TODO: val is probably a pair register, but is dest to?
    /// `dest` and `val` are registered indexed on 4 bits.
    LongToFloat { dest: u8, val: u8 },
    /// Copy val to dest and convert the long value to a double.
    /// dest and val are pair registers.
    /// `dest` and `val` are registered indexed on 4 bits.
    LongToDouble { dest: u8, val: u8 },
    /// Copy val to dest and convert the float value to an integer.
    /// `dest` and `val` are registered indexed on 4 bits.
    FloatToInt { dest: u8, val: u8 },
    /// Copy val to dest and convert the float value to a long.
    /// TODO: dest is probably a pair register, but is val too?
    /// `dest` and `val` are registered indexed on 4 bits.
    FloatToLong { dest: u8, val: u8 },
    /// Copy val to dest and convert the float value to a double.
    /// TODO: dest is probably a pair register, but is val too?
    /// `dest` and `val` are registered indexed on 4 bits.
    FloatToDouble { dest: u8, val: u8 },
    /// Copy val to dest and convert the double value to an integer.
    /// TODO: val is probably a pair register, but is dest too?
    /// `dest` and `val` are registered indexed on 4 bits.
    DoubleToInt { dest: u8, val: u8 },
    /// Copy val to dest and convert the double value to a long.
    /// val and dest are pair registers.
    /// `dest` and `val` are registered indexed on 4 bits.
    DoubleToLong { dest: u8, val: u8 },
    /// Copy val to dest and convert the double value to a float.
    /// TODO: val is probably a pair register, but is dest too?
    /// `dest` and `val` are registered indexed on 4 bits.
    DoubleToFloat { dest: u8, val: u8 },
    /// Copy val to dest and convert the integer value to a byte.
    /// `dest` and `val` are registered indexed on 4 bits.
    IntToByte { dest: u8, val: u8 },
    /// Copy val to dest and convert the integer value to a char.
    /// `dest` and `val` are registered indexed on 4 bits.
    IntToChar { dest: u8, val: u8 },
    /// Copy val to dest and convert the integer value to a short.
    /// `dest` and `val` are registered indexed on 4 bits.
    IntToShort { dest: u8, val: u8 },
    /// Put a + b in dest.
    AddInt { dest: u8, b: u8, c: u8 },
    /// Put a - b in dest.
    SubInt { dest: u8, b: u8, c: u8 },
    /// Put a * b in dest.
    MulInt { dest: u8, b: u8, c: u8 },
    /// Put a / b in dest.
    DivInt { dest: u8, b: u8, c: u8 },
    /// Put a % b in dest.
    RemInt { dest: u8, b: u8, c: u8 },
    /// Put a & b in dest.
    AndInt { dest: u8, b: u8, c: u8 },
    /// Put a | b in dest.
    OrInt { dest: u8, b: u8, c: u8 },
    /// Put a ^ b in dest.
    XorInt { dest: u8, b: u8, c: u8 },
    /// Put a << b in dest.
    ShlInt { dest: u8, b: u8, c: u8 },
    /// Put a >> b (signed) in dest.
    ShrInt { dest: u8, b: u8, c: u8 },
    /// Put a >> b in dest.
    UshrInt { dest: u8, b: u8, c: u8 },
    /// Put a + b in dest.
    /// dest, b and c are register pairs
    AddLong { dest: u8, b: u8, c: u8 },
    /// Put a - b in dest.
    /// dest, b and c are register pairs
    SubLong { dest: u8, b: u8, c: u8 },
    /// Put a * b in dest.
    /// dest, b and c are register pairs
    MulLong { dest: u8, b: u8, c: u8 },
    /// Put a / b in dest.
    /// dest, b and c are register pairs
    DivLong { dest: u8, b: u8, c: u8 },
    /// Put a % b in dest.
    /// dest, b and c are register pairs
    RemLong { dest: u8, b: u8, c: u8 },
    /// Put a & b in dest.
    /// dest, b and c are register pairs
    AndLong { dest: u8, b: u8, c: u8 },
    /// Put a | b in dest.
    /// dest, b and c are register pairs
    OrLong { dest: u8, b: u8, c: u8 },
    /// Put a ^ b in dest.
    /// dest, b and c are register pairs
    XorLong { dest: u8, b: u8, c: u8 },
    /// Put a << b in dest.
    /// dest, and b are register pairs, and c is a single register
    ShlLong { dest: u8, b: u8, c: u8 },
    /// Put a >> b (signed) in dest.
    /// dest, and b are register pairs, and c is a single register
    ShrLong { dest: u8, b: u8, c: u8 },
    /// Put a >> b in dest.
    /// dest, and b are register pairs, and c is a single register
    UshrLong { dest: u8, b: u8, c: u8 },
    /// Put a + b in dest.
    AddFloat { dest: u8, b: u8, c: u8 },
    /// Put a - b in dest.
    SubFloat { dest: u8, b: u8, c: u8 },
    /// Put a * b in dest.
    MulFloat { dest: u8, b: u8, c: u8 },
    /// Put a / b in dest.
    DivFloat { dest: u8, b: u8, c: u8 },
    /// Put a % b in dest.
    RemFloat { dest: u8, b: u8, c: u8 },
    /// Put a + b in dest.
    /// dest, b and c are register pairs
    AddDouble { dest: u8, b: u8, c: u8 },
    /// Put a - b in dest.
    /// dest, b and c are register pairs
    SubDouble { dest: u8, b: u8, c: u8 },
    /// Put a * b in dest.
    /// dest, b and c are register pairs
    MulDouble { dest: u8, b: u8, c: u8 },
    /// Put a / b in dest.
    /// dest, b and c are register pairs
    DivDouble { dest: u8, b: u8, c: u8 },
    /// Put a % b in dest.
    /// dest, b and c are register pairs
    RemDouble { dest: u8, b: u8, c: u8 },
    /// Put dest + b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    AddInt2Addr { dest: u8, b: u8 },
    /// Put dest - b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    SubInt2Addr { dest: u8, b: u8 },
    /// Put dest * b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    MulInt2Addr { dest: u8, b: u8 },
    /// Put dest / b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    DivInt2Addr { dest: u8, b: u8 },
    /// Put dest % b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    RemInt2Addr { dest: u8, b: u8 },
    /// Put dest & b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    AndInt2Addr { dest: u8, b: u8 },
    /// Put dest | b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    OrInt2Addr { dest: u8, b: u8 },
    /// Put dest ^ b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    XorInt2Addr { dest: u8, b: u8 },
    /// Put dest << b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    ShlInt2Addr { dest: u8, b: u8 },
    /// Put dest >> b (signed) in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    ShrInt2Addr { dest: u8, b: u8 },
    /// Put dest >> b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    UshrInt2Addr { dest: u8, b: u8 },
    /// Put dest + b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    AddLong2Addr { dest: u8, b: u8 },
    /// Put dest - b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    SubLong2Addr { dest: u8, b: u8 },
    /// Put dest * b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    MulLong2Addr { dest: u8, b: u8 },
    /// Put dest / b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    DivLong2Addr { dest: u8, b: u8 },
    /// Put dest % b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    RemLong2Addr { dest: u8, b: u8 },
    /// Put dest & b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    AndLong2Addr { dest: u8, b: u8 },
    /// Put dest | b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    OrLong2Addr { dest: u8, b: u8 },
    /// Put dest ^ b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    XorLong2Addr { dest: u8, b: u8 },
    /// Put dest << b in dest.
    /// dest is a register pair, and b is a single register
    /// `dest` and `b` are registers indexed on 4 bits
    ShlLong2Addr { dest: u8, b: u8 },
    /// Put dest >> b (signed) in dest.
    /// dest is a register pair, and b is a single register
    /// `dest` and `b` are registers indexed on 4 bits
    ShrLong2Addr { dest: u8, b: u8 },
    /// Put dest >> b in dest.
    /// dest is a register pair, and b is a single register
    /// `dest` and `b` are registers indexed on 4 bits
    UshrLong2Addr { dest: u8, b: u8 },
    /// Put dest + b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    AddFloat2Addr { dest: u8, b: u8 },
    /// Put dest - b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    SubFloat2Addr { dest: u8, b: u8 },
    /// Put dest * b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    MulFloat2Addr { dest: u8, b: u8 },
    /// Put dest / b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    DivFloat2Addr { dest: u8, b: u8 },
    /// Put dest % b in dest.
    /// `dest` and `b` are registers indexed on 4 bits
    RemFloat2Addr { dest: u8, b: u8 },
    /// Put dest + b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    AddDouble2Addr { dest: u8, b: u8 },
    /// Put dest - b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    SubDouble2Addr { dest: u8, b: u8 },
    /// Put dest * b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    MulDouble2Addr { dest: u8, b: u8 },
    /// Put dest / b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    DivDouble2Addr { dest: u8, b: u8 },
    /// Put dest % b in dest.
    /// dest and b are register pairs
    /// `dest` and `b` are registers indexed on 4 bits
    RemDouble2Addr { dest: u8, b: u8 },
    /// Put b + lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    AddIntLit { dest: u8, b: u8, lit: i16 },
    /// Put lit - b in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    RsubIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b * lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    MulIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b / lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    DivIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b % lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    RemIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b & lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    AndIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b | lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    OrIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b ^ lit in dest.
    /// Either `dest` and `b` are registers indexed on 4 bits and lit is encoded in 16 bits
    /// or `dest` and `b` are registers indexed on 8 bits and lit is encoded in 8 bits
    XorIntLit { dest: u8, b: u8, lit: i16 },
    /// Put b << lit in dest.
    ShlIntLit { dest: u8, b: u8, lit: i8 },
    /// Put b >> lit (signed) in dest.
    ShrIntLit { dest: u8, b: u8, lit: i8 },
    /// Put b >> lit in dest.
    UshrIntLit { dest: u8, b: u8, lit: i8 },
    /// Call a polymorphic method.
    InvokePolymorphic {
        method: IdMethod,
        proto: IdMethodType,
        args: Vec<u16>,
    },
    /// Invoke a method from a call site.
    InvokeCustom { call_site: CallSite, args: Vec<u16> },
    /// Put contents reference to the method handle in the register.
    ConstMethodHandle { handle: MethodHandle, to: u8 },
    /// Put contents reference to the prototype in the register.
    ConstMethodType { proto: IdMethodType, to: u8 },
    /// Try block. It does not match an dalvik instruction but is derived from the code item struct.
    Try {
        end_label: String,
        /// The  list of exceptions and their associated handler label.
        ///
        ///
        /// The handler are sorted: if severat Exception Type match an exceptions, the
        /// the handler used is the first in the list.
        handlers: Vec<(IdType, String)>,
        default_handler: Option<String>,
    },
    /// Label marker. It does not match an dalvik instruction, but it's use as a marker for a
    /// jump destination.
    Label { name: String },
    /// Debug information. Define a local variable associated with a register.
    DebugLocal {
        reg: u32,
        name: Option<DexString>,
        type_: Option<IdType>,
        signature: Option<DexString>,
    },
    /// Debug information. Undefine a local variable associated with a register.
    DebugEndLocal { reg: u32 },
    /// Debug information. Indicate the end of the prologue.
    DebugEndPrologue {},
    /// Debug information. Indicate the beginning of the Epilogue
    DebugBeginEpilogue {},
    /// Debug information. Indicate the source file of the following instructions.
    DebugSourceFile { file: Option<DexString> },
    /// Debug information. Indicate the line number of the following instructions.
    DebugLine { number: usize },
}

impl<V: Visitor> Visitable<V> for Instruction {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        // TODO wildcard?
        match self {
            Self::Nop {} => Ok(()),
            Self::Move { from: _, to: _ } => Ok(()),
            Self::MoveWide { from: _, to: _ } => Ok(()),
            Self::MoveObject { from: _, to: _ } => Ok(()),
            Self::MoveResult { to: _ } => Ok(()),
            Self::MoveResultWide { to: _ } => Ok(()),
            Self::MoveException { to: _ } => Ok(()),
            Self::MoveResultObject { to: _ } => Ok(()),
            Self::ReturnVoid {} => Ok(()),
            Self::Return { reg: _ } => Ok(()),
            Self::ReturnWide { reg: _ } => Ok(()),
            Self::ReturnObject { reg: _ } => Ok(()),
            Self::Const { reg: _, lit: _ } => Ok(()),
            Self::ConstWide { reg: _, lit: _ } => Ok(()),
            Self::ConstString { reg: _, lit } => v.visit_string(lit),
            Self::ConstClass { reg: _, lit } => v.visit_type(lit),
            Self::MonitorEnter { reg: _ } => Ok(()),
            Self::MonitorExit { reg: _ } => Ok(()),
            Self::CheckCast { reg: _, lit } => v.visit_type(lit),
            Self::InstanceOf {
                dest: _,
                obj: _,
                lit,
            } => v.visit_type(lit),
            Self::ArrayLength { dest: _, arr: _ } => Ok(()),
            Self::NewInstance { reg: _, lit } => v.visit_type(lit),
            Self::NewArray {
                reg: _,
                size_reg: _,
                lit,
            } => v.visit_type(lit),
            Self::FilledNewArray {
                type_,
                reg_values: _,
            } => v.visit_type(type_),
            Self::FillArrayData {
                arr: _,
                elt_width: _,
                data: _,
            } => Ok(()),
            Self::Throw { reg: _ } => Ok(()),
            Self::Goto { label: _ } => Ok(()),
            Self::Switch {
                reg: _,
                branches: _,
            } => Ok(()),
            Self::CmpLFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::CmpGFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::CmpLDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::CmpGDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::CmpLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::IfEq {
                a: _,
                b: _,
                label: _,
            } => Ok(()),
            Self::IfNe {
                a: _,
                b: _,
                label: _,
            } => Ok(()),
            Self::IfLt {
                a: _,
                b: _,
                label: _,
            } => Ok(()),
            Self::IfGe {
                a: _,
                b: _,
                label: _,
            } => Ok(()),
            Self::IfGt {
                a: _,
                b: _,
                label: _,
            } => Ok(()),
            Self::IfLe {
                a: _,
                b: _,
                label: _,
            } => Ok(()),
            Self::IfEqZ { a: _, label: _ } => Ok(()),
            Self::IfNeZ { a: _, label: _ } => Ok(()),
            Self::IfLtZ { a: _, label: _ } => Ok(()),
            Self::IfGeZ { a: _, label: _ } => Ok(()),
            Self::IfGtZ { a: _, label: _ } => Ok(()),
            Self::IfLeZ { a: _, label: _ } => Ok(()),
            Self::AGet {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::AGetWide {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::AGetObject {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::AGetBoolean {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::AGetByte {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::AGetChar {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::AGetShort {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APut {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APutWide {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APutObject {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APutBoolean {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APutByte {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APutChar {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::APutShort {
                from: _,
                arr: _,
                idx: _,
            } => Ok(()),
            Self::IGet {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IGetWide {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IGetObject {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IGetBoolean {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IGetByte {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IGetChar {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IGetShort {
                to: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPut {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPutWide {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPutObject {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPutBoolean {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPutByte {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPutChar {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::IPutShort {
                from: _,
                obj: _,
                field,
            } => v.visit_field_id(field),
            Self::SGet { to: _, field } => v.visit_field_id(field),
            Self::SGetWide { to: _, field } => v.visit_field_id(field),
            Self::SGetObject { to: _, field } => v.visit_field_id(field),
            Self::SGetBoolean { to: _, field } => v.visit_field_id(field),
            Self::SGetByte { to: _, field } => v.visit_field_id(field),
            Self::SGetChar { to: _, field } => v.visit_field_id(field),
            Self::SGetShort { to: _, field } => v.visit_field_id(field),
            Self::SPut { from: _, field } => v.visit_field_id(field),
            Self::SPutWide { from: _, field } => v.visit_field_id(field),
            Self::SPutObject { from: _, field } => v.visit_field_id(field),
            Self::SPutBoolean { from: _, field } => v.visit_field_id(field),
            Self::SPutByte { from: _, field } => v.visit_field_id(field),
            Self::SPutChar { from: _, field } => v.visit_field_id(field),
            Self::SPutShort { from: _, field } => v.visit_field_id(field),
            Self::InvokeVirtual { method, args: _ } => v.visit_method_id(method),
            Self::InvokeSuper { method, args: _ } => v.visit_method_id(method),
            Self::InvokeDirect { method, args: _ } => v.visit_method_id(method),
            Self::InvokeStatic { method, args: _ } => v.visit_method_id(method),
            Self::InvokeInterface { method, args: _ } => v.visit_method_id(method),
            Self::NegInt { dest: _, val: _ } => Ok(()),
            Self::NotInt { dest: _, val: _ } => Ok(()),
            Self::NegLong { dest: _, val: _ } => Ok(()),
            Self::NotLong { dest: _, val: _ } => Ok(()),
            Self::NegFloat { dest: _, val: _ } => Ok(()),
            Self::NegDouble { dest: _, val: _ } => Ok(()),
            Self::IntToLong { dest: _, val: _ } => Ok(()),
            Self::IntToFloat { dest: _, val: _ } => Ok(()),
            Self::IntToDouble { dest: _, val: _ } => Ok(()),
            Self::LongToInt { dest: _, val: _ } => Ok(()),
            Self::LongToFloat { dest: _, val: _ } => Ok(()),
            Self::LongToDouble { dest: _, val: _ } => Ok(()),
            Self::FloatToInt { dest: _, val: _ } => Ok(()),
            Self::FloatToLong { dest: _, val: _ } => Ok(()),
            Self::FloatToDouble { dest: _, val: _ } => Ok(()),
            Self::DoubleToInt { dest: _, val: _ } => Ok(()),
            Self::DoubleToLong { dest: _, val: _ } => Ok(()),
            Self::DoubleToFloat { dest: _, val: _ } => Ok(()),
            Self::IntToByte { dest: _, val: _ } => Ok(()),
            Self::IntToChar { dest: _, val: _ } => Ok(()),
            Self::IntToShort { dest: _, val: _ } => Ok(()),
            Self::AddInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::SubInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::MulInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::DivInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::RemInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::AndInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::OrInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::XorInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::ShlInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::ShrInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::UshrInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::AddLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::SubLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::MulLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::DivLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::RemLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::AndLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::OrLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::XorLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::ShlLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::ShrLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::UshrLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::AddFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::SubFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::MulFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::DivFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::RemFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::AddDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::SubDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::MulDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::DivDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::RemDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(()),
            Self::AddInt2Addr { dest: _, b: _ } => Ok(()),
            Self::SubInt2Addr { dest: _, b: _ } => Ok(()),
            Self::MulInt2Addr { dest: _, b: _ } => Ok(()),
            Self::DivInt2Addr { dest: _, b: _ } => Ok(()),
            Self::RemInt2Addr { dest: _, b: _ } => Ok(()),
            Self::AndInt2Addr { dest: _, b: _ } => Ok(()),
            Self::OrInt2Addr { dest: _, b: _ } => Ok(()),
            Self::XorInt2Addr { dest: _, b: _ } => Ok(()),
            Self::ShlInt2Addr { dest: _, b: _ } => Ok(()),
            Self::ShrInt2Addr { dest: _, b: _ } => Ok(()),
            Self::UshrInt2Addr { dest: _, b: _ } => Ok(()),
            Self::AddLong2Addr { dest: _, b: _ } => Ok(()),
            Self::SubLong2Addr { dest: _, b: _ } => Ok(()),
            Self::MulLong2Addr { dest: _, b: _ } => Ok(()),
            Self::DivLong2Addr { dest: _, b: _ } => Ok(()),
            Self::RemLong2Addr { dest: _, b: _ } => Ok(()),
            Self::AndLong2Addr { dest: _, b: _ } => Ok(()),
            Self::OrLong2Addr { dest: _, b: _ } => Ok(()),
            Self::XorLong2Addr { dest: _, b: _ } => Ok(()),
            Self::ShlLong2Addr { dest: _, b: _ } => Ok(()),
            Self::ShrLong2Addr { dest: _, b: _ } => Ok(()),
            Self::UshrLong2Addr { dest: _, b: _ } => Ok(()),
            Self::AddFloat2Addr { dest: _, b: _ } => Ok(()),
            Self::SubFloat2Addr { dest: _, b: _ } => Ok(()),
            Self::MulFloat2Addr { dest: _, b: _ } => Ok(()),
            Self::DivFloat2Addr { dest: _, b: _ } => Ok(()),
            Self::RemFloat2Addr { dest: _, b: _ } => Ok(()),
            Self::AddDouble2Addr { dest: _, b: _ } => Ok(()),
            Self::SubDouble2Addr { dest: _, b: _ } => Ok(()),
            Self::MulDouble2Addr { dest: _, b: _ } => Ok(()),
            Self::DivDouble2Addr { dest: _, b: _ } => Ok(()),
            Self::RemDouble2Addr { dest: _, b: _ } => Ok(()),
            Self::AddIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::RsubIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::MulIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::DivIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::RemIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::AndIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::OrIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::XorIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::ShlIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::ShrIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::UshrIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(()),
            Self::InvokePolymorphic {
                method,
                proto,
                args: _,
            } => {
                v.visit_method_id(method)?;
                v.visit_method_type(proto)
            }
            Self::InvokeCustom { call_site, args: _ } => v.visit_call_site(call_site),
            Self::ConstMethodHandle { handle, to: _ } => v.visit_method_handle(handle),
            Self::ConstMethodType { proto, to: _ } => v.visit_method_type(proto),
            Self::Try {
                end_label: _,
                handlers,
                default_handler: _,
            } => {
                for (ty, _) in handlers {
                    v.visit_type(ty)?;
                }
                Ok(())
            }
            Self::Label { name: _ } => Ok(()),
            Self::DebugLocal {
                reg: _,
                name,
                type_,
                signature,
            } => {
                if let Some(name) = name {
                    v.visit_string(name)?;
                }
                if let Some(type_) = type_ {
                    v.visit_type(type_)?;
                }
                if let Some(signature) = signature {
                    v.visit_string(signature)?;
                }
                Ok(())
            }
            Self::DebugEndLocal { reg: _ } => Ok(()),
            Self::DebugEndPrologue {} => Ok(()),
            Self::DebugBeginEpilogue {} => Ok(()),
            Self::DebugSourceFile { file: Some(file) } => v.visit_string(file),
            Self::DebugSourceFile { file: None } => Ok(()),
            Self::DebugLine { number: _ } => Ok(()),
        }
    }
}

impl<V: VisitorMut> VisitableMut<V> for Instruction {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        match self {
            Self::Nop {} => Ok(self),
            Self::Move { from: _, to: _ } => Ok(self),
            Self::MoveWide { from: _, to: _ } => Ok(self),
            Self::MoveObject { from: _, to: _ } => Ok(self),
            Self::MoveResult { to: _ } => Ok(self),
            Self::MoveResultWide { to: _ } => Ok(self),
            Self::MoveException { to: _ } => Ok(self),
            Self::MoveResultObject { to: _ } => Ok(self),
            Self::ReturnVoid {} => Ok(self),
            Self::Return { reg: _ } => Ok(self),
            Self::ReturnWide { reg: _ } => Ok(self),
            Self::ReturnObject { reg: _ } => Ok(self),
            Self::Const { reg: _, lit: _ } => Ok(self),
            Self::ConstWide { reg: _, lit: _ } => Ok(self),
            Self::ConstString { reg, lit } => Ok(Self::ConstString {
                reg,
                lit: v.visit_string(lit)?,
            }),
            Self::ConstClass { reg, lit } => Ok(Self::ConstClass {
                reg,
                lit: v.visit_type(lit)?,
            }),
            Self::MonitorEnter { reg: _ } => Ok(self),
            Self::MonitorExit { reg: _ } => Ok(self),
            Self::CheckCast { reg, lit } => Ok(Self::CheckCast {
                reg,
                lit: v.visit_type(lit)?,
            }),
            Self::InstanceOf { dest, obj, lit } => Ok(Self::InstanceOf {
                dest,
                obj,
                lit: v.visit_type(lit)?,
            }),
            Self::ArrayLength { dest: _, arr: _ } => Ok(self),
            Self::NewInstance { reg, lit } => Ok(Self::NewInstance {
                reg,
                lit: v.visit_type(lit)?,
            }),
            Self::NewArray { reg, size_reg, lit } => Ok(Self::NewArray {
                reg,
                size_reg,
                lit: v.visit_type(lit)?,
            }),
            Self::FilledNewArray { type_, reg_values } => Ok(Self::FilledNewArray {
                type_: v.visit_type(type_)?,
                reg_values,
            }),
            Self::FillArrayData {
                arr: _,
                elt_width: _,
                data: _,
            } => Ok(self),
            Self::Throw { reg: _ } => Ok(self),
            Self::Goto { label: _ } => Ok(self),
            Self::Switch {
                reg: _,
                branches: _,
            } => Ok(self),
            Self::CmpLFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::CmpGFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::CmpLDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::CmpGDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::CmpLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::IfEq {
                a: _,
                b: _,
                label: _,
            } => Ok(self),
            Self::IfNe {
                a: _,
                b: _,
                label: _,
            } => Ok(self),
            Self::IfLt {
                a: _,
                b: _,
                label: _,
            } => Ok(self),
            Self::IfGe {
                a: _,
                b: _,
                label: _,
            } => Ok(self),
            Self::IfGt {
                a: _,
                b: _,
                label: _,
            } => Ok(self),
            Self::IfLe {
                a: _,
                b: _,
                label: _,
            } => Ok(self),
            Self::IfEqZ { a: _, label: _ } => Ok(self),
            Self::IfNeZ { a: _, label: _ } => Ok(self),
            Self::IfLtZ { a: _, label: _ } => Ok(self),
            Self::IfGeZ { a: _, label: _ } => Ok(self),
            Self::IfGtZ { a: _, label: _ } => Ok(self),
            Self::IfLeZ { a: _, label: _ } => Ok(self),
            Self::AGet {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::AGetWide {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::AGetObject {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::AGetBoolean {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::AGetByte {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::AGetChar {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::AGetShort {
                dest: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APut {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APutWide {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APutObject {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APutBoolean {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APutByte {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APutChar {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::APutShort {
                from: _,
                arr: _,
                idx: _,
            } => Ok(self),
            Self::IGet { to, obj, field } => Ok(Self::IGet {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IGetWide { to, obj, field } => Ok(Self::IGetWide {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IGetObject { to, obj, field } => Ok(Self::IGetObject {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IGetBoolean { to, obj, field } => Ok(Self::IGetBoolean {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IGetByte { to, obj, field } => Ok(Self::IGetByte {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IGetChar { to, obj, field } => Ok(Self::IGetChar {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IGetShort { to, obj, field } => Ok(Self::IGetShort {
                to,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPut { from, obj, field } => Ok(Self::IPut {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPutWide { from, obj, field } => Ok(Self::IPutWide {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPutObject { from, obj, field } => Ok(Self::IPutObject {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPutBoolean { from, obj, field } => Ok(Self::IPutBoolean {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPutByte { from, obj, field } => Ok(Self::IPutByte {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPutChar { from, obj, field } => Ok(Self::IPutChar {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::IPutShort { from, obj, field } => Ok(Self::IPutShort {
                from,
                obj,
                field: v.visit_field_id(field)?,
            }),
            Self::SGet { to, field } => Ok(Self::SGet {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SGetWide { to, field } => Ok(Self::SGetWide {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SGetObject { to, field } => Ok(Self::SGetObject {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SGetBoolean { to, field } => Ok(Self::SGetBoolean {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SGetByte { to, field } => Ok(Self::SGetByte {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SGetChar { to, field } => Ok(Self::SGetChar {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SGetShort { to, field } => Ok(Self::SGetShort {
                to,
                field: v.visit_field_id(field)?,
            }),
            Self::SPut { from, field } => Ok(Self::SPut {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::SPutWide { from, field } => Ok(Self::SPutWide {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::SPutObject { from, field } => Ok(Self::SPutObject {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::SPutBoolean { from, field } => Ok(Self::SPutBoolean {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::SPutByte { from, field } => Ok(Self::SPutByte {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::SPutChar { from, field } => Ok(Self::SPutChar {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::SPutShort { from, field } => Ok(Self::SPutShort {
                from,
                field: v.visit_field_id(field)?,
            }),
            Self::InvokeVirtual { method, args } => Ok(Self::InvokeVirtual {
                method: v.visit_method_id(method)?,
                args,
            }),
            Self::InvokeSuper { method, args } => Ok(Self::InvokeSuper {
                method: v.visit_method_id(method)?,
                args,
            }),
            Self::InvokeDirect { method, args } => Ok(Self::InvokeDirect {
                method: v.visit_method_id(method)?,
                args,
            }),
            Self::InvokeStatic { method, args } => Ok(Self::InvokeStatic {
                method: v.visit_method_id(method)?,
                args,
            }),
            Self::InvokeInterface { method, args } => Ok(Self::InvokeInterface {
                method: v.visit_method_id(method)?,
                args,
            }),
            Self::NegInt { dest: _, val: _ } => Ok(self),
            Self::NotInt { dest: _, val: _ } => Ok(self),
            Self::NegLong { dest: _, val: _ } => Ok(self),
            Self::NotLong { dest: _, val: _ } => Ok(self),
            Self::NegFloat { dest: _, val: _ } => Ok(self),
            Self::NegDouble { dest: _, val: _ } => Ok(self),
            Self::IntToLong { dest: _, val: _ } => Ok(self),
            Self::IntToFloat { dest: _, val: _ } => Ok(self),
            Self::IntToDouble { dest: _, val: _ } => Ok(self),
            Self::LongToInt { dest: _, val: _ } => Ok(self),
            Self::LongToFloat { dest: _, val: _ } => Ok(self),
            Self::LongToDouble { dest: _, val: _ } => Ok(self),
            Self::FloatToInt { dest: _, val: _ } => Ok(self),
            Self::FloatToLong { dest: _, val: _ } => Ok(self),
            Self::FloatToDouble { dest: _, val: _ } => Ok(self),
            Self::DoubleToInt { dest: _, val: _ } => Ok(self),
            Self::DoubleToLong { dest: _, val: _ } => Ok(self),
            Self::DoubleToFloat { dest: _, val: _ } => Ok(self),
            Self::IntToByte { dest: _, val: _ } => Ok(self),
            Self::IntToChar { dest: _, val: _ } => Ok(self),
            Self::IntToShort { dest: _, val: _ } => Ok(self),
            Self::AddInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::SubInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::MulInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::DivInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::RemInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::AndInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::OrInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::XorInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::ShlInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::ShrInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::UshrInt {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::AddLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::SubLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::MulLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::DivLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::RemLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::AndLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::OrLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::XorLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::ShlLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::ShrLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::UshrLong {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::AddFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::SubFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::MulFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::DivFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::RemFloat {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::AddDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::SubDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::MulDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::DivDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::RemDouble {
                dest: _,
                b: _,
                c: _,
            } => Ok(self),
            Self::AddInt2Addr { dest: _, b: _ } => Ok(self),
            Self::SubInt2Addr { dest: _, b: _ } => Ok(self),
            Self::MulInt2Addr { dest: _, b: _ } => Ok(self),
            Self::DivInt2Addr { dest: _, b: _ } => Ok(self),
            Self::RemInt2Addr { dest: _, b: _ } => Ok(self),
            Self::AndInt2Addr { dest: _, b: _ } => Ok(self),
            Self::OrInt2Addr { dest: _, b: _ } => Ok(self),
            Self::XorInt2Addr { dest: _, b: _ } => Ok(self),
            Self::ShlInt2Addr { dest: _, b: _ } => Ok(self),
            Self::ShrInt2Addr { dest: _, b: _ } => Ok(self),
            Self::UshrInt2Addr { dest: _, b: _ } => Ok(self),
            Self::AddLong2Addr { dest: _, b: _ } => Ok(self),
            Self::SubLong2Addr { dest: _, b: _ } => Ok(self),
            Self::MulLong2Addr { dest: _, b: _ } => Ok(self),
            Self::DivLong2Addr { dest: _, b: _ } => Ok(self),
            Self::RemLong2Addr { dest: _, b: _ } => Ok(self),
            Self::AndLong2Addr { dest: _, b: _ } => Ok(self),
            Self::OrLong2Addr { dest: _, b: _ } => Ok(self),
            Self::XorLong2Addr { dest: _, b: _ } => Ok(self),
            Self::ShlLong2Addr { dest: _, b: _ } => Ok(self),
            Self::ShrLong2Addr { dest: _, b: _ } => Ok(self),
            Self::UshrLong2Addr { dest: _, b: _ } => Ok(self),
            Self::AddFloat2Addr { dest: _, b: _ } => Ok(self),
            Self::SubFloat2Addr { dest: _, b: _ } => Ok(self),
            Self::MulFloat2Addr { dest: _, b: _ } => Ok(self),
            Self::DivFloat2Addr { dest: _, b: _ } => Ok(self),
            Self::RemFloat2Addr { dest: _, b: _ } => Ok(self),
            Self::AddDouble2Addr { dest: _, b: _ } => Ok(self),
            Self::SubDouble2Addr { dest: _, b: _ } => Ok(self),
            Self::MulDouble2Addr { dest: _, b: _ } => Ok(self),
            Self::DivDouble2Addr { dest: _, b: _ } => Ok(self),
            Self::RemDouble2Addr { dest: _, b: _ } => Ok(self),
            Self::AddIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::RsubIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::MulIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::DivIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::RemIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::AndIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::OrIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::XorIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::ShlIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::ShrIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::UshrIntLit {
                dest: _,
                b: _,
                lit: _,
            } => Ok(self),
            Self::InvokePolymorphic {
                method,
                proto,
                args,
            } => Ok(Self::InvokePolymorphic {
                method: v.visit_method_id(method)?,
                proto: v.visit_method_type(proto)?,
                args,
            }),
            Self::InvokeCustom { call_site, args } => Ok(Self::InvokeCustom {
                call_site: v.visit_call_site(call_site)?,
                args,
            }),
            Self::ConstMethodHandle { handle, to } => Ok(Self::ConstMethodHandle {
                handle: v.visit_method_handle(handle)?,
                to,
            }),
            Self::ConstMethodType { proto, to } => Ok(Self::ConstMethodType {
                proto: v.visit_method_type(proto)?,
                to,
            }),
            Self::Try {
                end_label,
                handlers,
                default_handler,
            } => Ok(Self::Try {
                end_label,
                handlers: handlers
                    .into_iter()
                    .map(|(ty, lab)| v.visit_type(ty).map(|ty| (ty, lab)))
                    .collect::<Result<_>>()?,
                default_handler,
            }),
            Self::Label { name: _ } => Ok(self),
            Self::DebugLocal {
                reg,
                name,
                type_,
                signature,
            } => Ok(Self::DebugLocal {
                reg,
                name: name.map(|name| v.visit_string(name)).transpose()?,
                type_: type_.map(|type_| v.visit_type(type_)).transpose()?,
                signature: signature
                    .map(|signature| v.visit_string(signature))
                    .transpose()?,
            }),
            Self::DebugEndLocal { reg: _ } => Ok(self),
            Self::DebugEndPrologue {} => Ok(self),
            Self::DebugBeginEpilogue {} => Ok(self),
            Self::DebugSourceFile { file: Some(file) } => v
                .visit_string(file)
                .map(|file| Self::DebugSourceFile { file: Some(file) }),
            Self::DebugSourceFile { file: None } => Ok(self),
            Self::DebugLine { number: _ } => Ok(self),
        }
    }
}

macro_rules! sanity_check_4_bit_reg {
    ($ins_name:tt, $r1:ident, $r2:ident) => {{
        if $r1 & 0b1111_0000 != 0 {
            Err(anyhow!(
                "{} uses registers indexed on 4 bits, found {}",
                $ins_name,
                $r1
            ))
        } else if $r2 & &0b1111_0000 != 0 {
            Err(anyhow!(
                "{} uses registers indexed on 4 bits, found {}",
                $ins_name,
                $r1
            ))
        } else {
            Ok(())
        }
    }};
}

macro_rules! sanity_check_22b_or_22s {
    ($ins_name:tt, $r1:ident, $r2:ident, $lit:ident) => {{
        let mut reg_on_4_bit = true;
        let mut lit_on_8_bits = true;
        if $r1 & 0b1111_0000 != 0 {
            reg_on_4_bit = false;
        }
        if $r2 & 0b1111_0000 != 0 {
            reg_on_4_bit = false;
        }
        if *$lit < I8_MIN_AS_I16 || *$lit > I8_MAX_AS_I16 {
            lit_on_8_bits = false;
        }
        if !reg_on_4_bit && !lit_on_8_bits {
            Err(anyhow!(
                "{}/lit uses either registers indexed on 4 bits, and a literal \
                 encoded on 16 bits ({}/lit16), or registers indexed on 8 bits and \
                 a literal encoded on 8 bits ({}/lit8). Found reg {} and {}, and lit \
                 {}",
                $ins_name,
                $ins_name,
                $ins_name,
                $r1,
                $r2,
                $lit,
            ))
        } else {
            Ok(())
        }
    }};
}

macro_rules! raw_ins_if {
    ($ins_op:tt, $label_addrs:ident, $label:ident, $addr:ident, $r1:ident, $r2:ident) => {{
        let label_addr = $label_addrs.get($label).ok_or(anyhow!(
            "Label {} not found in code, but found `if` with this label",
            $label,
        ))?;
        let branch_offset = *label_addr as i32 - $addr as i32;
        if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
            bail!("Found an `if` that jump to far from the instruction",);
        }
        Ok(InsFormat::Format22T {
            op: $ins_op,
            va: *$r1,
            vb: *$r2,
            c: branch_offset as i16,
        })
    }};
}

macro_rules! raw_ins_ifz {
    ($ins_op:tt, $label_addrs:ident, $label:ident, $addr:ident, $r1:ident) => {{
        let label_addr = $label_addrs.get($label).ok_or(anyhow!(
            "Label {} not found in code, but found `if` with this label",
            $label,
        ))?;
        let branch_offset = *label_addr as i32 - $addr as i32;
        if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
            bail!("Found an `if` that jump to far from the instruction",);
        }
        Ok(InsFormat::Format21T {
            op: $ins_op,
            va: *$r1,
            b: branch_offset as i16,
        })
    }};
}

macro_rules! raw_ins_iget_put {
    ($ins_op:tt, $field_ids:ident, $field:ident, $r1:ident, $r2:ident) => {{
        let field_idx = $field_ids.get($field).ok_or(anyhow!(
            "Field {} (field of class {}) not found in dex builder",
            $field.__repr__(),
            $field.class_.__repr__(),
        ))?;
        Ok(InsFormat::Format22C {
            op: $ins_op,
            va: *$r1,
            vb: *$r2,
            c: *field_idx as u16,
        })
    }};
}

macro_rules! raw_ins_sget_put {
    ($ins_op:tt, $field_ids:ident, $field:ident, $r1:ident) => {{
        let field_idx = $field_ids.get($field).ok_or(anyhow!(
            "Field {} (field of class {}) not found in dex builder",
            $field.__repr__(),
            $field.class_.__repr__(),
        ))?;
        Ok(InsFormat::Format21C {
            op: $ins_op,
            va: *$r1,
            b: *field_idx as u16,
        })
    }};
}

macro_rules! raw_ins_invoke {
    ($ins_op_35c:tt, $ins_op_3rc:tt, $method_ids:ident, $meth:ident, $args:ident) => {{
        let meth_idx = $method_ids.get($meth).ok_or(anyhow!(
            "Method {} (method of class {}) not found in dex builder",
            $meth.__repr__(),
            $meth.class_.__repr__(),
        ))?;
        debug_assert!(
            *meth_idx <= u16::MAX as usize,
            "methode id too big for invoke instruction"
        );
        let meth_idx = *meth_idx as u16;
        let mut last = None;
        let mut first = None;
        let mut consec = true;
        let mut four_bites = true;
        let len = $args.len();
        for r in $args.iter().cloned() {
            if first.is_none() {
                first = Some(r);
            }
            if let Some(last) = last {
                if r != last + 1 {
                    consec = false;
                }
            }
            if r & 0b1111_0000 != 0 {
                four_bites = false;
            }
            last = Some(r);
        }
        if four_bites && len <= 5 {
            let mut regs = vec![];
            for reg in $args.iter().cloned() {
                regs.push(reg);
            }
            while regs.len() != 5 {
                regs.push(0);
            }
            let [vc, vd, ve, vf, vg]: [u8; 5] = regs
                .into_iter()
                .map(|r| r as u8)
                .collect::<Vec<_>>()
                .try_into()
                .ok()
                .unwrap();
            let a = $args.len() as u8;
            Ok(InsFormat::Format35C {
                op: $ins_op_35c,
                a,
                vc,
                ve,
                vd,
                vf,
                vg,
                b: meth_idx,
            })
        } else if consec && len <= 255 {
            let a = $args.len() as u8;
            let vc = first.unwrap_or(0);
            Ok(InsFormat::Format3RC {
                op: $ins_op_3rc,
                a,
                vc,
                b: meth_idx,
            })
        } else {
            // Not supposed to happend with a sanitized invoke
            Err(anyhow!("Invalid Invoke instruction"))
        }
    }};
}

#[cfg_attr(feature = "python", pymethods)]
impl Instruction {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    /// Test if the instruction is a dalvik instruction or a pseudo instruction like a label or a
    /// debug info.
    pub fn is_pseudo_ins(&self) -> bool {
        matches!(
            self,
            Self::Try { .. }
                | Self::Label { .. }
                | Self::DebugLocal { .. }
                | Self::DebugEndLocal { .. }
                | Self::DebugEndPrologue {}
                | Self::DebugBeginEpilogue {}
                | Self::DebugSourceFile { .. }
                | Self::DebugLine { .. }
        )
    }

    pub fn __str__(&self) -> String {
        match self {
            Self::Nop {} => "nop".into(),
            Self::Move { from, to } => {
                format!("move {} {}", to, from)
            }
            Self::MoveWide { from, to } => {
                format!("move-wide {} {}", to, from)
            }
            Self::MoveObject { from, to } => {
                format!("move-object {} {}", to, from)
            }
            Self::MoveResult { to } => {
                format!("move-result {}", to)
            }
            Self::MoveResultWide { to } => {
                format!("move-result-wide {}", to)
            }
            Self::MoveResultObject { to } => {
                format!("move-result-object {}", to)
            }
            Self::MoveException { to } => {
                format!("move-exception {}", to)
            }
            Self::ReturnVoid {} => "return-void".into(),
            Self::Return { reg } => {
                format!("return {}", reg)
            }
            Self::ReturnWide { reg } => {
                format!("return-wide {}", reg)
            }
            Self::ReturnObject { reg } => {
                format!("return-object {}", reg)
            }
            Self::Const { lit, reg } => {
                format!("const {} {}", reg, lit)
            }
            Self::ConstWide { lit, reg } => {
                format!("const-wide {} {}", reg, lit)
            }
            Self::ConstString { lit, reg } => {
                format!("const-string {} \"{}\"", reg, lit.__str__())
            }
            Self::ConstClass { lit, reg } => {
                format!("const-class {} \"{}\"", reg, lit.__str__())
            }
            Self::MonitorEnter { reg } => {
                format!("monitor-enter {}", reg)
            }
            Self::MonitorExit { reg } => {
                format!("monitor-exit {}", reg)
            }
            Self::CheckCast { lit, reg } => {
                format!("check-cast {} {}", reg, lit.__str__())
            }
            Self::InstanceOf { lit, dest, obj } => {
                format!("instance-of {} {} {}", dest, obj, lit.__str__())
            }
            Self::ArrayLength { arr, dest } => {
                format!("array-length{} {}", dest, arr,)
            }
            Self::NewInstance { lit, reg } => {
                format!("new-instance {} {}", reg, lit.__str__())
            }
            Self::NewArray { lit, size_reg, reg } => {
                format!("new-array {} {} {}", reg, size_reg, lit.__str__())
            }
            Self::FilledNewArray { reg_values, type_ } => {
                let args = if reg_values.len() >= 5 {
                    format!("{} .. {}", reg_values[0], reg_values[reg_values.len() - 1])
                } else {
                    reg_values
                        .iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("filled-new-array {{{}}} {}", args, type_.__str__())
            }
            Self::FillArrayData {
                arr,
                data,
                elt_width,
            } => {
                let data: String = if data.len() / *elt_width as usize == 0 {
                    "".into()
                } else if data.len() / *elt_width as usize <= 2 {
                    let mut arr = "".into();
                    for (i, v) in data.iter().enumerate() {
                        if i == 0 {
                            arr += "0x"
                        } else if i % *elt_width as usize == 0 {
                            arr += " 0x"
                        }
                        arr += format!("{v:02x}").as_str();
                    }
                    arr
                } else {
                    let mut arr = "0x".into();
                    for v in &data[..*elt_width as usize] {
                        arr += format!("{v:02x}").as_str();
                    }
                    arr += " ... 0x";
                    for v in &data[data.len() - *elt_width as usize..] {
                        arr += format!("{v:02x}").as_str();
                    }

                    arr
                };
                format!("fill-array-data {} {}", arr, data)
            }
            Self::Throw { reg } => {
                format!("throw {}", reg)
            }
            Self::Goto { label } => {
                format!("goto {}", label)
            }
            Self::Switch { branches, reg } => {
                let mut branches_str: String = "".into();
                let mut branches: Vec<_> = branches.iter().collect();
                branches.sort_by_key(|(key, _)| **key);
                for (key, label) in branches {
                    branches_str += &format!("\n    {key}: goto {label}");
                }
                format!("switch {} {}", reg, branches_str)
            }
            Self::CmpLFloat { b, dest, c } => {
                format!("cmpl-float {} {} {}", dest, b, c)
            }
            Self::CmpGFloat { b, dest, c } => {
                format!("cmpg-float {} {} {}", dest, b, c)
            }
            Self::CmpLDouble { b, dest, c } => {
                format!("cmpl-double {} {} {}", dest, b, c)
            }
            Self::CmpGDouble { b, dest, c } => {
                format!("cmpg-double {} {} {}", dest, b, c)
            }
            Self::CmpLong { b, dest, c } => {
                format!("cmp-long {} {} {}", dest, b, c)
            }
            Self::IfEq { label, a, b } => {
                format!("if-eq {} {} {}", a, b, label)
            }
            Self::IfNe { label, a, b } => {
                format!("if-neq {} {} {}", a, b, label)
            }
            Self::IfLt { label, a, b } => {
                format!("if-lt {} {} {}", a, b, label)
            }
            Self::IfGe { label, a, b } => {
                format!("if-ge {} {} {}", a, b, label)
            }
            Self::IfGt { label, a, b } => {
                format!("if-gt {} {} {}", a, b, label)
            }
            Self::IfLe { label, a, b } => {
                format!("if-le {} {} {}", a, b, label)
            }
            Self::IfEqZ { label, a } => {
                format!("if-eqz {} {}", a, label)
            }
            Self::IfNeZ { label, a } => {
                format!("if-nez {} {}", a, label)
            }
            Self::IfLtZ { label, a } => {
                format!("if-ltz {} {}", a, label)
            }
            Self::IfGeZ { label, a } => {
                format!("if-gez {} {}", a, label)
            }
            Self::IfGtZ { label, a } => {
                format!("if-gtz {} {}", a, label)
            }
            Self::IfLeZ { label, a } => {
                format!("if-lez {} {}", a, label)
            }
            Self::AGet { arr, idx, dest } => {
                format!("aget {} {} {}", dest, arr, idx)
            }
            Self::AGetWide { arr, idx, dest } => {
                format!("aget-wide {} {} {}", dest, arr, idx)
            }
            Self::AGetObject { arr, idx, dest } => {
                format!("aget-object {} {} {}", dest, arr, idx)
            }
            Self::AGetBoolean { arr, idx, dest } => {
                format!("aget-boolean {} {} {}", dest, arr, idx)
            }
            Self::AGetByte { arr, idx, dest } => {
                format!("aget-byte {} {} {}", dest, arr, idx)
            }
            Self::AGetChar { arr, idx, dest } => {
                format!("aget-char {} {} {}", dest, arr, idx)
            }
            Self::AGetShort { arr, idx, dest } => {
                format!("aget-short {} {} {}", dest, arr, idx)
            }
            Self::APut { arr, from, idx } => {
                format!("aput {} {} {}", from, arr, idx)
            }
            Self::APutWide { arr, from, idx } => {
                format!("aput-wide {} {} {}", from, arr, idx)
            }
            Self::APutObject { arr, from, idx } => {
                format!("aput-object {} {} {}", from, arr, idx)
            }
            Self::APutBoolean { arr, from, idx } => {
                format!("aput-boolean {} {} {}", from, arr, idx)
            }
            Self::APutByte { arr, from, idx } => {
                format!("aput-byte {} {} {}", from, arr, idx)
            }
            Self::APutChar { arr, from, idx } => {
                format!("aput-char {} {} {}", from, arr, idx)
            }
            Self::APutShort { arr, from, idx } => {
                format!("aput-short {} {} {}", from, arr, idx)
            }
            Self::IGet { field, to, obj } => {
                format!("iget {} {} {}", to, obj, field.__str__())
            }
            Self::IGetWide { field, to, obj } => {
                format!("iget-wide {} {} {}", to, obj, field.__str__())
            }
            Self::IGetObject { field, to, obj } => {
                format!("iget-object {} {} {}", to, obj, field.__str__())
            }
            Self::IGetBoolean { field, to, obj } => {
                format!("iget-boolean {} {} {}", to, obj, field.__str__())
            }
            Self::IGetByte { field, to, obj } => {
                format!("iget-byte {} {} {}", to, obj, field.__str__())
            }
            Self::IGetChar { field, to, obj } => {
                format!("iget-char {} {} {}", to, obj, field.__str__())
            }
            Self::IGetShort { field, to, obj } => {
                format!("iget-short {} {} {}", to, obj, field.__str__())
            }
            Self::IPut { field, from, obj } => {
                format!("iput {} {} {}", from, obj, field.__str__())
            }
            Self::IPutWide { field, from, obj } => {
                format!("iput-wide {} {} {}", from, obj, field.__str__())
            }
            Self::IPutObject { field, from, obj } => {
                format!("iput-object {} {} {}", from, obj, field.__str__())
            }
            Self::IPutBoolean { field, from, obj } => {
                format!("iput-boolean {} {} {}", from, obj, field.__str__())
            }
            Self::IPutByte { field, from, obj } => {
                format!("iput-byte {} {} {}", from, obj, field.__str__())
            }
            Self::IPutChar { field, from, obj } => {
                format!("iput-char {} {} {}", from, obj, field.__str__())
            }
            Self::IPutShort { field, from, obj } => {
                format!("iput-short {} {} {}", from, obj, field.__str__())
            }
            Self::SGet { field, to } => {
                format!("sget {} {}", to, field.__str__())
            }
            Self::SGetWide { field, to } => {
                format!("sget-wide {} {}", to, field.__str__())
            }
            Self::SGetObject { field, to } => {
                format!("sget-object {} {}", to, field.__str__())
            }
            Self::SGetBoolean { field, to } => {
                format!("sget-boolean {} {}", to, field.__str__())
            }
            Self::SGetByte { field, to } => {
                format!("sget-byte {} {}", to, field.__str__())
            }
            Self::SGetChar { field, to } => {
                format!("sget-char {} {}", to, field.__str__())
            }
            Self::SGetShort { field, to } => {
                format!("sget-short {} {}", to, field.__str__())
            }
            Self::SPut { field, from } => {
                format!("sput {} {}", from, field.__str__())
            }
            Self::SPutWide { field, from } => {
                format!("sput-wide {} {}", from, field.__str__())
            }
            Self::SPutObject { field, from } => {
                format!("sput-object {} {}", from, field.__str__())
            }
            Self::SPutBoolean { field, from } => {
                format!("sput-boolean {} {}", from, field.__str__())
            }
            Self::SPutByte { field, from } => {
                format!("sput-byte {} {}", from, field.__str__())
            }
            Self::SPutChar { field, from } => {
                format!("sput-char {} {}", from, field.__str__())
            }
            Self::SPutShort { field, from } => {
                format!("sput-short {} {}", from, field.__str__())
            }
            Self::InvokeVirtual { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("invoke-virtual {{{}}} {}", args, method.__str__())
            }
            Self::InvokeSuper { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("invoke-super {{{}}} {}", args, method.__str__())
            }
            Self::InvokeDirect { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("invoke-direct {{{}}} {}", args, method.__str__())
            }
            Self::InvokeStatic { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("invoke-static {{{}}} {}", args, method.__str__())
            }
            Self::InvokeInterface { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("invoke-interface {{{}}} {}", args, method.__str__())
            }
            Self::NegInt { val, dest } => {
                format!("neg-int {} {}", dest, val)
            }
            Self::NotInt { val, dest } => {
                format!("not-int {} {}", dest, val)
            }
            Self::NegLong { val, dest } => {
                format!("neg-long {} {}", dest, val)
            }
            Self::NotLong { val, dest } => {
                format!("not-long {} {}", dest, val)
            }
            Self::NegFloat { val, dest } => {
                format!("neg-float {} {}", dest, val)
            }
            Self::NegDouble { val, dest } => {
                format!("neg-double {} {}", dest, val)
            }
            Self::IntToLong { val, dest } => {
                format!("int-to-long {} {}", dest, val)
            }
            Self::IntToFloat { val, dest } => {
                format!("int-to-float {} {}", dest, val)
            }
            Self::IntToDouble { val, dest } => {
                format!("int-to-double {} {}", dest, val)
            }
            Self::LongToInt { val, dest } => {
                format!("long-to-int {} {}", dest, val)
            }
            Self::LongToFloat { val, dest } => {
                format!("long-to-float {} {}", dest, val)
            }
            Self::LongToDouble { val, dest } => {
                format!("long-to-double {} {}", dest, val)
            }
            Self::FloatToInt { val, dest } => {
                format!("float-to-int {} {}", dest, val)
            }
            Self::FloatToLong { val, dest } => {
                format!("float-to-long {} {}", dest, val)
            }
            Self::FloatToDouble { val, dest } => {
                format!("float-to-double {} {}", dest, val)
            }
            Self::DoubleToInt { val, dest } => {
                format!("double-to-int {} {}", dest, val)
            }
            Self::DoubleToLong { val, dest } => {
                format!("double-to-long {} {}", dest, val)
            }
            Self::DoubleToFloat { val, dest } => {
                format!("double-to-float {} {}", dest, val)
            }
            Self::IntToByte { val, dest } => {
                format!("int-to-byte {} {}", dest, val)
            }
            Self::IntToChar { val, dest } => {
                format!("int-to-char {} {}", dest, val)
            }
            Self::IntToShort { val, dest } => {
                format!("int-to-short {} {}", dest, val)
            }
            Self::AddInt { b, dest, c } => {
                format!("add-int {} {} {}", dest, b, c)
            }
            Self::SubInt { b, dest, c } => {
                format!("sub-int {} {} {}", dest, b, c)
            }
            Self::MulInt { b, dest, c } => {
                format!("mul-int {} {} {}", dest, b, c)
            }
            Self::DivInt { b, dest, c } => {
                format!("div-int {} {} {}", dest, b, c)
            }
            Self::RemInt { b, dest, c } => {
                format!("rem-int {} {} {}", dest, b, c)
            }
            Self::AndInt { b, dest, c } => {
                format!("and-int {} {} {}", dest, b, c)
            }
            Self::OrInt { b, dest, c } => {
                format!("or-int {} {} {}", dest, b, c)
            }
            Self::XorInt { b, dest, c } => {
                format!("xor-int {} {} {}", dest, b, c)
            }
            Self::ShlInt { b, dest, c } => {
                format!("shl-int {} {} {}", dest, b, c)
            }
            Self::ShrInt { b, dest, c } => {
                format!("shr-int {} {} {}", dest, b, c)
            }
            Self::UshrInt { b, dest, c } => {
                format!("ushr-int {} {} {}", dest, b, c)
            }
            Self::AddLong { b, dest, c } => {
                format!("add-long {} {} {}", dest, b, c)
            }
            Self::SubLong { b, dest, c } => {
                format!("sub-long {} {} {}", dest, b, c)
            }
            Self::MulLong { b, dest, c } => {
                format!("mul-long {} {} {}", dest, b, c)
            }
            Self::DivLong { b, dest, c } => {
                format!("div-long {} {} {}", dest, b, c)
            }
            Self::RemLong { b, dest, c } => {
                format!("rem-long {} {} {}", dest, b, c)
            }
            Self::AndLong { b, dest, c } => {
                format!("and-long {} {} {}", dest, b, c)
            }
            Self::OrLong { b, dest, c } => {
                format!("or-long {} {} {}", dest, b, c)
            }
            Self::XorLong { b, dest, c } => {
                format!("xor-long {} {} {}", dest, b, c)
            }
            Self::ShlLong { b, dest, c } => {
                format!("shl-long {} {} {}", dest, b, c)
            }
            Self::ShrLong { b, dest, c } => {
                format!("shr-long {} {} {}", dest, b, c)
            }
            Self::UshrLong { b, dest, c } => {
                format!("ushr-long {} {} {}", dest, b, c)
            }
            Self::AddFloat { b, dest, c } => {
                format!("add-float {} {} {}", dest, b, c)
            }
            Self::SubFloat { b, dest, c } => {
                format!("sub-float {} {} {}", dest, b, c)
            }
            Self::MulFloat { b, dest, c } => {
                format!("mul-float {} {} {}", dest, b, c)
            }
            Self::DivFloat { b, dest, c } => {
                format!("div-float {} {} {}", dest, b, c)
            }
            Self::RemFloat { b, dest, c } => {
                format!("rem-float {} {} {}", dest, b, c)
            }
            Self::AddDouble { b, dest, c } => {
                format!("add-double {} {} {}", dest, b, c)
            }
            Self::SubDouble { b, dest, c } => {
                format!("sub-double {} {} {}", dest, b, c)
            }
            Self::MulDouble { b, dest, c } => {
                format!("mul-double {} {} {}", dest, b, c)
            }
            Self::DivDouble { b, dest, c } => {
                format!("div-double {} {} {}", dest, b, c)
            }
            Self::RemDouble { b, dest, c } => {
                format!("rem-double {} {} {}", dest, b, c)
            }
            Self::AddInt2Addr { b, dest } => {
                format!("add-int/2addr {} {}", dest, b)
            }
            Self::SubInt2Addr { b, dest } => {
                format!("sub-int/2addr {} {}", dest, b)
            }
            Self::MulInt2Addr { b, dest } => {
                format!("mul-int/2addr {} {}", dest, b)
            }
            Self::DivInt2Addr { b, dest } => {
                format!("div-int/2addr {} {}", dest, b)
            }
            Self::RemInt2Addr { b, dest } => {
                format!("rem-int/2addr {} {}", dest, b)
            }
            Self::AndInt2Addr { b, dest } => {
                format!("and-int/2addr {} {}", dest, b)
            }
            Self::OrInt2Addr { b, dest } => {
                format!("or-int/2addr {} {}", dest, b)
            }
            Self::XorInt2Addr { b, dest } => {
                format!("xor-int/2addr {} {}", dest, b)
            }
            Self::ShlInt2Addr { b, dest } => {
                format!("shl-int/2addr {} {}", dest, b)
            }
            Self::ShrInt2Addr { b, dest } => {
                format!("shr-int/2addr {} {}", dest, b)
            }
            Self::UshrInt2Addr { b, dest } => {
                format!("ushr-int/2addr {} {}", dest, b)
            }
            Self::AddLong2Addr { b, dest } => {
                format!("add-long/2addr {} {}", dest, b)
            }
            Self::SubLong2Addr { b, dest } => {
                format!("sub-long/2addr {} {}", dest, b)
            }
            Self::MulLong2Addr { b, dest } => {
                format!("mul-long/2addr {} {}", dest, b)
            }
            Self::DivLong2Addr { b, dest } => {
                format!("div-long/2addr {} {}", dest, b)
            }
            Self::RemLong2Addr { b, dest } => {
                format!("rem-long/2addr {} {}", dest, b)
            }
            Self::AndLong2Addr { b, dest } => {
                format!("and-long/2addr {} {}", dest, b)
            }
            Self::OrLong2Addr { b, dest } => {
                format!("or-long/2addr {} {}", dest, b)
            }
            Self::XorLong2Addr { b, dest } => {
                format!("xor-long/2addr {} {}", dest, b)
            }
            Self::ShlLong2Addr { b, dest } => {
                format!("shl-long/2addr {} {}", dest, b)
            }
            Self::ShrLong2Addr { b, dest } => {
                format!("shr-long/2addr {} {}", dest, b)
            }
            Self::UshrLong2Addr { b, dest } => {
                format!("ushr-long/2addr {} {}", dest, b)
            }
            Self::AddFloat2Addr { b, dest } => {
                format!("add-float/2addr {} {}", dest, b)
            }
            Self::SubFloat2Addr { b, dest } => {
                format!("sub-float/2addr {} {}", dest, b)
            }
            Self::MulFloat2Addr { b, dest } => {
                format!("mul-float/2addr {} {}", dest, b)
            }
            Self::DivFloat2Addr { b, dest } => {
                format!("div-float/2addr {} {}", dest, b)
            }
            Self::RemFloat2Addr { b, dest } => {
                format!("rem-float/2addr {} {}", dest, b)
            }
            Self::AddDouble2Addr { b, dest } => {
                format!("add-double/2addr {} {}", dest, b)
            }
            Self::SubDouble2Addr { b, dest } => {
                format!("sub-double/2addr {} {}", dest, b)
            }
            Self::MulDouble2Addr { b, dest } => {
                format!("mul-double/2addr {} {}", dest, b)
            }
            Self::DivDouble2Addr { b, dest } => {
                format!("div-double/2addr {} {}", dest, b)
            }
            Self::RemDouble2Addr { b, dest } => {
                format!("rem-double/2addr {} {}", dest, b)
            }
            Self::AddIntLit { lit, b, dest } => {
                format!("add-int/lit {} {} {}", dest, b, lit)
            }
            Self::RsubIntLit { lit, b, dest } => {
                format!("rsub-int/lit {} {} {}", dest, b, lit)
            }
            Self::MulIntLit { lit, b, dest } => {
                format!("mul-int/lit {} {} {}", dest, b, lit)
            }
            Self::DivIntLit { lit, b, dest } => {
                format!("div-int/lit {} {} {}", dest, b, lit)
            }
            Self::RemIntLit { lit, b, dest } => {
                format!("rem-int/lit {} {} {}", dest, b, lit)
            }
            Self::AndIntLit { lit, b, dest } => {
                format!("and-int/lit {} {} {}", dest, b, lit)
            }
            Self::OrIntLit { lit, b, dest } => {
                format!("or-int/lit {} {} {}", dest, b, lit)
            }
            Self::XorIntLit { lit, b, dest } => {
                format!("xor-int/lit {} {} {}", dest, b, lit)
            }
            Self::ShlIntLit { lit, b, dest } => {
                format!("shl-int/lit {} {} {}", dest, b, lit)
            }
            Self::ShrIntLit { lit, b, dest } => {
                format!("shr-int/lit {} {} {}", dest, b, lit)
            }
            Self::UshrIntLit { lit, b, dest } => {
                format!("ushr-int/lit {} {} {}", dest, b, lit)
            }
            Self::InvokePolymorphic {
                args,
                method,
                proto,
            } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!(
                    "invoke-polymorphic {{{}}} {} {}",
                    args,
                    method.__str__(),
                    proto.__str__()
                )
            }
            Self::InvokeCustom { args, call_site } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("invoke-custom {{{}}} {}", args, call_site.__str__(),)
            }
            Self::ConstMethodHandle { to, handle } => {
                format!("const-method-handle {} {}", to, handle.__str__())
            }
            Self::ConstMethodType { to, proto } => {
                format!("const-method-type {} {}", to, proto.__str__())
            }
            Self::Try {
                handlers,
                end_label,
                default_handler,
            } => {
                let handlers = handlers
                    .iter()
                    .map(|(ty, label)| format!("    {}: {label}", ty.__str__()))
                    .collect::<Vec<_>>()
                    .join("\n    ");
                let default_handler = if let Some(label) = default_handler {
                    format!("    default: {label}")
                } else {
                    "".into()
                };

                format!("try until {}{}{}", end_label, handlers, default_handler)
            }
            Self::Label { name } => {
                format!("{}:", name)
            }
            Self::DebugLocal {
                reg,
                name,
                type_,
                signature,
            } => {
                // TODO: check if/how apktool/smali handles empty name and type
                let name = name.as_ref().map(DexString::__str__).unwrap_or_default();
                let ty = type_.as_ref().map(IdType::__str__).unwrap_or(String::new());
                if let Some(signature) = signature {
                    format!(".local {reg}, \"{name}\":{ty}, \"{}\"", signature.__str__())
                } else {
                    format!(".local {reg}, \"{name}\":{ty}")
                }
            }
            Self::DebugEndLocal { reg } => format!(".end local {reg}"),
            Self::DebugEndPrologue {} => ".prologue".into(),
            Self::DebugBeginEpilogue {} => ".epilogue".into(),
            // TODO: check if/how apktool/smali handles empty change of src file
            Self::DebugSourceFile { file: Some(file) } => {
                format!(".source_file {}", file.__str__())
            }
            // TODO: find a better representation
            Self::DebugSourceFile { file: None } => ".source_file unknown".into(),
            Self::DebugLine { number } => format!(".line {number}"),
        }
    }

    pub fn __repr__(&self) -> String {
        match self {
            Self::Nop {} => "Instruction(Nop)".into(),
            Self::Move { from, to } => {
                format!("Instruction::Move({}, {})", to, from)
            }
            Self::MoveWide { from, to } => {
                format!("Instruction::MoveWide({}, {})", to, from)
            }
            Self::MoveObject { from, to } => {
                format!("Instruction::MoveObject({}, {})", to, from)
            }
            Self::MoveResult { to } => {
                format!("Instruction::MoveResult({})", to)
            }
            Self::MoveResultWide { to } => {
                format!("Instruction::MoveResultWide({})", to)
            }
            Self::MoveResultObject { to } => {
                format!("Instruction::MoveResultObject({})", to)
            }
            Self::MoveException { to } => {
                format!("Instruction::MoveException({})", to)
            }
            Self::ReturnVoid {} => "Instruction::ReturnVoid)".into(),
            Self::Return { reg } => {
                format!("Instruction::Return({})", reg)
            }
            Self::ReturnWide { reg } => {
                format!("Instruction::ReturnWide({})", reg)
            }
            Self::ReturnObject { reg } => {
                format!("Instruction::ReturnObject({})", reg)
            }
            Self::Const { reg, lit } => {
                format!("Instruction::Const({}, {})", reg, lit)
            }
            Self::ConstWide { reg, lit } => {
                format!("Instruction::ConstWide({}, {})", reg, lit)
            }
            Self::ConstString { reg, lit } => {
                format!("Instruction::ConstString({}, {})", reg, lit.__repr__())
            }
            Self::ConstClass { reg, lit } => {
                format!("Instruction::ConstClass({}, {})", reg, lit.__repr__())
            }
            Self::MonitorEnter { reg } => {
                format!("Instruction::MonitorEnter({})", reg)
            }
            Self::MonitorExit { reg } => {
                format!("Instruction::MonitorExit({})", reg)
            }
            Self::CheckCast { reg, lit } => {
                format!("Instruction::CheckCast({}, {})", reg, lit.__repr__())
            }
            Self::InstanceOf { obj, lit, dest } => {
                format!(
                    "Instruction::InstanceOf({}, {}, {})",
                    dest,
                    obj,
                    lit.__repr__()
                )
            }
            Self::ArrayLength { arr, dest } => {
                format!("Instruction::ArrayLength({}, {})", dest, arr,)
            }
            Self::NewInstance { reg, lit } => {
                format!("Instruction::NewInstance({}, {})", reg, lit.__repr__())
            }
            Self::NewArray { reg, lit, size_reg } => {
                format!(
                    "Instruction::NewArray({}, {}, {})",
                    reg,
                    size_reg,
                    lit.__repr__()
                )
            }
            Self::FilledNewArray { type_, reg_values } => {
                let args = if reg_values.len() >= 5 {
                    format!(
                        "{}, ..., {}",
                        reg_values[0],
                        reg_values[reg_values.len() - 1]
                    )
                } else {
                    reg_values
                        .iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(", ")
                };
                format!(
                    "Instruction::FilledNewArray([{}], {})",
                    args,
                    type_.__repr__()
                )
            }
            Self::FillArrayData {
                elt_width,
                arr,
                data,
            } => {
                let data: String = if data.len() / *elt_width as usize == 0 {
                    "".into()
                } else if data.len() / *elt_width as usize <= 2 {
                    let mut arr = "".into();
                    for (i, v) in data.iter().enumerate() {
                        if i == 0 {
                            arr += "0x"
                        } else if i % *elt_width as usize == 0 {
                            arr += ", 0x"
                        }
                        arr += format!("{v:02x}").as_str();
                    }
                    arr
                } else {
                    let mut arr = "0x".into();
                    for v in &data[..*elt_width as usize] {
                        arr += format!("{v:02x}").as_str();
                    }
                    arr += ", ..., 0x";
                    for v in &data[data.len() - *elt_width as usize..] {
                        arr += format!("{v:02x}").as_str();
                    }

                    arr
                };
                format!("Instruction::FillArrayData({}, [{}])", arr, data)
            }
            Self::Throw { reg } => {
                format!("Instruction::Throw({})", reg)
            }
            Self::Goto { label } => {
                format!("Instruction::Goto({})", label)
            }
            Self::Switch { reg, .. } => {
                format!("Instruction::Switch({}, ...)", reg)
            }
            Self::CmpLFloat { b, c, dest } => {
                format!("Instruction::CmpLFloat({}, {}, {})", dest, b, c)
            }
            Self::CmpGFloat { b, c, dest } => {
                format!("Instruction::CmpGFloat({}, {}, {})", dest, b, c)
            }
            Self::CmpLDouble { b, c, dest } => {
                format!("Instruction::CmpLDouble({}, {}, {})", dest, b, c)
            }
            Self::CmpGDouble { b, c, dest } => {
                format!("Instruction::CmpGDouble({}, {}, {})", dest, b, c)
            }
            Self::CmpLong { b, c, dest } => {
                format!("Instruction::CmpLong({}, {}, {})", dest, b, c)
            }
            Self::IfEq { a, b, label } => {
                format!("Instruction::IfEq({}, {}, {})", a, b, label)
            }
            Self::IfNe { a, b, label } => {
                format!("Instruction::IfNe({}, {}, {})", a, b, label)
            }
            Self::IfLt { a, b, label } => {
                format!("Instruction::IfLt({}, {}, {})", a, b, label)
            }
            Self::IfGe { a, b, label } => {
                format!("Instruction::IfGe({}, {}, {})", a, b, label)
            }
            Self::IfGt { a, b, label } => {
                format!("Instruction::IfGt({}, {}, {})", a, b, label)
            }
            Self::IfLe { a, b, label } => {
                format!("Instruction::IfLe({}, {}, {})", a, b, label)
            }
            Self::IfEqZ { a, label } => {
                format!("Instruction::IfEqZ({}, {})", a, label)
            }
            Self::IfNeZ { a, label } => {
                format!("Instruction::IfNeZ({}, {})", a, label)
            }
            Self::IfLtZ { a, label } => {
                format!("Instruction::IfLtZ({}, {})", a, label)
            }
            Self::IfGeZ { a, label } => {
                format!("Instruction::IfGeZ({}, {})", a, label)
            }
            Self::IfGtZ { a, label } => {
                format!("Instruction::IfGtZ({}, {})", a, label)
            }
            Self::IfLeZ { a, label } => {
                format!("Instruction::IfLeZ({}, {})", a, label)
            }
            Self::AGet { idx, arr, dest } => {
                format!("Instruction::AGet({}, {}, {})", dest, arr, idx)
            }
            Self::AGetWide { idx, arr, dest } => {
                format!("Instruction::AGetWide({}, {}, {})", dest, arr, idx)
            }
            Self::AGetObject { idx, arr, dest } => {
                format!("Instruction::AGetObject({}, {}, {})", dest, arr, idx)
            }
            Self::AGetBoolean { idx, arr, dest } => {
                format!("Instruction::AGetBoolean({}, {}, {})", dest, arr, idx)
            }
            Self::AGetByte { idx, arr, dest } => {
                format!("Instruction::AGetByte({}, {}, {})", dest, arr, idx)
            }
            Self::AGetChar { idx, arr, dest } => {
                format!("Instruction::AGetChar({}, {}, {})", dest, arr, idx)
            }
            Self::AGetShort { idx, arr, dest } => {
                format!("Instruction::AGetShort({}, {}, {})", dest, arr, idx)
            }
            Self::APut { idx, arr, from } => {
                format!("Instruction::APut({}, {}, {})", from, arr, idx)
            }
            Self::APutWide { idx, arr, from } => {
                format!("Instruction::APutWide({}, {}, {})", from, arr, idx)
            }
            Self::APutObject { idx, arr, from } => {
                format!("Instruction::APutObject({}, {}, {})", from, arr, idx)
            }
            Self::APutBoolean { idx, arr, from } => {
                format!("Instruction::APutBoolean({}, {}, {})", from, arr, idx)
            }
            Self::APutByte { idx, arr, from } => {
                format!("Instruction::APutByte({}, {}, {})", from, arr, idx)
            }
            Self::APutChar { idx, arr, from } => {
                format!("Instruction::APutChar({}, {}, {})", from, arr, idx)
            }
            Self::APutShort { idx, arr, from } => {
                format!("Instruction::APutShort({}, {}, {})", from, arr, idx)
            }
            Self::IGet { obj, field, to } => {
                format!("Instruction::IGet({}, {}, {})", to, obj, field.__repr__())
            }
            Self::IGetWide { obj, field, to } => {
                format!(
                    "Instruction::IGetWide({}, {}, {})",
                    to,
                    obj,
                    field.__repr__()
                )
            }
            Self::IGetObject { obj, field, to } => {
                format!(
                    "Instruction::IGetObject({}, {}, {})",
                    to,
                    obj,
                    field.__repr__()
                )
            }
            Self::IGetBoolean { obj, field, to } => {
                format!(
                    "Instruction::IGetBoolean({}, {}, {})",
                    to,
                    obj,
                    field.__repr__()
                )
            }
            Self::IGetByte { obj, field, to } => {
                format!(
                    "Instruction::IGetByte({}, {}, {})",
                    to,
                    obj,
                    field.__repr__()
                )
            }
            Self::IGetChar { obj, field, to } => {
                format!(
                    "Instruction::IGetChar({}, {}, {})",
                    to,
                    obj,
                    field.__repr__()
                )
            }
            Self::IGetShort { obj, field, to } => {
                format!(
                    "Instruction::IGetShort({}, {}, {})",
                    to,
                    obj,
                    field.__repr__()
                )
            }
            Self::IPut { obj, field, from } => {
                format!("Instruction::IPut({}, {}, {})", from, obj, field.__repr__())
            }
            Self::IPutWide { obj, field, from } => {
                format!(
                    "Instruction::IPutWide({}, {}, {})",
                    from,
                    obj,
                    field.__repr__()
                )
            }
            Self::IPutObject { obj, field, from } => {
                format!(
                    "Instruction::IPutObject({}, {}, {})",
                    from,
                    obj,
                    field.__repr__()
                )
            }
            Self::IPutBoolean { obj, field, from } => {
                format!(
                    "Instruction::IPutBoolean({}, {}, {})",
                    from,
                    obj,
                    field.__repr__()
                )
            }
            Self::IPutByte { obj, field, from } => {
                format!(
                    "Instruction::IPutByte({}, {}, {})",
                    from,
                    obj,
                    field.__repr__()
                )
            }
            Self::IPutChar { obj, field, from } => {
                format!(
                    "Instruction::IPutChar({}, {}, {})",
                    from,
                    obj,
                    field.__repr__()
                )
            }
            Self::IPutShort { obj, field, from } => {
                format!(
                    "Instruction::IPutShort({}, {}, {})",
                    from,
                    obj,
                    field.__repr__()
                )
            }
            Self::SGet { field, to } => {
                format!("Instruction::SGet({}, {})", to, field.__repr__())
            }
            Self::SGetWide { field, to } => {
                format!("Instruction::SGetWide({}, {})", to, field.__repr__())
            }
            Self::SGetObject { field, to } => {
                format!("Instruction::SGetObject({}, {})", to, field.__repr__())
            }
            Self::SGetBoolean { field, to } => {
                format!("Instruction::SGetBoolean({}, {})", to, field.__repr__())
            }
            Self::SGetByte { field, to } => {
                format!("Instruction::SGetByte({}, {})", to, field.__repr__())
            }
            Self::SGetChar { field, to } => {
                format!("Instruction::SGetChar({}, {})", to, field.__repr__())
            }
            Self::SGetShort { field, to } => {
                format!("Instruction::SGetShort({}, {})", to, field.__repr__())
            }
            Self::SPut { field, from } => {
                format!("Instruction::SPut({}, {})", from, field.__repr__())
            }
            Self::SPutWide { field, from } => {
                format!("Instruction::SPutWide({}, {})", from, field.__repr__())
            }
            Self::SPutObject { field, from } => {
                format!("Instruction::SPutObject({}, {})", from, field.__repr__())
            }
            Self::SPutBoolean { field, from } => {
                format!("Instruction::SPutBoolean({}, {})", from, field.__repr__())
            }
            Self::SPutByte { field, from } => {
                format!("Instruction::SPutByte({}, {})", from, field.__repr__())
            }
            Self::SPutChar { field, from } => {
                format!("Instruction::SPutChar({}, {})", from, field.__repr__())
            }
            Self::SPutShort { field, from } => {
                format!("Instruction::SPutShort({}, {})", from, field.__repr__())
            }
            Self::InvokeVirtual { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("Instruction::InvokeVirtual({}, {})", args, method.__str__())
            }
            Self::InvokeSuper { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("Instruction::InvokeSuper({}, {})", args, method.__str__())
            }
            Self::InvokeDirect { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("Instruction::InvokeDirect({}, {})", args, method.__str__())
            }
            Self::InvokeStatic { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!("Instruction::InvokeStatic({}, {})", args, method.__str__())
            }
            Self::InvokeInterface { args, method } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!(
                    "Instruction::InvokeInterface({}, {})",
                    args,
                    method.__str__()
                )
            }
            Self::NegInt { val, dest } => {
                format!("Instruction::NegInt({}, {})", dest, val)
            }
            Self::NotInt { val, dest } => {
                format!("Instruction::NotInt({}, {})", dest, val)
            }
            Self::NegLong { val, dest } => {
                format!("Instruction::NegLong({}, {})", dest, val)
            }
            Self::NotLong { val, dest } => {
                format!("Instruction::NotLong({}, {})", dest, val)
            }
            Self::NegFloat { val, dest } => {
                format!("Instruction::NegFloat({}, {})", dest, val)
            }
            Self::NegDouble { val, dest } => {
                format!("Instruction::NegDouble({}, {})", dest, val)
            }
            Self::IntToLong { val, dest } => {
                format!("Instruction::IntToLong({}, {})", dest, val)
            }
            Self::IntToFloat { val, dest } => {
                format!("Instruction::IntToFloat({}, {})", dest, val)
            }
            Self::IntToDouble { val, dest } => {
                format!("Instruction::IntToDouble({}, {})", dest, val)
            }
            Self::LongToInt { val, dest } => {
                format!("Instruction::LongToInt({}, {})", dest, val)
            }
            Self::LongToFloat { val, dest } => {
                format!("Instruction::LongToFloat({}, {})", dest, val)
            }
            Self::LongToDouble { val, dest } => {
                format!("Instruction::LongToDouble({}, {})", dest, val)
            }
            Self::FloatToInt { val, dest } => {
                format!("Instruction::FloatToInt({}, {})", dest, val)
            }
            Self::FloatToLong { val, dest } => {
                format!("Instruction::FloatToLong({}, {})", dest, val)
            }
            Self::FloatToDouble { val, dest } => {
                format!("Instruction::FloatToDouble({}, {})", dest, val)
            }
            Self::DoubleToInt { val, dest } => {
                format!("Instruction::DoubleToInt({}, {})", dest, val)
            }
            Self::DoubleToLong { val, dest } => {
                format!("Instruction::DoubleToLong({}, {})", dest, val)
            }
            Self::DoubleToFloat { val, dest } => {
                format!("Instruction::DoubleToFloat({}, {})", dest, val)
            }
            Self::IntToByte { val, dest } => {
                format!("Instruction::IntToByte({}, {})", dest, val)
            }
            Self::IntToChar { val, dest } => {
                format!("Instruction::IntToChar({}, {})", dest, val)
            }
            Self::IntToShort { val, dest } => {
                format!("Instruction::IntToShort({}, {})", dest, val)
            }
            Self::AddInt { b, c, dest } => {
                format!("Instruction::AddInt({}, {}, {})", dest, b, c)
            }
            Self::SubInt { b, c, dest } => {
                format!("Instruction::SubInt({}, {}, {})", dest, b, c)
            }
            Self::MulInt { b, c, dest } => {
                format!("Instruction::MulInt({}, {}, {})", dest, b, c)
            }
            Self::DivInt { b, c, dest } => {
                format!("Instruction::DivInt({}, {}, {})", dest, b, c)
            }
            Self::RemInt { b, c, dest } => {
                format!("Instruction::RemInt({}, {}, {})", dest, b, c)
            }
            Self::AndInt { b, c, dest } => {
                format!("Instruction::AndInt({}, {}, {})", dest, b, c)
            }
            Self::OrInt { b, c, dest } => {
                format!("Instruction::OrInt({}, {}, {})", dest, b, c)
            }
            Self::XorInt { b, c, dest } => {
                format!("Instruction::XorInt({}, {}, {})", dest, b, c)
            }
            Self::ShlInt { b, c, dest } => {
                format!("Instruction::ShlInt({}, {}, {})", dest, b, c)
            }
            Self::ShrInt { b, c, dest } => {
                format!("Instruction::ShrInt({}, {}, {})", dest, b, c)
            }
            Self::UshrInt { b, c, dest } => {
                format!("Instruction::UshrInt({}, {}, {})", dest, b, c)
            }
            Self::AddLong { b, c, dest } => {
                format!("Instruction::AddLong({}, {}, {})", dest, b, c)
            }
            Self::SubLong { b, c, dest } => {
                format!("Instruction::SubLong({}, {}, {})", dest, b, c)
            }
            Self::MulLong { b, c, dest } => {
                format!("Instruction::MulLong({}, {}, {})", dest, b, c)
            }
            Self::DivLong { b, c, dest } => {
                format!("Instruction::DivLong({}, {}, {})", dest, b, c)
            }
            Self::RemLong { b, c, dest } => {
                format!("Instruction::RemLong({}, {}, {})", dest, b, c)
            }
            Self::AndLong { b, c, dest } => {
                format!("Instruction::AndLong({}, {}, {})", dest, b, c)
            }
            Self::OrLong { b, c, dest } => {
                format!("Instruction::OrLong({}, {}, {})", dest, b, c)
            }
            Self::XorLong { b, c, dest } => {
                format!("Instruction::XorLong({}, {}, {})", dest, b, c)
            }
            Self::ShlLong { b, c, dest } => {
                format!("Instruction::ShlLong({}, {}, {})", dest, b, c)
            }
            Self::ShrLong { b, c, dest } => {
                format!("Instruction::ShrLong({}, {}, {})", dest, b, c)
            }
            Self::UshrLong { b, c, dest } => {
                format!("Instruction::UshrLong({}, {}, {})", dest, b, c)
            }
            Self::AddFloat { b, c, dest } => {
                format!("Instruction::AddFloat({}, {}, {})", dest, b, c)
            }
            Self::SubFloat { b, c, dest } => {
                format!("Instruction::SubFloat({}, {}, {})", dest, b, c)
            }
            Self::MulFloat { b, c, dest } => {
                format!("Instruction::MulFloat({}, {}, {})", dest, b, c)
            }
            Self::DivFloat { b, c, dest } => {
                format!("Instruction::DivFloat({}, {}, {})", dest, b, c)
            }
            Self::RemFloat { b, c, dest } => {
                format!("Instruction::RemFloat({}, {}, {})", dest, b, c)
            }
            Self::AddDouble { b, c, dest } => {
                format!("Instruction::AddDouble({}, {}, {})", dest, b, c)
            }
            Self::SubDouble { b, c, dest } => {
                format!("Instruction::SubDouble({}, {}, {})", dest, b, c)
            }
            Self::MulDouble { b, c, dest } => {
                format!("Instruction::MulDouble({}, {}, {})", dest, b, c)
            }
            Self::DivDouble { b, c, dest } => {
                format!("Instruction::DivDouble({}, {}, {})", dest, b, c)
            }
            Self::RemDouble { b, c, dest } => {
                format!("Instruction::RemDouble({}, {}, {})", dest, b, c)
            }
            Self::AddInt2Addr { b, dest } => {
                format!("Instruction::AddInt2Addr({}, {})", dest, b)
            }
            Self::SubInt2Addr { b, dest } => {
                format!("Instruction::SubInt2Addr({}, {})", dest, b)
            }
            Self::MulInt2Addr { b, dest } => {
                format!("Instruction::MulInt2Addr({}, {})", dest, b)
            }
            Self::DivInt2Addr { b, dest } => {
                format!("Instruction::DivInt2Addr({}, {})", dest, b)
            }
            Self::RemInt2Addr { b, dest } => {
                format!("Instruction::RemInt2Addr({}, {})", dest, b)
            }
            Self::AndInt2Addr { b, dest } => {
                format!("Instruction::AndInt2Addr({}, {})", dest, b)
            }
            Self::OrInt2Addr { b, dest } => {
                format!("Instruction::OrInt2Addr({}, {})", dest, b)
            }
            Self::XorInt2Addr { b, dest } => {
                format!("Instruction::XorInt2Addr({}, {})", dest, b)
            }
            Self::ShlInt2Addr { b, dest } => {
                format!("Instruction::ShlInt2Addr({}, {})", dest, b)
            }
            Self::ShrInt2Addr { b, dest } => {
                format!("Instruction::ShrInt2Addr({}, {})", dest, b)
            }
            Self::UshrInt2Addr { b, dest } => {
                format!("Instruction::UshrInt2Addr({}, {})", dest, b)
            }
            Self::AddLong2Addr { b, dest } => {
                format!("Instruction::AddLong2Addr({}, {})", dest, b)
            }
            Self::SubLong2Addr { b, dest } => {
                format!("Instruction::SubLong2Addr({}, {})", dest, b)
            }
            Self::MulLong2Addr { b, dest } => {
                format!("Instruction::MulLong2Addr({}, {})", dest, b)
            }
            Self::DivLong2Addr { b, dest } => {
                format!("Instruction::DivLong2Addr({}, {})", dest, b)
            }
            Self::RemLong2Addr { b, dest } => {
                format!("Instruction::RemLong2Addr({}, {})", dest, b)
            }
            Self::AndLong2Addr { b, dest } => {
                format!("Instruction::AndLong2Addr({}, {})", dest, b)
            }
            Self::OrLong2Addr { b, dest } => {
                format!("Instruction::OrLong2Addr({}, {})", dest, b)
            }
            Self::XorLong2Addr { b, dest } => {
                format!("Instruction::XorLong2Addr({}, {})", dest, b)
            }
            Self::ShlLong2Addr { b, dest } => {
                format!("Instruction::ShlLong2Addr({}, {})", dest, b)
            }
            Self::ShrLong2Addr { b, dest } => {
                format!("Instruction::ShrLong2Addr({}, {})", dest, b)
            }
            Self::UshrLong2Addr { b, dest } => {
                format!("Instruction::UshrLong2Addr({}, {})", dest, b)
            }
            Self::AddFloat2Addr { b, dest } => {
                format!("Instruction::AddFloat2Addr({}, {})", dest, b)
            }
            Self::SubFloat2Addr { b, dest } => {
                format!("Instruction::SubFloat2Addr({}, {})", dest, b)
            }
            Self::MulFloat2Addr { b, dest } => {
                format!("Instruction::MulFloat2Addr({}, {})", dest, b)
            }
            Self::DivFloat2Addr { b, dest } => {
                format!("Instruction::DivFloat2Addr({}, {})", dest, b)
            }
            Self::RemFloat2Addr { b, dest } => {
                format!("Instruction::RemFloat2Addr({}, {})", dest, b)
            }
            Self::AddDouble2Addr { b, dest } => {
                format!("Instruction::AddDouble2Addr({}, {})", dest, b)
            }
            Self::SubDouble2Addr { b, dest } => {
                format!("Instruction::SubDouble2Addr({}, {})", dest, b)
            }
            Self::MulDouble2Addr { b, dest } => {
                format!("Instruction::MulDouble2Addr({}, {})", dest, b)
            }
            Self::DivDouble2Addr { b, dest } => {
                format!("Instruction::DivDouble2Addr({}, {})", dest, b)
            }
            Self::RemDouble2Addr { b, dest } => {
                format!("Instruction::RemDouble2Addr({}, {})", dest, b)
            }
            Self::AddIntLit { b, lit, dest } => {
                format!("Instruction::AddIntLit({}, {}, {})", dest, b, lit)
            }
            Self::RsubIntLit { b, lit, dest } => {
                format!("Instruction::RsubIntLit({}, {}, {})", dest, b, lit)
            }
            Self::MulIntLit { b, lit, dest } => {
                format!("Instruction::MulIntLit({}, {}, {})", dest, b, lit)
            }
            Self::DivIntLit { b, lit, dest } => {
                format!("Instruction::DivIntLit({}, {}, {})", dest, b, lit)
            }
            Self::RemIntLit { b, lit, dest } => {
                format!("Instruction::RemIntLit({}, {}, {})", dest, b, lit)
            }
            Self::AndIntLit { b, lit, dest } => {
                format!("Instruction::AndIntLit({}, {}, {})", dest, b, lit)
            }
            Self::OrIntLit { b, lit, dest } => {
                format!("Instruction::OrIntLit({}, {}, {})", dest, b, lit)
            }
            Self::XorIntLit { b, lit, dest } => {
                format!("Instruction::XorIntLit({}, {}, {})", dest, b, lit)
            }
            Self::ShlIntLit { b, lit, dest } => {
                format!("Instruction::ShlIntLit({}, {}, {})", dest, b, lit)
            }
            Self::ShrIntLit { b, lit, dest } => {
                format!("Instruction::ShrIntLit({}, {}, {})", dest, b, lit)
            }
            Self::UshrIntLit { b, lit, dest } => {
                format!("Instruction::UshrIntLit({}, {}, {})", dest, b, lit)
            }
            Self::InvokePolymorphic {
                args,
                proto,
                method,
            } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!(
                    "Instruction::InvokePolymorphic({}, {}, {})",
                    args,
                    method.__str__(),
                    proto.__str__()
                )
            }
            Self::InvokeCustom { args, call_site } => {
                let args = if args.len() >= 5 {
                    format!("{} .. {}", args[0], args[args.len() - 1])
                } else {
                    args.iter()
                        .map(|v| format!("{v}"))
                        .collect::<Vec<_>>()
                        .join(" ")
                };
                format!(
                    "Instruction::InvokeCustom({}, {})",
                    args,
                    call_site.__str__(),
                )
            }
            Self::ConstMethodHandle { handle, to } => {
                format!(
                    "Instruction::ConstMethodHandle({}, {})",
                    to,
                    handle.__repr__()
                )
            }
            Self::ConstMethodType { proto, to } => {
                format!("Instruction::ConstMethodType({}, {})", to, proto.__repr__())
            }
            Self::Try {
                end_label,
                default_handler,
                handlers,
            } => {
                let handlers = handlers
                    .iter()
                    .map(|(ty, label)| format!("{}, {label}", ty.__repr__()))
                    .collect::<Vec<_>>()
                    .join(", ");
                format!(
                    "Instruction::Try({}, [{}], {:?})",
                    end_label, handlers, default_handler,
                )
            }
            Self::Label { name } => {
                format!("Instruction::Label({})", name)
            }
            Self::DebugLocal {
                reg,
                name,
                type_,
                signature,
            } => {
                let name = name.clone().unwrap_or("None".into()).__str__();
                let type_ = type_
                    .as_ref()
                    .map(IdType::__repr__)
                    .unwrap_or("None".into());
                let signature = signature.clone().unwrap_or("None".into()).__str__();
                format!("Instruction::DebugLoca({reg}, {name}, {type_}, {signature}")
            }
            Self::DebugEndLocal { reg } => format!("Instruction::DebugEndLocal({reg})"),
            Self::DebugEndPrologue {} => "Instruction::DebugEndPrologue".into(),
            Self::DebugBeginEpilogue {} => "Instruction::DebugBeginEpilogue".into(),
            // TODO: check if/how apktool/smali handles empty change of src file
            Self::DebugSourceFile { file: Some(file) } => {
                format!("Instruction::DebugSourceFile({})", file.__str__())
            }
            Self::DebugSourceFile { file: None } => "Instruction::DebugSourceFile(None)".into(),
            Self::DebugLine { number } => format!("Instruction::DebugLine({number})"),
        }
    }

    /// Return the smallest size possible for the associated instruction.
    ///
    /// # Warning
    ///
    /// The returned size is the size in bytes (`u8`) (the same as the value return
    /// by the `Serializable::size()` method), but instructions in the bytecode
    /// count addresses by unit of `u16`.
    pub fn min_ins_size(&self) -> usize {
        match self {
            Self::ConstString { .. } => 4,
            Self::Goto { .. } => 2,
            _ => self.ins_size().unwrap(),
        }
    }

    /// Return the bigest size possible for the associated instruction.
    ///
    /// # Warning
    ///
    /// The returned size is the size in bytes (`u8`) (the same as the value return
    /// by the `Serializable::size()` method), but instructions in the bytecode
    /// count addresses by unit of `u16`.
    pub fn max_ins_size(&self) -> usize {
        match self {
            Self::ConstString { .. } => 6,
            Self::Goto { .. } => 6,
            _ => self.ins_size().unwrap(),
        }
    }

    /// Return the actual size of the instruction.
    ///
    /// # Warning
    ///
    /// The returned size is the size in bytes (`u8`) (the same as the value return
    /// by the `Serializable::size()` method), but instructions in the bytecode
    /// count addresses by unit of `u16`.
    pub fn ins_size(&self) -> Result<usize> {
        match self {
            Self::ConstString { .. } => Err(anyhow!(
                "Cannot get the size of a const-string size without knowing the string idx"
            )),
            Self::Goto { .. } => Err(anyhow!(
                "Cannot get the size of a goto without knowing the branch offset"
            )),
            Self::ConstClass { .. } => Ok(4),
            Self::CheckCast { .. } => Ok(4),
            Self::InstanceOf { .. } => Ok(4),
            Self::NewInstance { .. } => Ok(4),
            Self::NewArray { .. } => Ok(4),
            Self::FilledNewArray { .. } => Ok(6),
            Self::FillArrayData { .. } => Ok(6),
            Self::Switch { .. } => Ok(6),
            Self::IfEq { .. } => Ok(4),
            Self::IfNe { .. } => Ok(4),
            Self::IfLt { .. } => Ok(4),
            Self::IfGe { .. } => Ok(4),
            Self::IfGt { .. } => Ok(4),
            Self::IfLe { .. } => Ok(4),
            Self::IfEqZ { .. } => Ok(4),
            Self::IfNeZ { .. } => Ok(4),
            Self::IfLtZ { .. } => Ok(4),
            Self::IfGeZ { .. } => Ok(4),
            Self::IfGtZ { .. } => Ok(4),
            Self::IfLeZ { .. } => Ok(4),
            Self::IGet { .. } => Ok(4),
            Self::IGetWide { .. } => Ok(4),
            Self::IGetObject { .. } => Ok(4),
            Self::IGetBoolean { .. } => Ok(4),
            Self::IGetByte { .. } => Ok(4),
            Self::IGetChar { .. } => Ok(4),
            Self::IGetShort { .. } => Ok(4),
            Self::IPut { .. } => Ok(4),
            Self::IPutWide { .. } => Ok(4),
            Self::IPutObject { .. } => Ok(4),
            Self::IPutBoolean { .. } => Ok(4),
            Self::IPutByte { .. } => Ok(4),
            Self::IPutChar { .. } => Ok(4),
            Self::IPutShort { .. } => Ok(4),
            Self::SGet { .. } => Ok(4),
            Self::SGetWide { .. } => Ok(4),
            Self::SGetObject { .. } => Ok(4),
            Self::SGetBoolean { .. } => Ok(4),
            Self::SGetByte { .. } => Ok(4),
            Self::SGetChar { .. } => Ok(4),
            Self::SGetShort { .. } => Ok(4),
            Self::SPut { .. } => Ok(4),
            Self::SPutWide { .. } => Ok(4),
            Self::SPutObject { .. } => Ok(4),
            Self::SPutBoolean { .. } => Ok(4),
            Self::SPutByte { .. } => Ok(4),
            Self::SPutChar { .. } => Ok(4),
            Self::SPutShort { .. } => Ok(4),
            Self::InvokeVirtual { .. } => Ok(6),
            Self::InvokeSuper { .. } => Ok(6),
            Self::InvokeDirect { .. } => Ok(6),
            Self::InvokeStatic { .. } => Ok(6),
            Self::InvokeInterface { .. } => Ok(6),
            Self::InvokePolymorphic { .. } => Ok(8),
            Self::InvokeCustom { .. } => Ok(6),
            Self::ConstMethodHandle { .. } => Ok(4),
            Self::ConstMethodType { .. } => Ok(4),
            Self::Try { .. } => Ok(0),
            Self::Label { .. } => Ok(0),
            Self::DebugLocal { .. } => Ok(0),
            Self::DebugEndLocal { .. } => Ok(0),
            Self::DebugEndPrologue {} => Ok(0),
            Self::DebugBeginEpilogue {} => Ok(0),
            Self::DebugSourceFile { .. } => Ok(0),
            Self::DebugLine { .. } => Ok(0),
            _ => self
                .get_raw_ins(GetRawInsParam::default())
                .map(|ins| ins.size()),
        }
    }

    pub fn sanity_check(&self) -> Result<()> {
        match self {
            Self::InstanceOf { dest, obj, .. } => sanity_check_4_bit_reg!("instance-of", dest, obj),
            Self::ArrayLength { dest, arr } => sanity_check_4_bit_reg!("array-length", dest, arr),
            Self::NewArray { reg, size_reg, .. } => {
                sanity_check_4_bit_reg!("new-array", reg, size_reg)
            }
            Self::FilledNewArray { reg_values, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = reg_values.len();
                for r in reg_values.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "filled-new-array instruction must either use 5 or \
                 less register indexed on 4 bites or less than 255 \
                 consecutive registers"
                    ))
                }
            }
            Self::IfEq { a, b, .. } => sanity_check_4_bit_reg!("if-eq", a, b),
            Self::IfNe { a, b, .. } => sanity_check_4_bit_reg!("if-neq", a, b),
            Self::IfLt { a, b, .. } => sanity_check_4_bit_reg!("if-lt", a, b),
            Self::IfGe { a, b, .. } => sanity_check_4_bit_reg!("if-ge", a, b),
            Self::IfGt { a, b, .. } => sanity_check_4_bit_reg!("if-gt", a, b),
            Self::IfLe { a, b, .. } => sanity_check_4_bit_reg!("if-le", a, b),
            Self::IGet { to, obj, .. } => sanity_check_4_bit_reg!("iget", to, obj),
            Self::IGetWide { to, obj, .. } => sanity_check_4_bit_reg!("iget-wide", to, obj),
            Self::IGetObject { to, obj, .. } => sanity_check_4_bit_reg!("iget-object", to, obj),
            Self::IGetBoolean { to, obj, .. } => sanity_check_4_bit_reg!("iget-boolean", to, obj),
            Self::IGetByte { to, obj, .. } => sanity_check_4_bit_reg!("iget-byte", to, obj),
            Self::IGetChar { to, obj, .. } => sanity_check_4_bit_reg!("iget-char", to, obj),
            Self::IGetShort { to, obj, .. } => sanity_check_4_bit_reg!("iget-short", to, obj),
            Self::IPut { from, obj, .. } => sanity_check_4_bit_reg!("iput", from, obj),
            Self::IPutWide { from, obj, .. } => sanity_check_4_bit_reg!("iput-wide", from, obj),
            Self::IPutObject { from, obj, .. } => sanity_check_4_bit_reg!("iput-object", from, obj),
            Self::IPutBoolean { from, obj, .. } => {
                sanity_check_4_bit_reg!("iput-boolean", from, obj)
            }
            Self::IPutByte { from, obj, .. } => sanity_check_4_bit_reg!("iput-byte", from, obj),
            Self::IPutChar { from, obj, .. } => sanity_check_4_bit_reg!("iput-char", from, obj),
            Self::IPutShort { from, obj, .. } => sanity_check_4_bit_reg!("iput-short", from, obj),
            Self::InvokeVirtual { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-virtual instruction must either use 5 or \
                less register indexed on 4 bites or less than 255 \
                consecutive registers"
                    ))
                }
            }
            Self::InvokeSuper { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-super instruction must either use 5 or \
                less register indexed on 4 bites or less than 255 \
                consecutive registers"
                    ))
                }
            }
            Self::InvokeDirect { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-direct instruction must either use 5 or \
                less register indexed on 4 bites or less than 255 \
                consecutive registers"
                    ))
                }
            }
            Self::InvokeStatic { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-static instruction must either use 5 or \
                less register indexed on 4 bites or less than 255 \
                consecutive registers"
                    ))
                }
            }
            Self::InvokeInterface { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-interface instruction must either use 5 or \
               less register indexed on 4 bites or less than 255 \
               consecutive registers"
                    ))
                }
            }
            Self::NegInt { dest, val } => sanity_check_4_bit_reg!("neg-int", dest, val),
            Self::NotInt { dest, val } => sanity_check_4_bit_reg!("not-int", dest, val),
            Self::NegLong { dest, val } => sanity_check_4_bit_reg!("neg-long", dest, val),
            Self::NotLong { dest, val } => sanity_check_4_bit_reg!("not-long", dest, val),
            Self::NegFloat { dest, val } => sanity_check_4_bit_reg!("neg-float", dest, val),
            Self::NegDouble { dest, val } => sanity_check_4_bit_reg!("neg-double", dest, val),
            Self::IntToLong { dest, val } => sanity_check_4_bit_reg!("int-to-long", dest, val),
            Self::IntToFloat { dest, val } => sanity_check_4_bit_reg!("int-to-float", dest, val),
            Self::IntToDouble { dest, val } => sanity_check_4_bit_reg!("int-to-double", dest, val),
            Self::LongToInt { dest, val } => sanity_check_4_bit_reg!("long-to-int", dest, val),
            Self::LongToFloat { dest, val } => sanity_check_4_bit_reg!("long-to-float", dest, val),
            Self::LongToDouble { dest, val } => {
                sanity_check_4_bit_reg!("long-to-double", dest, val)
            }
            Self::FloatToInt { dest, val } => sanity_check_4_bit_reg!("float-to-int", dest, val),
            Self::FloatToLong { dest, val } => sanity_check_4_bit_reg!("float-to-long", dest, val),
            Self::FloatToDouble { dest, val } => {
                sanity_check_4_bit_reg!("float-to-double", dest, val)
            }
            Self::DoubleToInt { dest, val } => sanity_check_4_bit_reg!("double-to-int", dest, val),
            Self::DoubleToLong { dest, val } => {
                sanity_check_4_bit_reg!("double-to-long", dest, val)
            }
            Self::DoubleToFloat { dest, val } => {
                sanity_check_4_bit_reg!("double-to-float", dest, val)
            }
            Self::IntToByte { dest, val } => sanity_check_4_bit_reg!("int-to-byte", dest, val),
            Self::IntToChar { dest, val } => sanity_check_4_bit_reg!("int-to-char", dest, val),
            Self::IntToShort { dest, val } => sanity_check_4_bit_reg!("int-to-short", dest, val),
            Self::AddInt2Addr { dest, b } => sanity_check_4_bit_reg!("add-int/2addr", dest, b),
            Self::SubInt2Addr { dest, b } => sanity_check_4_bit_reg!("sub-int/2addr", dest, b),
            Self::MulInt2Addr { dest, b } => sanity_check_4_bit_reg!("mul-int/2addr", dest, b),
            Self::DivInt2Addr { dest, b } => sanity_check_4_bit_reg!("div-int/2addr", dest, b),
            Self::RemInt2Addr { dest, b } => sanity_check_4_bit_reg!("rem-int/2addr", dest, b),
            Self::AndInt2Addr { dest, b } => sanity_check_4_bit_reg!("and-int/2addr", dest, b),
            Self::OrInt2Addr { dest, b } => sanity_check_4_bit_reg!("or-int/2addr", dest, b),
            Self::XorInt2Addr { dest, b } => sanity_check_4_bit_reg!("xor-int/2addr", dest, b),
            Self::ShlInt2Addr { dest, b } => sanity_check_4_bit_reg!("shl-int/2addr", dest, b),
            Self::ShrInt2Addr { dest, b } => sanity_check_4_bit_reg!("shr-int/2addr", dest, b),
            Self::UshrInt2Addr { dest, b } => sanity_check_4_bit_reg!("ushr-int/2addr", dest, b),
            Self::AddLong2Addr { dest, b } => sanity_check_4_bit_reg!("add-long/2addr", dest, b),
            Self::SubLong2Addr { dest, b } => sanity_check_4_bit_reg!("sub-long/2addr", dest, b),
            Self::MulLong2Addr { dest, b } => sanity_check_4_bit_reg!("mul-long/2addr", dest, b),
            Self::DivLong2Addr { dest, b } => sanity_check_4_bit_reg!("div-long/2addr", dest, b),
            Self::RemLong2Addr { dest, b } => sanity_check_4_bit_reg!("rem-long/2addr", dest, b),
            Self::AndLong2Addr { dest, b } => sanity_check_4_bit_reg!("and-long/2addr", dest, b),
            Self::OrLong2Addr { dest, b } => sanity_check_4_bit_reg!("or-long/2addr", dest, b),
            Self::XorLong2Addr { dest, b } => sanity_check_4_bit_reg!("xor-long/2addr", dest, b),
            Self::ShlLong2Addr { dest, b } => sanity_check_4_bit_reg!("shl-long/2addr", dest, b),
            Self::ShrLong2Addr { dest, b } => sanity_check_4_bit_reg!("shr-long/2addr", dest, b),
            Self::UshrLong2Addr { dest, b } => sanity_check_4_bit_reg!("ushr-long/2addr", dest, b),
            Self::AddFloat2Addr { dest, b } => sanity_check_4_bit_reg!("add-float/2addr", dest, b),
            Self::SubFloat2Addr { dest, b } => sanity_check_4_bit_reg!("sub-float/2addr", dest, b),
            Self::MulFloat2Addr { dest, b } => sanity_check_4_bit_reg!("mul-float/2addr", dest, b),
            Self::DivFloat2Addr { dest, b } => sanity_check_4_bit_reg!("div-float/2addr", dest, b),
            Self::RemFloat2Addr { dest, b } => sanity_check_4_bit_reg!("rem-float/2addr", dest, b),
            Self::AddDouble2Addr { dest, b } => {
                sanity_check_4_bit_reg!("add-double/2addr", dest, b)
            }
            Self::SubDouble2Addr { dest, b } => {
                sanity_check_4_bit_reg!("sub-double/2addr", dest, b)
            }
            Self::MulDouble2Addr { dest, b } => {
                sanity_check_4_bit_reg!("mul-double/2addr", dest, b)
            }
            Self::DivDouble2Addr { dest, b } => {
                sanity_check_4_bit_reg!("div-double/2addr", dest, b)
            }
            Self::RemDouble2Addr { dest, b } => {
                sanity_check_4_bit_reg!("rem-double/2addr", dest, b)
            }
            Self::AddIntLit { dest, b, lit } => sanity_check_22b_or_22s!("add-int", dest, b, lit),
            Self::RsubIntLit { dest, b, lit } => sanity_check_22b_or_22s!("rsub-int", dest, b, lit),
            Self::MulIntLit { dest, b, lit } => sanity_check_22b_or_22s!("mul-int", dest, b, lit),
            Self::DivIntLit { dest, b, lit } => sanity_check_22b_or_22s!("div-int", dest, b, lit),
            Self::RemIntLit { dest, b, lit } => sanity_check_22b_or_22s!("rem-int", dest, b, lit),
            Self::AndIntLit { dest, b, lit } => sanity_check_22b_or_22s!("and-int", dest, b, lit),
            Self::OrIntLit { dest, b, lit } => sanity_check_22b_or_22s!("or-int", dest, b, lit),
            Self::XorIntLit { dest, b, lit } => sanity_check_22b_or_22s!("xor-int", dest, b, lit),
            Self::InvokePolymorphic { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-polymorphic instruction must either use 5 or \
                        less register indexed on 4 bites or less than 255 \
                        consecutive registers"
                    ))
                }
            }
            Self::InvokeCustom { args, .. } => {
                let mut last = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if (four_bites && len <= 5) || (consec && len <= 255) {
                    Ok(())
                } else {
                    Err(anyhow!(
                        "invoke-custom instruction must either use 5 or \
                        less register indexed on 4 bites or less than 255 \
                        consecutive registers"
                    ))
                }
            }
            _ => Ok(()),
        }
    }
    /// Return all strings referenced in the instruction.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut visitor = StringCollector::default();
        visitor.visit_instruction(self).unwrap();
        visitor.result()
    }

    /// Return all types referenced in the instruction.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut visitor = TypeCollector::default();
        visitor.visit_instruction(self).unwrap();
        visitor.result()
    }

    /// Return all prototypes referenced in the instruction.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut visitor = MethodTypeCollector::default();
        visitor.visit_instruction(self).unwrap();
        visitor.result()
    }

    /// Return all field ids referenced in the instruction.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut visitor = FieldIdCollector::default();
        visitor.visit_instruction(self).unwrap();
        visitor.result()
    }

    /// Return all method ids referenced in the instruction.
    pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
        let mut visitor = MethodIdCollector::default();
        visitor.visit_instruction(self).unwrap();
        visitor.result()
    }

    /// Return all method handles referenced in the instruction.
    pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
        let mut visitor = MethodHandleCollector::default();
        visitor.visit_instruction(self).unwrap();
        visitor.result()
    }
}

#[derive(Debug, Default)]
pub struct GetRawInsParam<'a> {
    pub strings: Option<&'a HashMap<DexString, usize>>,
    pub type_ids: Option<&'a HashMap<IdType, usize>>,
    pub field_ids: Option<&'a HashMap<IdField, usize>>,
    pub method_ids: Option<&'a HashMap<IdMethod, usize>>,
    pub proto_ids: Option<&'a HashMap<IdMethodType, usize>>,
    pub call_site_idx: Option<usize>,
    pub method_handle_idx: Option<usize>,
    pub jump_data: Option<(usize, &'a HashMap<String, usize>)>,
    pub goto_size: Option<usize>,
    pub data_offset: Option<i32>,
}

impl Instruction {
    /// Return the raw instruction ([`InsFormat`]) when it does not need additionnal data.
    ///
    /// Some instruction require additional data, provided as options. If the required information
    /// is not provided, the method will return an error.
    ///
    /// The parameters of the functions are pass as a [`GetRawInsParam`] to make is easier track
    /// which parameter is which and make it easier to pass only one or two values.
    ///
    /// # Variants that require `strings`
    ///
    /// `strings` is a lookup table that associate its idx to a string.
    ///
    /// - `ConstString`
    ///
    /// # Variants that require `types_ids`
    ///
    /// `types_ids` is a lookup table that associate its idx to a type.
    ///
    /// - `ConstClass`
    /// - `CheckCast`
    /// - `InstanceOf`
    /// - `NewInstance`
    /// - `NewArray`
    /// - `FilledNewArray`
    ///
    /// # Variants that require `field_ids`
    ///
    /// `field_ids` is a lookup table that associate its idx to a field.
    ///
    /// - `IGet`
    /// - `IGetWide`
    /// - `IGetObject`
    /// - `IGetBoolean`
    /// - `IGetByte`
    /// - `IGetChar`
    /// - `IGetShort`
    /// - `IPut`
    /// - `IPutWide`
    /// - `IPutObject`
    /// - `IPutBoolean`
    /// - `IPutByte`
    /// - `IPutChar`
    /// - `IPutShort`
    /// - `SGet`
    /// - `SGetWide`
    /// - `SGetObject`
    /// - `SGetBoolean`
    /// - `SGetByte`
    /// - `SGetChar`
    /// - `SGetShort`
    /// - `SPut`
    /// - `SPutWide`
    /// - `SPutObject`
    /// - `SPutBoolean`
    /// - `SPutByte`
    /// - `SPutChar`
    /// - `SPutShort`
    ///
    /// # Variants that require `method_ids`
    ///
    /// `method_ids` is a lookup table that associate its idx to a method.
    ///
    /// - `InvokeVirtual`
    /// - `InvokeSuper`
    /// - `InvokeDirect`
    /// - `InvokeStatic`
    /// - `InvokeInterface`
    /// - `InvokePolymorphic`
    ///
    /// # Variants that require `proto_ids`
    ///
    /// `proto_ids` is a lookup table that associate its idx to a method type.
    ///
    /// - `InvokePolymorphic`
    /// - `ConstMethodType`
    ///
    /// # Variants that require `call_site_idx`
    ///
    /// `call_site_idx` is the idx of the call site used by the instruction
    ///
    /// - `InvokeCustom`
    ///
    /// # Variants that require `method_handle_idx`
    ///
    /// `method_handle_idx` is the idx of the method handle used by the instruction
    ///
    /// - `ConstMethodHandle`
    ///
    /// # Variants that require `data_offset`
    ///
    /// `data_offset` is the offset from the instruction to the location of the data table.
    ///
    /// - `FillArrayData`
    /// - `Switch`
    ///
    /// # Variants that require `goto_size`
    ///
    /// `goto_size` is the size of the goto instruction (wich can be greater than the best size
    /// beause the algo used to compute offset trash)
    ///
    /// - `Goto`
    ///
    /// # Variants that require `jump_data`
    ///
    /// `jump_data` is the address of the current instruction and the lookup table that associate a
    /// label with its addresse.
    ///
    /// - `Goto`
    /// - `IfEq`
    /// - `IfNe`
    /// - `IfLt`
    /// - `IfGe`
    /// - `IfGt`
    /// - `IfLe`
    /// - `IfEqZ`
    /// - `IfNeZ`
    /// - `IfLtZ`
    /// - `IfGeZ`
    /// - `IfGtZ`
    /// - `IfLeZ`
    ///
    /// # Variants that do not implement this method:
    ///
    /// - `Try`
    /// - `Label`
    /// - `DebugLocal`
    /// - `DebugEndLocal`
    /// - `DebugEndPrologue`
    /// - `DebugBeginEpilogue`
    /// - `DebugSourceFile`
    /// - `DebugLine`
    ///
    pub fn get_raw_ins(&self, param: GetRawInsParam) -> Result<InsFormat> {
        match (self, param) {
            (Self::ConstString { .. }, GetRawInsParam { strings: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a const-string without knowing the string idx"
            )),
            (
                Self::ConstString { reg, lit },
                GetRawInsParam {
                    strings: Some(strings),
                    ..
                },
            ) => {
                let string_idx = strings.get(lit).ok_or(anyhow!(
                    "String {} not found in dex builder",
                    lit.__repr__(),
                ))?;
                match string_idx {
                    0..=U16_MAX_AS_USIZE => Ok(InsFormat::Format21C {
                        op: 0x1a,
                        va: *reg,
                        b: *string_idx as u16,
                    }),
                    _ => Ok(InsFormat::Format31C {
                        op: 0x1b,
                        va: *reg,
                        b: *string_idx as u32,
                    }),
                }
            }
            (Self::ConstClass { .. }, GetRawInsParam { type_ids: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a const-class without knowing the class idx"
            )),
            (
                Self::ConstClass { reg, lit },
                GetRawInsParam {
                    type_ids: Some(types),
                    ..
                },
            ) => Ok(InsFormat::Format21C {
                op: 0x1c,
                va: *reg,
                b: *types
                    .get(lit)
                    .ok_or(anyhow!("Class {} not found in dex builder", lit.__repr__(),))?
                    as u16,
            }),
            (Self::CheckCast { .. }, GetRawInsParam { type_ids: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a check-cast without knowing the type idx"
            )),
            (
                Self::CheckCast { reg, lit },
                GetRawInsParam {
                    type_ids: Some(types),
                    ..
                },
            ) => Ok(InsFormat::Format21C {
                op: 0x1f,
                va: *reg,
                b: *types
                    .get(lit)
                    .ok_or(anyhow!("Class {} not found in dex builder", lit.__repr__(),))?
                    as u16,
            }),
            (Self::InstanceOf { .. }, GetRawInsParam { type_ids: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a instance-of without knowing the type idx"
            )),
            (
                Self::InstanceOf { dest, obj, lit },
                GetRawInsParam {
                    type_ids: Some(types),
                    ..
                },
            ) => Ok(InsFormat::Format22C {
                op: 0x20,
                va: *dest,
                vb: *obj,
                c: *types
                    .get(lit)
                    .ok_or(anyhow!("Class {} not found in dex builder", lit.__repr__(),))?
                    as u16,
            }),
            (Self::NewInstance { .. }, GetRawInsParam { type_ids: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a new-instance without knowing the class idx"
            )),
            (
                Self::NewInstance { reg, lit },
                GetRawInsParam {
                    type_ids: Some(types),
                    ..
                },
            ) => Ok(InsFormat::Format21C {
                op: 0x22,
                va: *reg,
                b: *types
                    .get(lit)
                    .ok_or(anyhow!("Class {} not found in dex builder", lit.__repr__(),))?
                    as u16,
            }),
            (Self::NewArray { .. }, GetRawInsParam { type_ids: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a new-array without knowing the type idx"
            )),
            (
                Self::NewArray { reg, size_reg, lit },
                GetRawInsParam {
                    type_ids: Some(types),
                    ..
                },
            ) => Ok(InsFormat::Format22C {
                op: 0x23,
                va: *reg,
                vb: *size_reg,
                c: *types
                    .get(lit)
                    .ok_or(anyhow!("Type {} not found in dex builder", lit.__repr__(),))?
                    as u16,
            }),
            (Self::FilledNewArray { .. }, GetRawInsParam { type_ids: None, .. }) => Err(anyhow!(
                "Cannot get the raw instruction of a filled-new-array without knowing the type idx"
            )),
            (
                Self::FilledNewArray { reg_values, type_ },
                GetRawInsParam {
                    type_ids: Some(types),
                    ..
                },
            ) => {
                let mut last = None;
                let mut first = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = reg_values.len();
                for r in reg_values.iter().cloned() {
                    if first.is_none() {
                        first = Some(r);
                    }
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if four_bites && len <= 5 {
                    let mut regs = vec![];
                    for reg in reg_values.iter().cloned() {
                        regs.push(reg);
                    }
                    while regs.len() != 5 {
                        regs.push(0);
                    }
                    let [vc, vd, ve, vf, vg]: [u8; 5] = regs
                        .into_iter()
                        .map(|r| r as u8)
                        .collect::<Vec<_>>()
                        .try_into()
                        .ok()
                        .unwrap();
                    let a = reg_values.len() as u8;
                    Ok(InsFormat::Format35C {
                        op: 0x24,
                        a,
                        vc,
                        ve,
                        vd,
                        vf,
                        vg,
                        b: *types.get(type_).ok_or(anyhow!(
                            "Type {} not found in dex builder",
                            type_.__repr__(),
                        ))? as u16,
                    })
                } else if consec && len <= 255 {
                    let a = reg_values.len() as u8;
                    let vc = first.unwrap_or(0);
                    Ok(InsFormat::Format3RC {
                        op: 0x25,
                        a,
                        vc,
                        b: *types.get(type_).ok_or(anyhow!(
                            "Type {} not found in dex builder",
                            type_.__repr__(),
                        ))? as u16,
                    })
                } else {
                    // Not supposed to happend with a sanitized array
                    panic!("Invalid NewArrayInstruction {self:?}")
                }
            }
            (
                Self::FillArrayData { .. },
                GetRawInsParam {
                    data_offset: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a fill-array-data without knowing the offset \
                    to the -fill-array-data-payload"
            )),
            (
                Self::FillArrayData { arr, .. },
                GetRawInsParam {
                    data_offset: Some(data_offset),
                    ..
                },
            ) => Ok(InsFormat::Format31T {
                op: 0x26,
                va: *arr,
                b: data_offset,
            }),
            (
                Self::Goto { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the size of a goto without knowing the branch offset"
            )),
            (
                Self::Goto { .. },
                GetRawInsParam {
                    goto_size: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the size of a goto without knowing the size of the instruction"
            )),
            (
                Self::Goto { label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    goto_size: Some(goto_size),
                    ..
                },
            ) => {
                let label_addr = label_addrs.get(label).ok_or(anyhow!(
                    "Label {} not found, but found goto with this label",
                    label,
                ))?;
                let branch_offset = *label_addr as i32 - addr as i32;
                if goto_size == 2 && (I8_MIN_AS_I32..=I8_MAX_AS_I32).contains(&branch_offset) {
                    Ok(InsFormat::Format10T {
                        op: 0x28,
                        a: branch_offset as i8,
                    })
                } else if goto_size == 4
                    && (I16_MIN_AS_I32..=I16_MAX_AS_I32).contains(&branch_offset)
                {
                    Ok(InsFormat::Format20T {
                        op: 0x29,
                        a: branch_offset as i16,
                    })
                } else if goto_size == 6 {
                    Ok(InsFormat::Format30T {
                        op: 0x2a,
                        a: branch_offset,
                    })
                } else {
                    Err(anyhow!("Invalid goto_size and/or data_offset value"))
                }
            }
            (
                Self::Switch { .. },
                GetRawInsParam {
                    data_offset: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a switch without knowing the offset \
                    to the packed/sparse-switch-payload"
            )),
            (
                Self::Switch { reg, .. },
                GetRawInsParam {
                    data_offset: Some(data_offset),
                    ..
                },
            ) => Ok(InsFormat::Format31T {
                op: if self.is_packed()? { 0x2b } else { 0x2c },
                va: *reg,
                b: data_offset,
            }),
            (
                Self::IfEq { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-eq without knowing \
                    the offset to the branch"
            )),
            (
                Self::IfNe { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-ne without knowing \
                the offset to the branch"
            )),
            (
                Self::IfLt { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-lt without knowing \
                the offset to the branch"
            )),
            (
                Self::IfGe { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-ge without knowing \
                the offset to the branch"
            )),
            (
                Self::IfGt { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-gt without knowing \
                the offset to the branch"
            )),
            (
                Self::IfLe { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-le without knowing \
                the offset to the branch"
            )),
            (
                Self::IfEqZ { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-eqz without knowing \
                the offset to the branch"
            )),
            (
                Self::IfNeZ { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-nez without knowing \
                the offset to the branch"
            )),
            (
                Self::IfLtZ { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-ltz without knowing \
                the offset to the branch"
            )),
            (
                Self::IfGeZ { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-gez without knowing \
                the offset to the branch"
            )),
            (
                Self::IfGtZ { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-gtz without knowing \
                the offset to the branch"
            )),
            (
                Self::IfLeZ { .. },
                GetRawInsParam {
                    jump_data: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a if-lez without knowing \
                the offset to the branch"
            )),
            (
                Self::IfEq { a, b, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_if!(0x32, label_addrs, label, addr, a, b),
            (
                Self::IfNe { a, b, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_if!(0x33, label_addrs, label, addr, a, b),
            (
                Self::IfLt { a, b, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_if!(0x34, label_addrs, label, addr, a, b),
            (
                Self::IfGe { a, b, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_if!(0x35, label_addrs, label, addr, a, b),
            (
                Self::IfGt { a, b, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_if!(0x36, label_addrs, label, addr, a, b),
            (
                Self::IfLe { a, b, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_if!(0x37, label_addrs, label, addr, a, b),
            (
                Self::IfEqZ { a, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_ifz!(0x38, label_addrs, label, addr, a),
            (
                Self::IfNeZ { a, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_ifz!(0x39, label_addrs, label, addr, a),
            (
                Self::IfLtZ { a, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_ifz!(0x3a, label_addrs, label, addr, a),
            (
                Self::IfGeZ { a, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_ifz!(0x3b, label_addrs, label, addr, a),
            (
                Self::IfGtZ { a, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_ifz!(0x3c, label_addrs, label, addr, a),
            (
                Self::IfLeZ { a, label },
                GetRawInsParam {
                    jump_data: Some((addr, label_addrs)),
                    ..
                },
            ) => raw_ins_ifz!(0x3d, label_addrs, label, addr, a),
            (
                Self::IGet { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget without knowing the field idx"
            )),
            (
                Self::IGetWide { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget-wide without knowing the field idx"
            )),
            (
                Self::IGetObject { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget-object without knowing the field idx"
            )),
            (
                Self::IGetBoolean { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget-boolean without knowing the field idx"
            )),
            (
                Self::IGetByte { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget-byte without knowing the field idx"
            )),
            (
                Self::IGetChar { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget-char without knowing the field idx"
            )),
            (
                Self::IGetShort { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iget-short without knowing the field idx"
            )),
            (
                Self::IPut { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput without knowing the field idx"
            )),
            (
                Self::IPutWide { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput-wide without knowing the field idx"
            )),
            (
                Self::IPutObject { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput-object without knowing the field idx"
            )),
            (
                Self::IPutBoolean { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput-boolean without knowing the field idx"
            )),
            (
                Self::IPutByte { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput-byte without knowing the field idx"
            )),
            (
                Self::IPutChar { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput-char without knowing the field idx"
            )),
            (
                Self::IPutShort { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a iput-short without knowing the field idx"
            )),
            (
                Self::IGet { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x52, fields, field, to, obj),
            (
                Self::IGetWide { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x53, fields, field, to, obj),
            (
                Self::IGetObject { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x54, fields, field, to, obj),
            (
                Self::IGetBoolean { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x55, fields, field, to, obj),
            (
                Self::IGetByte { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x56, fields, field, to, obj),
            (
                Self::IGetChar { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x57, fields, field, to, obj),
            (
                Self::IGetShort { to, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x58, fields, field, to, obj),
            (
                Self::IPut { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x59, fields, field, from, obj),
            (
                Self::IPutWide { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x5a, fields, field, from, obj),
            (
                Self::IPutObject { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x5b, fields, field, from, obj),
            (
                Self::IPutBoolean { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x5c, fields, field, from, obj),
            (
                Self::IPutByte { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x5d, fields, field, from, obj),
            (
                Self::IPutChar { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x5e, fields, field, from, obj),
            (
                Self::IPutShort { from, obj, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_iget_put!(0x5f, fields, field, from, obj),
            (
                Self::SGet { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget without knowing the field idx"
            )),
            (
                Self::SGetWide { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget-wide without knowing the field idx"
            )),
            (
                Self::SGetObject { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget-object without knowing the field idx"
            )),
            (
                Self::SGetBoolean { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget-boolean without knowing the field idx"
            )),
            (
                Self::SGetByte { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget-byte without knowing the field idx"
            )),
            (
                Self::SGetChar { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget-char without knowing the field idx"
            )),
            (
                Self::SGetShort { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sget-short without knowing the field idx"
            )),
            (
                Self::SPut { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput without knowing the field idx"
            )),
            (
                Self::SPutWide { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput-wide without knowing the field idx"
            )),
            (
                Self::SPutObject { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput-object without knowing the field idx"
            )),
            (
                Self::SPutBoolean { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput-boolean without knowing the field idx"
            )),
            (
                Self::SPutByte { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput-byte without knowing the field idx"
            )),
            (
                Self::SPutChar { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput-char without knowing the field idx"
            )),
            (
                Self::SPutShort { .. },
                GetRawInsParam {
                    field_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a sput-short without knowing the field idx"
            )),
            (
                Self::SGet { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x60, fields, field, to),
            (
                Self::SGetWide { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x61, fields, field, to),
            (
                Self::SGetObject { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x62, fields, field, to),
            (
                Self::SGetBoolean { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x63, fields, field, to),
            (
                Self::SGetByte { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x64, fields, field, to),
            (
                Self::SGetChar { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x65, fields, field, to),
            (
                Self::SGetShort { to, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x66, fields, field, to),
            (
                Self::SPut { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x67, fields, field, from),
            (
                Self::SPutWide { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x68, fields, field, from),
            (
                Self::SPutObject { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x69, fields, field, from),
            (
                Self::SPutBoolean { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x6a, fields, field, from),
            (
                Self::SPutByte { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x6b, fields, field, from),
            (
                Self::SPutChar { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x6c, fields, field, from),
            (
                Self::SPutShort { from, field },
                GetRawInsParam {
                    field_ids: Some(fields),
                    ..
                },
            ) => raw_ins_sget_put!(0x6d, fields, field, from),
            (
                Self::InvokeVirtual { .. },
                GetRawInsParam {
                    method_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-virtual without knowing \
                the method idx"
            )),
            (
                Self::InvokeSuper { .. },
                GetRawInsParam {
                    method_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-super without knowing \
                the method idx"
            )),
            (
                Self::InvokeDirect { .. },
                GetRawInsParam {
                    method_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-direct without knowing \
                the method idx"
            )),
            (
                Self::InvokeStatic { .. },
                GetRawInsParam {
                    method_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-static without knowing \
                the method idx"
            )),
            (
                Self::InvokeInterface { .. },
                GetRawInsParam {
                    method_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-interface without knowing \
                the method idx"
            )),
            (
                Self::InvokeVirtual { args, method },
                GetRawInsParam {
                    method_ids: Some(methods),
                    ..
                },
            ) => raw_ins_invoke!(0x6e, 0x74, methods, method, args),
            (
                Self::InvokeSuper { args, method },
                GetRawInsParam {
                    method_ids: Some(methods),
                    ..
                },
            ) => raw_ins_invoke!(0x6f, 0x75, methods, method, args),
            (
                Self::InvokeDirect { args, method },
                GetRawInsParam {
                    method_ids: Some(methods),
                    ..
                },
            ) => raw_ins_invoke!(0x70, 0x76, methods, method, args),
            (
                Self::InvokeStatic { args, method },
                GetRawInsParam {
                    method_ids: Some(methods),
                    ..
                },
            ) => raw_ins_invoke!(0x71, 0x77, methods, method, args),
            (
                Self::InvokeInterface { args, method },
                GetRawInsParam {
                    method_ids: Some(methods),
                    ..
                },
            ) => raw_ins_invoke!(0x72, 0x78, methods, method, args),
            (
                Self::InvokePolymorphic { .. },
                GetRawInsParam {
                    method_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-polymorphic without knowing \
                    the method idx"
            )),
            (
                Self::InvokePolymorphic { .. },
                GetRawInsParam {
                    proto_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-polymorphic without knowing \
                    the proto idx"
            )),
            (
                Self::InvokePolymorphic {
                    args,
                    method,
                    proto,
                },
                GetRawInsParam {
                    method_ids: Some(methods),
                    proto_ids: Some(protos),
                    ..
                },
            ) => {
                let meth_idx = methods.get(method).ok_or(anyhow!(
                    "Method {} (method of class {}) not found in dex builder",
                    method.__repr__(),
                    method.class_.__repr__(),
                ))?;
                let proto_idx = protos.get(proto).ok_or(anyhow!(
                    "Prototype {} not found in dex builder",
                    proto.__repr__(),
                ))?;
                debug_assert!(
                    *meth_idx <= u16::MAX as usize,
                    "methode id too big for invoke instruction"
                );
                debug_assert!(
                    *proto_idx <= u16::MAX as usize,
                    "proto id too big for invoke instruction"
                );
                let meth_idx = *meth_idx as u16;
                let proto_idx = *proto_idx as u16;
                let mut last = None;
                let mut first = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if first.is_none() {
                        first = Some(r);
                    }
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if four_bites && len <= 5 {
                    let mut regs = vec![];
                    for reg in args.iter().cloned() {
                        regs.push(reg);
                    }
                    while regs.len() != 5 {
                        regs.push(0);
                    }
                    let [vc, vd, ve, vf, vg]: [u8; 5] = regs
                        .into_iter()
                        .map(|r| r as u8)
                        .collect::<Vec<_>>()
                        .try_into()
                        .ok()
                        .unwrap();
                    let a = args.len() as u8;
                    Ok(InsFormat::Format45CC {
                        op: 0xfa,
                        a,
                        vc,
                        ve,
                        vd,
                        vf,
                        vg,
                        b: meth_idx,
                        h: proto_idx,
                    })
                } else if consec && len <= 255 {
                    let a = args.len() as u8;
                    let vc = first.unwrap_or(0);
                    Ok(InsFormat::Format4RCC {
                        op: 0xfb,
                        a,
                        vc,
                        b: meth_idx,
                        h: proto_idx,
                    })
                } else {
                    // Not supposed to happend with a sanitized invoke
                    bail!("Invalid Invoke instruction {self:?}")
                }
            }
            (
                Self::InvokeCustom { .. },
                GetRawInsParam {
                    call_site_idx: None,
                    ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a invoke-custom without knowing the \
                call site idx"
            )),
            (
                Self::InvokeCustom { args, .. },
                GetRawInsParam {
                    call_site_idx: Some(call_site_idx),
                    ..
                },
            ) => {
                let mut last = None;
                let mut first = None;
                let mut consec = true;
                let mut four_bites = true;
                let len = args.len();
                for r in args.iter().cloned() {
                    if first.is_none() {
                        first = Some(r);
                    }
                    if let Some(last) = last {
                        if r != last + 1 {
                            consec = false;
                        }
                    }
                    if r & 0b1111_0000 != 0 {
                        four_bites = false;
                    }
                    last = Some(r);
                }
                if four_bites && len <= 5 {
                    let mut regs = vec![];
                    for reg in args.iter().cloned() {
                        regs.push(reg);
                    }
                    while regs.len() != 5 {
                        regs.push(0);
                    }
                    let [vc, vd, ve, vf, vg]: [u8; 5] = regs
                        .into_iter()
                        .map(|r| r as u8)
                        .collect::<Vec<_>>()
                        .try_into()
                        .ok()
                        .unwrap();
                    let a = args.len() as u8;
                    Ok(InsFormat::Format35C {
                        op: 0xfc,
                        a,
                        vc,
                        ve,
                        vd,
                        vf,
                        vg,
                        b: call_site_idx as u16,
                    })
                } else if consec && len <= 255 {
                    let a = args.len() as u8;
                    let vc = first.unwrap_or(0);
                    Ok(InsFormat::Format3RC {
                        op: 0xfd,
                        a,
                        vc,
                        b: call_site_idx as u16,
                    })
                } else {
                    // Not supposed to happend with a sanitized invoke
                    bail!("Invalid Invoke instruction {self:?}")
                }
            }
            (
                Self::ConstMethodHandle { .. },
                GetRawInsParam {
                    method_handle_idx: None,
                    ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a const-method-handle \
                    without knowing the method handle idx"
            )),
            (
                Self::ConstMethodHandle { to, .. },
                GetRawInsParam {
                    method_handle_idx: Some(handle_idx),
                    ..
                },
            ) => Ok(InsFormat::Format21C {
                op: 0xfe,
                va: *to,
                b: handle_idx as u16,
            }),
            (
                Self::ConstMethodType { .. },
                GetRawInsParam {
                    proto_ids: None, ..
                },
            ) => Err(anyhow!(
                "Cannot get the raw instruction of a const-method-type \
                    without knowing the proto idx"
            )),
            (
                Self::ConstMethodType { to, proto },
                GetRawInsParam {
                    proto_ids: Some(protos),
                    ..
                },
            ) => {
                let proto_idx = protos.get(proto).ok_or(anyhow!(
                    "Prototype {} not found in dex builder",
                    proto.__repr__(),
                ))?;
                Ok(InsFormat::Format21C {
                    op: 0xff,
                    va: *to,
                    b: *proto_idx as u16,
                })
            }
            (Self::Try { .. }, GetRawInsParam { .. }) => Err(anyhow!(
                "Try instruction cannot be converted to raw instruction"
            )),
            (Self::Label { .. }, GetRawInsParam { .. }) => {
                Err(anyhow!("Label cannot be converted to raw instruction"))
            }
            (Self::DebugLocal { .. }, GetRawInsParam { .. }) => Err(anyhow!(
                "Debug information cannot be converted to raw instruction"
            )),
            (Self::DebugEndLocal { .. }, GetRawInsParam { .. }) => Err(anyhow!(
                "Debug information cannot be converted to raw instruction"
            )),
            (Self::DebugEndPrologue {}, GetRawInsParam { .. }) => Err(anyhow!(
                "Debug information cannot be converted to raw instruction"
            )),
            (Self::DebugBeginEpilogue {}, GetRawInsParam { .. }) => Err(anyhow!(
                "Debug information cannot be converted to raw instruction"
            )),
            (Self::DebugSourceFile { .. }, GetRawInsParam { .. }) => Err(anyhow!(
                "Debug information cannot be converted to raw instruction"
            )),
            (Self::DebugLine { .. }, GetRawInsParam { .. }) => Err(anyhow!(
                "Debug information cannot be converted to raw instruction"
            )),
            (Self::Nop {}, GetRawInsParam { .. }) => Ok(InsFormat::Format10X { op: 0x00 }),
            (Self::Move { from, to }, GetRawInsParam { .. }) => match (to, from) {
                (0..=0b0000_1111, 0..=0b0000_1111) => Ok(InsFormat::Format12X {
                    op: 0x01,
                    va: *to as u8,
                    vb: *from as u8,
                }),
                (0..=0xff, _) => Ok(InsFormat::Format22X {
                    op: 0x02,
                    va: *to as u8,
                    vb: *from,
                }),
                (_, _) => Ok(InsFormat::Format32X {
                    op: 0x03,
                    va: *to,
                    vb: *from,
                }),
            },
            (Self::MoveWide { from, to }, GetRawInsParam { .. }) => match (to, from) {
                (0..=0b0000_1111, 0..=0b0000_1111) => Ok(InsFormat::Format12X {
                    op: 0x04,
                    va: *to as u8,
                    vb: *from as u8,
                }),
                (0..=0xff, _) => Ok(InsFormat::Format22X {
                    op: 0x05,
                    va: *to as u8,
                    vb: *from,
                }),
                (_, _) => Ok(InsFormat::Format32X {
                    op: 0x06,
                    va: *to,
                    vb: *from,
                }),
            },
            (Self::MoveObject { from, to }, GetRawInsParam { .. }) => match (to, from) {
                (0..=0b0000_1111, 0..=0b0000_1111) => Ok(InsFormat::Format12X {
                    op: 0x07,
                    va: *to as u8,
                    vb: *from as u8,
                }),
                (0..=0xff, _) => Ok(InsFormat::Format22X {
                    op: 0x08,
                    va: *to as u8,
                    vb: *from,
                }),
                (_, _) => Ok(InsFormat::Format32X {
                    op: 0x09,
                    va: *to,
                    vb: *from,
                }),
            },
            (Self::MoveResult { to }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x0a, va: *to })
            }
            (Self::MoveResultWide { to }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x0b, va: *to })
            }
            (Self::MoveResultObject { to }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x0c, va: *to })
            }
            (Self::MoveException { to }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x0d, va: *to })
            }
            (Self::ReturnVoid {}, GetRawInsParam { .. }) => Ok(InsFormat::Format10X { op: 0x0e }),
            (Self::Return { reg }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x0f, va: *reg })
            }
            (Self::ReturnWide { reg }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x10, va: *reg })
            }
            (Self::ReturnObject { reg }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x11, va: *reg })
            }
            (Self::Const { lit, reg }, GetRawInsParam { .. }) => match (reg, lit) {
                (0..=0b0000_1111, -8..=7) => Ok(InsFormat::Format11N {
                    op: 0x12,
                    va: *reg,
                    b: *lit as i8,
                }),
                (_, I16_MIN_AS_I32..=I16_MAX_AS_I32) => Ok(InsFormat::Format21S {
                    op: 0x13,
                    va: *reg,
                    b: *lit as i16,
                }),
                (_, lit) if lit % 0x1_0000 == 0 => Ok(InsFormat::Format21H {
                    op: 0x15,
                    va: *reg,
                    b: (*lit / 0x1_0000) as i16,
                }),
                (_, _) => Ok(InsFormat::Format31I {
                    op: 0x14,
                    va: *reg,
                    b: *lit,
                }),
            },
            (Self::ConstWide { lit, reg }, GetRawInsParam { .. }) => match lit {
                I16_MIN_AS_I64..=I16_MAX_AS_I64 => Ok(InsFormat::Format21S {
                    op: 0x16,
                    va: *reg,
                    b: *lit as i16,
                }),
                I32_MIN_AS_I64..=I32_MAX_AS_I64 => Ok(InsFormat::Format31I {
                    op: 0x17,
                    va: *reg,
                    b: *lit as i32,
                }),
                lit if lit % 0x1_0000_0000_0000 == 0 => Ok(InsFormat::Format21H {
                    op: 0x19,
                    va: *reg,
                    b: (*lit / 0x1_0000_0000_0000) as i16,
                }),
                _ => Ok(InsFormat::Format51L {
                    op: 0x18,
                    va: *reg,
                    b: *lit,
                }),
            },
            (Self::MonitorEnter { reg }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x1d, va: *reg })
            }
            (Self::MonitorExit { reg }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x1e, va: *reg })
            }
            (Self::ArrayLength { dest, arr }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x21,
                va: *dest,
                vb: *arr,
            }),
            (Self::Throw { reg }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format11X { op: 0x27, va: *reg })
            }
            (Self::CmpLFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x2d,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::CmpGFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x2e,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::CmpLDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x2f,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::CmpGDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x30,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::CmpLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x31,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AGet { dest, arr, idx }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x44,
                va: *dest,
                vb: *arr,
                vc: *idx,
            }),
            (Self::AGetWide { dest, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x45,
                    va: *dest,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::AGetObject { dest, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x46,
                    va: *dest,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::AGetBoolean { dest, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x47,
                    va: *dest,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::AGetByte { dest, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x48,
                    va: *dest,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::AGetChar { dest, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x49,
                    va: *dest,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::AGetShort { dest, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x4a,
                    va: *dest,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::APut { from, arr, idx }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x4b,
                va: *from,
                vb: *arr,
                vc: *idx,
            }),
            (Self::APutWide { from, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x4c,
                    va: *from,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::APutObject { from, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x4d,
                    va: *from,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::APutBoolean { from, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x4e,
                    va: *from,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::APutByte { from, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x4f,
                    va: *from,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::APutChar { from, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x50,
                    va: *from,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::APutShort { from, arr, idx }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format23X {
                    op: 0x51,
                    va: *from,
                    vb: *arr,
                    vc: *idx,
                })
            }
            (Self::NegInt { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x7b,
                va: *dest,
                vb: *val,
            }),
            (Self::NotInt { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x7c,
                va: *dest,
                vb: *val,
            }),
            (Self::NegLong { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x7d,
                va: *dest,
                vb: *val,
            }),
            (Self::NotLong { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x7e,
                va: *dest,
                vb: *val,
            }),
            (Self::NegFloat { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x7f,
                va: *dest,
                vb: *val,
            }),
            (Self::NegDouble { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x80,
                va: *dest,
                vb: *val,
            }),
            (Self::IntToLong { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x81,
                va: *dest,
                vb: *val,
            }),
            (Self::IntToFloat { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x82,
                va: *dest,
                vb: *val,
            }),
            (Self::IntToDouble { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x83,
                va: *dest,
                vb: *val,
            }),
            (Self::LongToInt { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x84,
                va: *dest,
                vb: *val,
            }),
            (Self::LongToFloat { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x85,
                va: *dest,
                vb: *val,
            }),
            (Self::LongToDouble { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x86,
                va: *dest,
                vb: *val,
            }),
            (Self::FloatToInt { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x87,
                va: *dest,
                vb: *val,
            }),
            (Self::FloatToLong { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x88,
                va: *dest,
                vb: *val,
            }),
            (Self::FloatToDouble { dest, val }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format12X {
                    op: 0x89,
                    va: *dest,
                    vb: *val,
                })
            }
            (Self::DoubleToInt { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x8a,
                va: *dest,
                vb: *val,
            }),
            (Self::DoubleToLong { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x8b,
                va: *dest,
                vb: *val,
            }),
            (Self::DoubleToFloat { dest, val }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format12X {
                    op: 0x8c,
                    va: *dest,
                    vb: *val,
                })
            }
            (Self::IntToByte { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x8d,
                va: *dest,
                vb: *val,
            }),
            (Self::IntToChar { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x8e,
                va: *dest,
                vb: *val,
            }),
            (Self::IntToShort { dest, val }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0x8f,
                va: *dest,
                vb: *val,
            }),
            (Self::AddInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x90,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::SubInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x91,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::MulInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x92,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::DivInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x93,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::RemInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x94,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AndInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x95,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::OrInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x96,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::XorInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x97,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::ShlInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x98,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::ShrInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x99,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::UshrInt { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x9a,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AddLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x9b,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::SubLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x9c,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::MulLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x9d,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::DivLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x9e,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::RemLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0x9f,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AndLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa0,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::OrLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa1,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::XorLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa2,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::ShlLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa3,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::ShrLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa4,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::UshrLong { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa5,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AddFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa6,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::SubFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa7,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::MulFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa8,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::DivFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xa9,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::RemFloat { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xaa,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AddDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xab,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::SubDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xac,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::MulDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xad,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::DivDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xae,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::RemDouble { dest, b, c }, GetRawInsParam { .. }) => Ok(InsFormat::Format23X {
                op: 0xaf,
                va: *dest,
                vb: *b,
                vc: *c,
            }),
            (Self::AddInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb0,
                va: *dest,
                vb: *b,
            }),
            (Self::SubInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb1,
                va: *dest,
                vb: *b,
            }),
            (Self::MulInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb2,
                va: *dest,
                vb: *b,
            }),
            (Self::DivInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb3,
                va: *dest,
                vb: *b,
            }),
            (Self::RemInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb4,
                va: *dest,
                vb: *b,
            }),
            (Self::AndInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb5,
                va: *dest,
                vb: *b,
            }),
            (Self::OrInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb6,
                va: *dest,
                vb: *b,
            }),
            (Self::XorInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb7,
                va: *dest,
                vb: *b,
            }),
            (Self::ShlInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb8,
                va: *dest,
                vb: *b,
            }),
            (Self::ShrInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xb9,
                va: *dest,
                vb: *b,
            }),
            (Self::UshrInt2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xba,
                va: *dest,
                vb: *b,
            }),
            (Self::AddLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xbb,
                va: *dest,
                vb: *b,
            }),
            (Self::SubLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xbc,
                va: *dest,
                vb: *b,
            }),
            (Self::MulLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xbd,
                va: *dest,
                vb: *b,
            }),
            (Self::DivLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xbe,
                va: *dest,
                vb: *b,
            }),
            (Self::RemLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xbf,
                va: *dest,
                vb: *b,
            }),
            (Self::AndLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc0,
                va: *dest,
                vb: *b,
            }),
            (Self::OrLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc1,
                va: *dest,
                vb: *b,
            }),
            (Self::XorLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc2,
                va: *dest,
                vb: *b,
            }),
            (Self::ShlLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc3,
                va: *dest,
                vb: *b,
            }),
            (Self::ShrLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc4,
                va: *dest,
                vb: *b,
            }),
            (Self::UshrLong2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc5,
                va: *dest,
                vb: *b,
            }),
            (Self::AddFloat2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc6,
                va: *dest,
                vb: *b,
            }),
            (Self::SubFloat2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc7,
                va: *dest,
                vb: *b,
            }),
            (Self::MulFloat2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc8,
                va: *dest,
                vb: *b,
            }),
            (Self::DivFloat2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xc9,
                va: *dest,
                vb: *b,
            }),
            (Self::RemFloat2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xca,
                va: *dest,
                vb: *b,
            }),
            (Self::AddDouble2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xcb,
                va: *dest,
                vb: *b,
            }),
            (Self::SubDouble2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xcc,
                va: *dest,
                vb: *b,
            }),
            (Self::MulDouble2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xcd,
                va: *dest,
                vb: *b,
            }),
            (Self::DivDouble2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xce,
                va: *dest,
                vb: *b,
            }),
            (Self::RemDouble2Addr { b, dest }, GetRawInsParam { .. }) => Ok(InsFormat::Format12X {
                op: 0xcf,
                va: *dest,
                vb: *b,
            }),
            (Self::AddIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "add-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (add-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (add-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xd8,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd0,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::RsubIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "sub-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (sub-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (sub-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xd9,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd1,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::MulIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "mul-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (mul-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (mul-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xda,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd2,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::DivIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "div-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (div-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (div-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xdb,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd3,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::RemIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "rem-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (rem-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (rem-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xdc,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd4,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::AndIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "and-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (and-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (and-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xdd,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd5,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::OrIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "or-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (or-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (or-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xde,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd6,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::XorIntLit { lit, b, dest }, GetRawInsParam { .. }) => {
                let mut reg_on_4_bit = true;
                let mut lit_on_8_bits = true;
                if dest & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if b & 0b1111_0000 != 0 {
                    reg_on_4_bit = false;
                }
                if *lit < I8_MIN_AS_I16 || *lit > I8_MAX_AS_I16 {
                    lit_on_8_bits = false;
                }
                if !reg_on_4_bit && !lit_on_8_bits {
                    // Should not happen on a sanitized op
                    panic!(
                        "xor-int/lit uses either registers indexed on 4 bits, and a literal \
                encoded on 16 bits (xor-int/lit16), or registers indexed on 8 bits and \
                a literal encoded on 8 bits (xor-int/lit8). Found reg {} and {}, and lit \
                {}",
                        dest, b, lit
                    )
                } else if lit_on_8_bits {
                    Ok(InsFormat::Format22B {
                        op: 0xdf,
                        va: *dest,
                        vb: *b,
                        c: *lit as i8,
                    })
                } else {
                    Ok(InsFormat::Format22S {
                        op: 0xd7,
                        va: *dest,
                        vb: *b,
                        c: *lit,
                    })
                }
            }
            (Self::ShlIntLit { dest, b, lit }, GetRawInsParam { .. }) => Ok(InsFormat::Format22B {
                op: 0xe0,
                va: *dest,
                vb: *b,
                c: *lit,
            }),
            (Self::ShrIntLit { dest, b, lit }, GetRawInsParam { .. }) => Ok(InsFormat::Format22B {
                op: 0xe1,
                va: *dest,
                vb: *b,
                c: *lit,
            }),
            (Self::UshrIntLit { dest, b, lit }, GetRawInsParam { .. }) => {
                Ok(InsFormat::Format22B {
                    op: 0xe2,
                    va: *dest,
                    vb: *b,
                    c: *lit,
                })
            }
        }
    }

    /// Test is a switch instruction is packed or not.
    ///
    /// Return an error if the instruction is not a `Switch`.
    pub fn is_packed(&self) -> Result<bool> {
        match self {
            Self::Switch { branches, .. } => {
                let mut last = None;
                let mut keys = branches.keys().collect::<Vec<_>>();
                keys.sort();
                for key in keys.into_iter().cloned() {
                    if let Some(last) = &last {
                        if *last != key - 1 {
                            return Ok(false);
                        }
                    }
                    last = Some(key);
                }
                Ok(true)
            }
            _ => Err(anyhow!("Instruction is not a Switch")),
        }
    }

    /// Return the size of a goto instruction from its address and an interval for
    /// the destination address in worst case senario.
    pub fn goto_size_from_branch_offset_interval(
        addr_goto: usize,
        min_addr_branch: usize,
        max_addr_branch: usize,
    ) -> Result<usize> {
        let worst_offset = if max_addr_branch as i32 - addr_goto as i32
            > addr_goto as i32 - min_addr_branch as i32
        {
            max_addr_branch as i32 - addr_goto as i32
        } else {
            min_addr_branch as i32 - addr_goto as i32
        };
        match worst_offset {
            I8_MIN_AS_I32..=I8_MAX_AS_I32 => Ok(2),
            I16_MIN_AS_I32..=I16_MAX_AS_I32 => Ok(4),
            _ => Ok(6),
        }
    }
}

#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CallSite {
    #[cfg_attr(feature = "python", pyo3(get))]
    pub method_handle: MethodHandle,
    #[cfg_attr(feature = "python", pyo3(get))]
    pub name: DexString,
    #[cfg_attr(feature = "python", pyo3(get))]
    pub type_: IdMethodType,
    #[cfg_attr(feature = "python", pyo3(get))]
    pub args: Vec<DexValue>,
}

#[cfg_attr(feature = "python", pymethods)]
impl CallSite {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(
        method_handle: MethodHandle,
        name: DexString,
        type_: IdMethodType,
        args: Vec<DexValue>,
    ) -> Self {
        let args = args.to_vec();
        Self {
            method_handle,
            name,
            type_,
            args,
        }
    }

    pub fn __str__(&self) -> String {
        let args = self
            .args
            .iter()
            .map(|arg| arg.__str__())
            .collect::<Vec<_>>()
            .join(", ");
        format!(
            "call({} {} {} ({})",
            self.method_handle.__str__(),
            self.type_.__str__(),
            self.name.__str__(),
            args
        )
    }

    pub fn __repr__(&self) -> String {
        let args = self
            .args
            .iter()
            .map(|arg| arg.__str__())
            .collect::<Vec<_>>()
            .join(", ");
        format!(
            "CallSite({}, {}, {}, [{}]",
            self.method_handle.__repr__(),
            self.type_.__repr__(),
            self.name.__repr__(),
            args
        )
    }

    /// Return all strings referenced in the call site.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.insert(self.name.clone());
        strings.extend(self.method_handle.get_all_strings());
        strings.extend(self.type_.get_all_strings());
        for arg in &self.args {
            strings.extend(arg.get_all_strings());
        }
        strings
    }

    /// Return all types referenced in the call site.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut type_ids = HashSet::new();
        type_ids.extend(self.method_handle.get_all_types());
        type_ids.extend(self.type_.get_all_types());
        for arg in &self.args {
            type_ids.extend(arg.get_all_types());
        }
        type_ids
    }

    /// Return all prototypes referenced in the call site.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut protos = HashSet::new();
        protos.extend(self.method_handle.get_all_protos());
        protos.insert(self.type_.clone());
        for arg in &self.args {
            protos.extend(arg.get_all_protos());
        }
        protos
    }

    /// Return all field ids referenced in the call site.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut fields = HashSet::new();
        fields.extend(self.method_handle.get_all_field_ids());
        for arg in &self.args {
            fields.extend(arg.get_all_field_ids());
        }
        fields
    }

    /// Return all method ids referenced in the call site.
    pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
        let mut methods = HashSet::new();
        methods.extend(self.method_handle.get_all_method_ids());
        for arg in &self.args {
            methods.extend(arg.get_all_method_ids());
        }
        methods
    }

    /// Return all method handles referenced in the call site.
    pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
        let mut handles = HashSet::new();
        handles.insert(self.method_handle.clone());
        for arg in &self.args {
            handles.extend(arg.get_all_method_handles());
        }
        handles
    }
}

impl<V: Visitor> Visitable<V> for CallSite {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        v.visit_method_handle(&self.method_handle)?;
        v.visit_string(&self.name)?;
        v.visit_method_type(&self.type_)?;
        for arg in &self.args {
            v.visit_value(arg)?;
        }
        Ok(())
    }
}

impl<V: VisitorMut> VisitableMut<V> for CallSite {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        Ok(Self {
            method_handle: v.visit_method_handle(self.method_handle)?,
            name: v.visit_string(self.name)?,
            type_: v.visit_method_type(self.type_)?,
            args: self
                .args
                .into_iter()
                .map(|arg| v.visit_value(arg))
                .collect::<Result<_>>()?,
        })
    }
}
