#[macro_use]
mod macros;

#[cfg(feature = "simd")]
mod simd;

#[cfg(feature = "simd")]
pub use self::simd::*;

use super::{
    dispatch::Done,
    eval,
    state::{Freg32, Freg64, Inst, Ip, Ireg, Mem0Len, Mem0Ptr, Sp, VmState},
    utils::{fetch_func, get_slot_value, get_value, memory_bytes, offset_ip},
};
#[cfg(feature = "simd")]
use crate::V128;
use crate::{
    TrapCode,
    core::{CoreTable, RawRef, ReadAs, wasm},
    engine::{
        EngineFunc,
        executor::handler::{
            Control,
            dispatch::Break,
            state::DoneReason,
            utils::{
                IntoControl as _,
                call_wasm,
                call_wasm_or_host,
                exec_copy_span,
                exec_copy_span_asc,
                exec_copy_span_des,
                exec_return,
                extract_mem0,
                fetch_data,
                fetch_elem,
                fetch_global,
                fetch_memory,
                fetch_table,
                memory_slice,
                memory_slice_mut,
                resolve_data_mut,
                resolve_elem_mut,
                resolve_func,
                resolve_global,
                resolve_indirect_func,
                resolve_instance,
                resolve_memory,
                resolve_table,
                resolve_table_mut,
                return_call_host,
                return_call_wasm,
                set_global,
                update_instance,
            },
        },
        utils::unreachable_unchecked,
    },
    errors::{FuelError, MemoryError, TableError},
    func::FuncEntity,
    ir::{self, Slot, SlotSpan, index},
    store::StoreError,
};
use core::cmp;

unsafe fn decode_op<Op: ir::Decode>(ip: Ip) -> (Ip, Op) {
    let ip = match cfg!(feature = "indirect-dispatch") {
        true => unsafe { ip.skip::<ir::OpCode>() },
        false => unsafe { ip.skip::<::core::primitive::usize>() },
    };
    unsafe { ip.decode() }
}

fn identity<T>(value: T) -> T {
    value
}

execution_handler! {
    fn trap(
        _state: &mut VmState,
        ip: Ip,
        _sp: Sp,
        _mem0: Mem0Ptr,
        _mem0_len: Mem0Len,
        _instance: Inst,
        _ireg: Ireg,
        _freg32: Freg32,
        _freg64: Freg64,
    ) -> Done = {
        let (_ip, crate::ir::decode::Trap { trap_code }) = unsafe { decode_op(ip) };
        trap!(trap_code)
    }
}

execution_handler! {
    fn consume_fuel(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (next_ip, crate::ir::decode::ConsumeFuel { fuel }) = unsafe { decode_op(ip) };
        let consumption_result = state
            .store
            .inner_mut()
            .fuel_mut()
            .consume_fuel_unchecked(u64::from(fuel));
        if let Err(FuelError::OutOfFuel { required_fuel }) = consumption_result {
            out_of_fuel!(state, ip, required_fuel)
        }
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn copy_span_asc(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            ip,
            crate::ir::decode::CopySpanAsc {
                results,
                values,
                len,
            },
        ) = unsafe { decode_op(ip) };
        exec_copy_span_asc(sp, results, values, len);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn copy_span_des(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            ip,
            crate::ir::decode::CopySpanDes {
                results,
                values,
                len,
            },
        ) = unsafe { decode_op(ip) };
        exec_copy_span_des(sp, results, values, len);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn branch(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (_new_ip, crate::ir::decode::Branch { offset }) = unsafe { decode_op(ip) };
        let ip = offset_ip(ip, offset);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn global_get64(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::GlobalGet64 { result, global }) = unsafe { decode_op(ip) };
        let global = fetch_global(instance, global);
        let global = resolve_global(state.store, &global);
        let value: u64 = global.get_raw().read_as();
        set_value!(result, value, sp, ireg, freg32, freg64);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

#[cfg(feature = "simd")]
execution_handler! {
    fn global_get128(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::GlobalGet128 { result, global }) = unsafe { decode_op(ip) };
        let global = fetch_global(instance, global);
        let global = resolve_global(state.store, &global);
        let value: V128 = global.get_raw().read_as();
        set_value!(result, value, sp, ireg, freg32, freg64);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn global_set64_s(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::GlobalSet64S { global, value }) = unsafe { decode_op(ip) };
        let value: u64 = get_value(value, sp, ireg, freg32, freg64);
        set_global(global, value, state, instance);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

#[cfg(feature = "simd")]
execution_handler! {
    fn global_set128_s(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::GlobalSet128S { global, value }) = unsafe { decode_op(ip) };
        let value: V128 = get_value(value, sp, ireg, freg32, freg64);
        set_global(global, value, state, instance);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn global_set32_i(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::GlobalSet32I { global, value }) = unsafe { decode_op(ip) };
        let value: u32 = get_value(value, sp, ireg, freg32, freg64);
        set_global(global, value, state, instance);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn global_set64_i(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::GlobalSet64I { global, value }) = unsafe { decode_op(ip) };
        let value: u64 = get_value(value, sp, ireg, freg32, freg64);
        set_global(global, value, state, instance);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn call_internal(
        state: &mut VmState,
        ip: Ip,
        _sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (caller_ip, crate::ir::decode::CallInternal { params, func }) = unsafe { decode_op(ip) };
        let func = EngineFunc::from(func);
        let (callee_ip, callee_sp) = call_wasm(state, caller_ip, params, func, None)?;
        dispatch!(state, callee_ip, callee_sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn call_imported(
        state: &mut VmState,
        ip: Ip,
        _sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (caller_ip, crate::ir::decode::CallImported { params, func }) = unsafe { decode_op(ip) };
        let func = fetch_func(instance, func);
        let (ip, sp, mem0, mem0_len, instance) =
            call_wasm_or_host(state, caller_ip, func, params, mem0, mem0_len, instance)?;
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn call_indirect(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            caller_ip,
            crate::ir::decode::CallIndirect {
                params,
                index,
                func_type,
                table,
            },
        ) = unsafe { decode_op(ip) };
        let func =
            resolve_indirect_func(index, table, func_type, state, sp, instance).into_control()?;
        let (callee_ip, sp, mem0, mem0_len, instance) =
            call_wasm_or_host(state, caller_ip, func, params, mem0, mem0_len, instance)?;
        dispatch!(state, callee_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn return_call_internal(
        state: &mut VmState,
        ip: Ip,
        _sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (_, crate::ir::decode::ReturnCallInternal { params, func }) = unsafe { decode_op(ip) };
        let func = EngineFunc::from(func);
        let (callee_ip, callee_sp) = return_call_wasm(state, params, func, None)?;
        dispatch!(state, callee_ip, callee_sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn return_call_imported(
        state: &mut VmState,
        ip: Ip,
        _sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (_, crate::ir::decode::ReturnCallImported { params, func }) = unsafe { decode_op(ip) };
        let func = fetch_func(instance, func);
        let func_entity = resolve_func(state.store, &func);
        let (callee_ip, sp, new_instance) = match func_entity {
            FuncEntity::Wasm(func) => {
                let wasm_func = func.func_body();
                let callee_instance = *func.instance();
                let callee_instance = resolve_instance(state.store, &callee_instance).into();
                let (callee_ip, callee_sp) =
                    return_call_wasm(state, params, wasm_func, Some(instance))?;
                (callee_ip, callee_sp, callee_instance)
            }
            FuncEntity::Host(host_func) => {
                let host_func = *host_func;
                return_call_host(state, func, host_func, params, instance)?
            }
        };
        let (instance, mem0, mem0_len) =
            update_instance(state.store, instance, new_instance, mem0, mem0_len);
        dispatch!(state, callee_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn return_call_indirect(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            _,
            crate::ir::decode::ReturnCallIndirect {
                params,
                index,
                func_type,
                table,
            },
        ) = unsafe { decode_op(ip) };
        let func =
            resolve_indirect_func(index, table, func_type, state, sp, instance).into_control()?;
        let func_entity = resolve_func(state.store, &func);
        let (callee_ip, sp, callee_instance) = match func_entity {
            FuncEntity::Wasm(func) => {
                let wasm_func = func.func_body();
                let callee_instance = *func.instance();
                let callee_instance: Inst = resolve_instance(state.store, &callee_instance).into();
                let (callee_ip, callee_sp) =
                    return_call_wasm(state, params, wasm_func, Some(instance))?;
                (callee_ip, callee_sp, callee_instance)
            }
            FuncEntity::Host(host_func) => {
                let host_func = *host_func;
                return_call_host(state, func, host_func, params, instance)?
            }
        };
        let (instance, mem0, mem0_len) =
            update_instance(state.store, instance, callee_instance, mem0, mem0_len);
        dispatch!(state, callee_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn r#return(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        exec_return(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn return_span(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (_ip, crate::ir::decode::ReturnSpan { values }) = unsafe { decode_op(ip) };
        let dst = SlotSpan::new(Slot::from(0));
        let src = values.span();
        let len = values.len();
        exec_copy_span_asc(sp, dst, src, len);
        exec_return(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

macro_rules! handler_return {
    ( $( fn $handler:ident($op:ident) = $eval:expr );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (_ip, crate::ir::decode::$op { value }) = unsafe { decode_op(ip) };
                    let value = get_value(value, sp, ireg, freg32, freg64);
                    set_value!(Slot::from(0), $eval(value), sp, ireg, freg32, freg64);
                    exec_return(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
handler_return! {
    fn return_u64_s(ReturnU64_S) = identity::<u64>;
    fn return_u32_i(ReturnU32_I) = identity::<u32>;
    fn return_u64_i(ReturnU64_I) = identity::<u64>;
}

execution_handler! {
    fn memory_size(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::MemorySize { memory, result }) = unsafe { decode_op(ip) };
        let memory = fetch_memory(instance, memory);
        let size = resolve_memory(state.store, &memory).size();
        set_value!(result, size, sp, ireg, freg32, freg64);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn memory_grow(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::MemoryGrow {
                memory,
                result,
                delta,
            },
        ) = unsafe { decode_op(ip) };
        let delta: u64 = get_value(delta, sp, ireg, freg32, freg64);
        let memref = fetch_memory(instance, memory);
        let mut mem0 = mem0;
        let mut mem0_len = mem0_len;
        let return_value = match state.store.grow_memory(&memref, delta) {
            Ok(return_value) => {
                // The `memory.grow` operation might have invalidated the cached
                // linear memory so we need to reset it in order for the cache to
                // reload in case it is used again.
                if memory.is_default() {
                    (mem0, mem0_len) = extract_mem0(state.store, instance);
                }
                return_value
            }
            Err(StoreError::External(
                MemoryError::OutOfBoundsGrowth | MemoryError::OutOfSystemMemory,
            )) => {
                let memory_ty = resolve_memory(state.store, &memref).ty();
                match memory_ty.is_64() {
                    true => u64::MAX,
                    false => u64::from(u32::MAX),
                }
            }
            Err(StoreError::External(MemoryError::OutOfFuel { required_fuel })) => {
                out_of_fuel!(state, ip, required_fuel)
            }
            Err(StoreError::External(MemoryError::ResourceLimiterDeniedAllocation)) => {
                trap!(TrapCode::GrowthOperationLimited);
            }
            Err(StoreError::Internal(error)) => unsafe {
                unreachable_unchecked!("internal interpreter error: {error}")
            },
            Err(error) => {
                panic!("`memory.grow`: internal interpreter error: {error}")
            }
        };
        set_value!(result, return_value, sp, ireg, freg32, freg64);
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn memory_copy(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::MemoryCopy {
                dst_memory,
                src_memory,
                dst,
                src,
                len,
            },
        ) = unsafe { decode_op(ip) };
        let dst: u64 = get_value(dst, sp, ireg, freg32, freg64);
        let src: u64 = get_value(src, sp, ireg, freg32, freg64);
        let len: u64 = get_value(len, sp, ireg, freg32, freg64);
        let Ok(dst_index) = usize::try_from(dst) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let Ok(src_index) = usize::try_from(src) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let Ok(len) = usize::try_from(len) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        if dst_memory == src_memory {
            memory_copy_within(state, ip, instance, dst_memory, dst_index, src_index, len)?;
            dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
        }
        let dst_memory = fetch_memory(instance, dst_memory);
        let src_memory = fetch_memory(instance, src_memory);
        let (src_memory, dst_memory, fuel) = state
            .store
            .inner_mut()
            .resolve_memory_pair_and_fuel(&src_memory, &dst_memory);
        // These accesses just perform the bounds checks required by the Wasm spec.
        let src_bytes = memory_slice(src_memory, src_index, len).into_control()?;
        let dst_bytes = memory_slice_mut(dst_memory, dst_index, len).into_control()?;
        consume_fuel!(state, ip, fuel, |costs| costs
            .fuel_for_copying_values::<u8>(len as u64));
        dst_bytes.copy_from_slice(src_bytes);
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

fn memory_copy_within(
    state: &mut VmState<'_>,
    ip: Ip,
    instance: Inst,
    dst_memory: index::Memory,
    dst_index: usize,
    src_index: usize,
    len: usize,
) -> Control<(), Break> {
    let memory = fetch_memory(instance, dst_memory);
    let (memory, fuel) = state.store.inner_mut().resolve_memory_and_fuel_mut(&memory);
    // These accesses just perform the bounds checks required by the Wasm spec.
    memory_slice(memory, src_index, len).into_control()?;
    memory_slice(memory, dst_index, len).into_control()?;
    consume_fuel!(state, ip, fuel, |costs| costs
        .fuel_for_copying_values::<u8>(len as u64));
    memory
        .data_mut()
        .copy_within(src_index..src_index.wrapping_add(len), dst_index);
    Control::Continue(())
}

execution_handler! {
    fn memory_fill(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::MemoryFill {
                memory,
                dst,
                len,
                value,
            },
        ) = unsafe { decode_op(ip) };
        let dst: u64 = get_value(dst, sp, ireg, freg32, freg64);
        let len: u64 = get_value(len, sp, ireg, freg32, freg64);
        let value: u8 = get_value(value, sp, ireg, freg32, freg64);
        let Ok(dst) = usize::try_from(dst) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let Ok(len) = usize::try_from(len) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let memory = fetch_memory(instance, memory);
        let (memory, fuel) = state.store.inner_mut().resolve_memory_and_fuel_mut(&memory);
        let slice = memory_slice_mut(memory, dst, len).into_control()?;
        consume_fuel!(state, ip, fuel, |costs| costs
            .fuel_for_copying_values::<u8>(len as u64));
        slice.fill(value);
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn memory_init(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::MemoryInit {
                memory,
                data,
                dst,
                src,
                len,
            },
        ) = unsafe { decode_op(ip) };
        let dst: u64 = get_value(dst, sp, ireg, freg32, freg64);
        let src: u32 = get_value(src, sp, ireg, freg32, freg64);
        let len: u32 = get_value(len, sp, ireg, freg32, freg64);
        let Ok(dst_index) = usize::try_from(dst) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let Ok(src_index) = usize::try_from(src) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let Ok(len) = usize::try_from(len) else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        let (memory, data, fuel) = state
            .store
            .inner_mut()
            .resolve_memory_init_params(&fetch_memory(instance, memory), &fetch_data(instance, data));
        let memory = memory_slice_mut(memory, dst_index, len).into_control()?;
        let Some(data) = data
            .bytes()
            .get(src_index..)
            .and_then(|data| data.get(..len))
        else {
            trap!(TrapCode::MemoryOutOfBounds)
        };
        consume_fuel!(state, ip, fuel, |costs| costs
            .fuel_for_copying_values::<u8>(len as u64));
        memory.copy_from_slice(data);
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn data_drop(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::DataDrop { data }) = unsafe { decode_op(ip) };
        let data = fetch_data(instance, data);
        resolve_data_mut(state.store, &data).drop_bytes();
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn table_size(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::TableSize { table, result }) = unsafe { decode_op(ip) };
        let table = fetch_table(instance, table);
        let size = resolve_table(state.store, &table).size();
        set_value!(result, size, sp, ireg, freg32, freg64);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn table_grow(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            ip,
            crate::ir::decode::TableGrow {
                table,
                result,
                delta,
                value,
            },
        ) = unsafe { decode_op(ip) };
        let table = fetch_table(instance, table);
        let delta = get_value(delta, sp, ireg, freg32, freg64);
        let value = get_value(value, sp, ireg, freg32, freg64);
        let return_value = match state.store.grow_table(&table, delta, value) {
            Ok(return_value) => return_value,
            Err(StoreError::External(TableError::GrowOutOfBounds | TableError::OutOfSystemMemory)) => {
                let table = resolve_table(state.store, &table);
                match table.ty().is_64() {
                    true => u64::MAX,
                    false => u64::from(u32::MAX),
                }
            }
            Err(StoreError::External(TableError::OutOfFuel { required_fuel })) => {
                done!(state, DoneReason::out_of_fuel(required_fuel));
            }
            Err(StoreError::External(TableError::ResourceLimiterDeniedAllocation)) => {
                trap!(TrapCode::GrowthOperationLimited);
            }
            Err(StoreError::Internal(error)) => unsafe {
                unreachable_unchecked!("internal interpreter error: {error}")
            },
            Err(error) => {
                panic!("`table.grow`: internal interpreter error: {error}")
            }
        };
        set_value!(result, return_value, sp, ireg, freg32, freg64);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn table_copy(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::TableCopy {
                dst_table,
                src_table,
                dst,
                src,
                len,
            },
        ) = unsafe { decode_op(ip) };
        let dst: u64 = get_value(dst, sp, ireg, freg32, freg64);
        let src: u64 = get_value(src, sp, ireg, freg32, freg64);
        let len: u64 = get_value(len, sp, ireg, freg32, freg64);
        if dst_table == src_table {
            // Case: copy within the same table
            let table = fetch_table(instance, dst_table);
            let (table, fuel) = state.store.inner_mut().resolve_table_and_fuel_mut(&table);
            if let Err(error) = table.copy_within(dst, src, len, Some(fuel)) {
                let trap_code = match error {
                    TableError::CopyOutOfBounds => TrapCode::TableOutOfBounds,
                    TableError::OutOfSystemMemory => TrapCode::OutOfSystemMemory,
                    TableError::OutOfFuel { required_fuel } => {
                        out_of_fuel!(state, ip, required_fuel)
                    }
                    _ => panic!("table.copy: unexpected error: {error:?}"),
                };
                trap!(trap_code)
            }
            dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
        }
        // Case: copy between two different tables
        let dst_table = fetch_table(instance, dst_table);
        let src_table = fetch_table(instance, src_table);
        let (dst_table, src_table, fuel) = state
            .store
            .inner_mut()
            .resolve_table_pair_and_fuel(&dst_table, &src_table);
        if let Err(error) = CoreTable::copy(dst_table, dst, src_table, src, len, Some(fuel)) {
            let trap_code = match error {
                TableError::CopyOutOfBounds => TrapCode::TableOutOfBounds,
                TableError::OutOfSystemMemory => TrapCode::OutOfSystemMemory,
                TableError::OutOfFuel { required_fuel } => {
                    out_of_fuel!(state, ip, required_fuel)
                }
                _ => panic!("table.copy: unexpected error: {error:?}"),
            };
            trap!(trap_code)
        }
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn table_fill(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::TableFill {
                table,
                dst,
                len,
                value,
            },
        ) = unsafe { decode_op(ip) };
        let dst: u64 = get_value(dst, sp, ireg, freg32, freg64);
        let len: u64 = get_value(len, sp, ireg, freg32, freg64);
        let value: RawRef = get_value(value, sp, ireg, freg32, freg64);
        let table = fetch_table(instance, table);
        let (table, fuel) = state.store.inner_mut().resolve_table_and_fuel_mut(&table);
        if let Err(error) = table.fill_raw(dst, value, len, Some(fuel)) {
            let trap_code = match error {
                TableError::OutOfSystemMemory => TrapCode::OutOfSystemMemory,
                TableError::FillOutOfBounds => TrapCode::TableOutOfBounds,
                TableError::OutOfFuel { required_fuel } => {
                    out_of_fuel!(state, ip, required_fuel)
                }
                _ => panic!("table.fill: unexpected error: {error:?}"),
            };
            trap!(trap_code)
        }
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn table_init(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            next_ip,
            crate::ir::decode::TableInit {
                table,
                elem,
                dst,
                src,
                len,
            },
        ) = unsafe { decode_op(ip) };
        let dst: u64 = get_value(dst, sp, ireg, freg32, freg64);
        let src: u32 = get_value(src, sp, ireg, freg32, freg64);
        let len: u32 = get_value(len, sp, ireg, freg32, freg64);
        let table = fetch_table(instance, table);
        let elem = fetch_elem(instance, elem);
        let (table, element, fuel) = state
            .store
            .inner_mut()
            .resolve_table_init_params(&table, &elem);
        if let Err(error) = table.init(element.as_ref(), dst, src, len, Some(fuel)) {
            let trap_code = match error {
                TableError::OutOfSystemMemory => TrapCode::OutOfSystemMemory,
                TableError::InitOutOfBounds => TrapCode::TableOutOfBounds,
                TableError::OutOfFuel { required_fuel } => {
                    out_of_fuel!(state, ip, required_fuel)
                }
                _ => panic!("table.init: unexpected error: {error:?}"),
            };
            trap!(trap_code)
        }
        dispatch!(state, next_ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn elem_drop(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::ElemDrop { elem }) = unsafe { decode_op(ip) };
        let elem = fetch_elem(instance, elem);
        resolve_elem_mut(state.store, &elem).drop_items();
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

macro_rules! impl_table_get {
    ( $( fn $handler:ident($op:ident) = $ext:expr );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (ip, crate::ir::decode::$op { table, result, index }) = unsafe { decode_op(ip) };
                    let table = fetch_table(instance, table);
                    let table = resolve_table(state.store, &table);
                    let index = $ext(get_value(index, sp, ireg, freg32, freg64));
                    let value = match table.get(index) {
                        Some(value) => value.raw(),
                        None => trap!(TrapCode::TableOutOfBounds)
                    };
                    set_value!(result, value, sp, ireg, freg32, freg64);
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
impl_table_get! {
    fn table_get_ss(TableGet_Ss) = identity;
    fn table_get_si(TableGet_Si) = u64::from;
}

macro_rules! impl_table_set {
    ( $( fn $handler:ident($op:ident) = $ext:expr );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (ip, crate::ir::decode::$op { table, index, value }) = unsafe { decode_op(ip) };
                    let table = fetch_table(instance, table);
                    let table = resolve_table_mut(state.store, &table);
                    let index = $ext(get_value(index, sp, ireg, freg32, freg64));
                    let value: u32 = get_value(value, sp, ireg, freg32, freg64);
                    if let Err(TableError::SetOutOfBounds) = table.set_raw(index, RawRef::from(value)) {
                        trap!(TrapCode::TableOutOfBounds)
                    };
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
impl_table_set! {
    fn table_set_ss(TableSet_Ss) = identity;
    fn table_set_si(TableSet_Si) = identity;
    fn table_set_is(TableSet_Is) = u64::from;
    fn table_set_ii(TableSet_Ii) = u64::from;
}

execution_handler! {
    fn ref_func(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::RefFunc { func, result }) = unsafe { decode_op(ip) };
        let func = fetch_func(instance, func);
        let Some(rawref) = func.unwrap_raw(&*state.store) else {
            unsafe { unreachable_unchecked!("store mismatch with: {func:?}") }
        };
        set_value!(result, rawref, sp, ireg, freg32, freg64);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

macro_rules! impl_i64_binop128 {
    (
        $( fn $handler:ident($op:ident) = $eval:expr );* $(;)?
    ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (ip, crate::ir::decode::$op { results, lhs_lo, lhs_hi, rhs_lo, rhs_hi }) = unsafe { decode_op(ip) };
                    let lhs_lo: i64 = get_value(lhs_lo, sp, ireg, freg32, freg64);
                    let lhs_hi: i64 = get_value(lhs_hi, sp, ireg, freg32, freg64);
                    let rhs_lo: i64 = get_value(rhs_lo, sp, ireg, freg32, freg64);
                    let rhs_hi: i64 = get_value(rhs_hi, sp, ireg, freg32, freg64);
                    let results = results.to_array();
                    let (result_lo, result_hi) = $eval(lhs_lo, lhs_hi, rhs_lo, rhs_hi);
                    set_value!(results[0], result_lo, sp, ireg, freg32, freg64);
                    set_value!(results[1], result_hi, sp, ireg, freg32, freg64);
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
impl_i64_binop128! {
    fn i64_add128(I64Add128) = wasm::i64_add128;
    fn i64_sub128(I64Sub128) = wasm::i64_sub128;
}

macro_rules! impl_i64_mul_wide {
    (
        $( fn $handler:ident($op:ident) = $eval:expr );* $(;)?
    ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (ip, crate::ir::decode::$op { results, lhs, rhs }) = unsafe { decode_op(ip) };
                    let lhs: i64 = get_value(lhs, sp, ireg, freg32, freg64);
                    let rhs: i64 = get_value(rhs, sp, ireg, freg32, freg64);
                    let (result_lo, result_hi) = $eval(lhs, rhs);
                    let results = results.to_array();
                    set_value!(results[0], result_lo, sp, ireg, freg32, freg64);
                    set_value!(results[1], result_hi, sp, ireg, freg32, freg64);
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
impl_i64_mul_wide! {
    fn i64_mul_wide(I64MulWide) = wasm::i64_mul_wide_s;
    fn u64_mul_wide(U64MulWide) = wasm::i64_mul_wide_u;
}

/// Fetches the branch table index value and normalizes it to clamp between `0..len_targets`.
fn fetch_branch_table_target(sp: Sp, index: Slot, len_targets: u32) -> usize {
    let index: u32 = get_slot_value(index, sp);
    let max_index = len_targets - 1;
    cmp::min(index, max_index) as usize
}

execution_handler! {
    fn branch_table(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (ip, crate::ir::decode::BranchTable { len_targets, index }) = unsafe { decode_op(ip) };
        let chosen_target = fetch_branch_table_target(sp, index, len_targets);
        let target_offset = 4 * chosen_target;
        let ip = unsafe { ip.add(target_offset) };
        let (_, offset) = unsafe { ip.decode::<ir::BranchOffset>() };
        let ip = offset_ip(ip, offset);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

execution_handler! {
    fn branch_table_span(
        state: &mut VmState,
        ip: Ip,
        sp: Sp,
        mem0: Mem0Ptr,
        mem0_len: Mem0Len,
        instance: Inst,
        ireg: Ireg,
        freg32: Freg32,
        freg64: Freg64,
    ) -> Done = {
        let (
            ip,
            crate::ir::decode::BranchTableSpan {
                len_targets,
                index,
                values,
                len_values,
            },
        ) = unsafe { decode_op(ip) };
        let chosen_target = fetch_branch_table_target(sp, index, len_targets);
        let target_offset = 6 * chosen_target;
        let ip = unsafe { ip.add(target_offset) };
        let (_, ir::BranchTableTarget { results, offset }) =
            unsafe { ip.decode::<ir::BranchTableTarget>() };
        // TODO: maybe provide 2 `br_table_span` operation variants if needed: `br_table_span_{asc,des}`
        exec_copy_span(sp, results, values, len_values);
        let ip = offset_ip(ip, offset);
        dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
    }
}

handler_unary! {
    // copy
    fn u64_copy_ss(U64Copy_Ss) = identity::<u64>;
    fn u32_copy_si(U32Copy_Si) = identity::<u32>;
    fn u64_copy_si(U64Copy_Si) = identity::<u64>;
    // i32
    fn i32_popcnt_ss(I32Popcnt_Ss) = wasm::i32_popcnt;
    fn i32_ctz_ss(I32Ctz_Ss) = wasm::i32_ctz;
    fn i32_clz_ss(I32Clz_Ss) = wasm::i32_clz;
    fn i32_sext8_ss(I32Sext8_Ss) = wasm::i32_extend8_s;
    fn i32_sext16_ss(I32Sext16_Ss) = wasm::i32_extend16_s;
    fn i32_wrap_i64_ss(I32WrapI64_Ss) = wasm::i32_wrap_i64;
    // i64
    fn i64_popcnt_ss(I64Popcnt_Ss) = wasm::i64_popcnt;
    fn i64_ctz_ss(I64Ctz_Ss) = wasm::i64_ctz;
    fn i64_clz_ss(I64Clz_Ss) = wasm::i64_clz;
    fn i64_sext8_ss(I64Sext8_Ss) = wasm::i64_extend8_s;
    fn i64_sext16_ss(I64Sext16_Ss) = wasm::i64_extend16_s;
    fn i64_sext32_ss(I64Sext32_Ss) = wasm::i64_extend32_s;
    // f32
    fn f32_abs_ss(F32Abs_Ss) = wasm::f32_abs;
    fn f32_neg_ss(F32Neg_Ss) = wasm::f32_neg;
    fn f32_ceil_ss(F32Ceil_Ss) = wasm::f32_ceil;
    fn f32_floor_ss(F32Floor_Ss) = wasm::f32_floor;
    fn f32_trunc_ss(F32Trunc_Ss) = wasm::f32_trunc;
    fn f32_nearest_ss(F32Nearest_Ss) = wasm::f32_nearest;
    fn f32_sqrt_ss(F32Sqrt_Ss) = wasm::f32_sqrt;
    fn f32_convert_i32_ss(F32ConvertI32_Ss) = wasm::f32_convert_i32_s;
    fn f32_convert_u32_ss(F32ConvertU32_Ss) = wasm::f32_convert_i32_u;
    fn f32_convert_i64_ss(F32ConvertI64_Ss) = wasm::f32_convert_i64_s;
    fn f32_convert_u64_ss(F32ConvertU64_Ss) = wasm::f32_convert_i64_u;
    fn f32_demote_f64_ss(F32DemoteF64_Ss) = wasm::f32_demote_f64;
    // f64
    fn f64_abs_ss(F64Abs_Ss) = wasm::f64_abs;
    fn f64_neg_ss(F64Neg_Ss) = wasm::f64_neg;
    fn f64_ceil_ss(F64Ceil_Ss) = wasm::f64_ceil;
    fn f64_floor_ss(F64Floor_Ss) = wasm::f64_floor;
    fn f64_trunc_ss(F64Trunc_Ss) = wasm::f64_trunc;
    fn f64_nearest_ss(F64Nearest_Ss) = wasm::f64_nearest;
    fn f64_sqrt_ss(F64Sqrt_Ss) = wasm::f64_sqrt;
    fn f64_convert_i32_ss(F64ConvertI32_Ss) = wasm::f64_convert_i32_s;
    fn f64_convert_u32_ss(F64ConvertU32_Ss) = wasm::f64_convert_i32_u;
    fn f64_convert_i64_ss(F64ConvertI64_Ss) = wasm::f64_convert_i64_s;
    fn f64_convert_u64_ss(F64ConvertU64_Ss) = wasm::f64_convert_i64_u;
    fn f64_promote_f32_ss(F64PromoteF32_Ss) = wasm::f64_promote_f32;
    // f2i conversions
    fn i32_trunc_f32_ss(I32TruncF32_Ss) = wasm::i32_trunc_f32_s;
    fn u32_trunc_f32_ss(U32TruncF32_Ss) = wasm::i32_trunc_f32_u;
    fn i32_trunc_f64_ss(I32TruncF64_Ss) = wasm::i32_trunc_f64_s;
    fn u32_trunc_f64_ss(U32TruncF64_Ss) = wasm::i32_trunc_f64_u;
    fn i64_trunc_f32_ss(I64TruncF32_Ss) = wasm::i64_trunc_f32_s;
    fn u64_trunc_f32_ss(U64TruncF32_Ss) = wasm::i64_trunc_f32_u;
    fn i64_trunc_f64_ss(I64TruncF64_Ss) = wasm::i64_trunc_f64_s;
    fn u64_trunc_f64_ss(U64TruncF64_Ss) = wasm::i64_trunc_f64_u;
    fn i32_trunc_sat_f32_ss(I32TruncSatF32_Ss) = wasm::i32_trunc_sat_f32_s;
    fn u32_trunc_sat_f32_ss(U32TruncSatF32_Ss) = wasm::i32_trunc_sat_f32_u;
    fn i32_trunc_sat_f64_ss(I32TruncSatF64_Ss) = wasm::i32_trunc_sat_f64_s;
    fn u32_trunc_sat_f64_ss(U32TruncSatF64_Ss) = wasm::i32_trunc_sat_f64_u;
    fn i64_trunc_sat_f32_ss(I64TruncSatF32_Ss) = wasm::i64_trunc_sat_f32_s;
    fn u64_trunc_sat_f32_ss(U64TruncSatF32_Ss) = wasm::i64_trunc_sat_f32_u;
    fn i64_trunc_sat_f64_ss(I64TruncSatF64_Ss) = wasm::i64_trunc_sat_f64_s;
    fn u64_trunc_sat_f64_ss(U64TruncSatF64_Ss) = wasm::i64_trunc_sat_f64_u;
}

handler_binary! {
    // i32
    // i32: commutative
    fn i32_eq_sss(I32Eq_Sss) = wasm::i32_eq;
    fn i32_eq_ssi(I32Eq_Ssi) = wasm::i32_eq;
    fn i32_and_sss(I32And_Sss) = eval::wasmi_i32_and;
    fn i32_and_ssi(I32And_Ssi) = eval::wasmi_i32_and;
    fn i32_or_sss(I32Or_Sss) = eval::wasmi_i32_or;
    fn i32_or_ssi(I32Or_Ssi) = eval::wasmi_i32_or;
    fn i32_not_eq_sss(I32NotEq_Sss) = wasm::i32_ne;
    fn i32_not_eq_ssi(I32NotEq_Ssi) = wasm::i32_ne;
    fn i32_not_and_sss(I32NotAnd_Sss) = eval::wasmi_i32_not_and;
    fn i32_not_and_ssi(I32NotAnd_Ssi) = eval::wasmi_i32_not_and;
    fn i32_not_or_sss(I32NotOr_Sss) = eval::wasmi_i32_not_or;
    fn i32_not_or_ssi(I32NotOr_Ssi) = eval::wasmi_i32_not_or;
    fn i32_add_sss(I32Add_Sss) = wasm::i32_add;
    fn i32_add_ssi(I32Add_Ssi) = wasm::i32_add;
    fn i32_mul_sss(I32Mul_Sss) = wasm::i32_mul;
    fn i32_mul_ssi(I32Mul_Ssi) = wasm::i32_mul;
    fn i32_bitand_sss(I32BitAnd_Sss) = wasm::i32_bitand;
    fn i32_bitand_ssi(I32BitAnd_Ssi) = wasm::i32_bitand;
    fn i32_bitor_sss(I32BitOr_Sss) = wasm::i32_bitor;
    fn i32_bitor_ssi(I32BitOr_Ssi) = wasm::i32_bitor;
    fn i32_bitxor_sss(I32BitXor_Sss) = wasm::i32_bitxor;
    fn i32_bitxor_ssi(I32BitXor_Ssi) = wasm::i32_bitxor;
    // i32: non-commutative
    fn i32_sub_sss(I32Sub_Sss) = wasm::i32_sub;
    fn i32_sub_ssi(I32Sub_Ssi) = wasm::i32_sub;
    fn i32_sub_sis(I32Sub_Sis) = wasm::i32_sub;
    fn i32_div_sss(I32Div_Sss) = wasm::i32_div_s;
    fn i32_div_ssi(I32Div_Ssi) = eval::wasmi_i32_div_ssi;
    fn i32_div_sis(I32Div_Sis) = wasm::i32_div_s;
    fn u32_div_sss(U32Div_Sss) = wasm::i32_div_u;
    fn u32_div_ssi(U32Div_Ssi) = eval::wasmi_u32_div_ssi;
    fn u32_div_sis(U32Div_Sis) = wasm::i32_div_u;
    fn i32_rem_sss(I32Rem_Sss) = wasm::i32_rem_s;
    fn i32_rem_ssi(I32Rem_Ssi) = eval::wasmi_i32_rem_ssi;
    fn i32_rem_sis(I32Rem_Sis) = wasm::i32_rem_s;
    fn u32_rem_sss(U32Rem_Sss) = wasm::i32_rem_u;
    fn u32_rem_ssi(U32Rem_Ssi) = eval::wasmi_u32_rem_ssi;
    fn u32_rem_sis(U32Rem_Sis) = wasm::i32_rem_u;
    // i32: comparisons
    fn i32_le_sss(I32Le_Sss) = wasm::i32_le_s;
    fn i32_le_ssi(I32Le_Ssi) = wasm::i32_le_s;
    fn i32_le_sis(I32Le_Sis) = wasm::i32_le_s;
    fn i32_lt_sss(I32Lt_Sss) = wasm::i32_lt_s;
    fn i32_lt_ssi(I32Lt_Ssi) = wasm::i32_lt_s;
    fn i32_lt_sis(I32Lt_Sis) = wasm::i32_lt_s;
    fn u32_le_sss(U32Le_Sss) = wasm::i32_le_u;
    fn u32_le_ssi(U32Le_Ssi) = wasm::i32_le_u;
    fn u32_le_sis(U32Le_Sis) = wasm::i32_le_u;
    fn u32_lt_sss(U32Lt_Sss) = wasm::i32_lt_u;
    fn u32_lt_ssi(U32Lt_Ssi) = wasm::i32_lt_u;
    fn u32_lt_sis(U32Lt_Sis) = wasm::i32_lt_u;
    // i32: shift + rotate
    fn i32_shl_sss(I32Shl_Sss) = wasm::i32_shl;
    fn i32_shl_ssi(I32Shl_Ssi) = eval::wasmi_i32_shl_ssi;
    fn i32_shl_sis(I32Shl_Sis) = wasm::i32_shl;
    fn i32_shr_sss(I32Shr_Sss) = wasm::i32_shr_s;
    fn i32_shr_ssi(I32Shr_Ssi) = eval::wasmi_i32_shr_ssi;
    fn i32_shr_sis(I32Shr_Sis) = wasm::i32_shr_s;
    fn u32_shr_sss(U32Shr_Sss) = wasm::i32_shr_u;
    fn u32_shr_ssi(U32Shr_Ssi) = eval::wasmi_u32_shr_ssi;
    fn u32_shr_sis(U32Shr_Sis) = wasm::i32_shr_u;
    fn i32_rotl_sss(I32Rotl_Sss) = wasm::i32_rotl;
    fn i32_rotl_ssi(I32Rotl_Ssi) = eval::wasmi_i32_rotl_ssi;
    fn i32_rotl_sis(I32Rotl_Sis) = wasm::i32_rotl;
    fn i32_rotr_sss(I32Rotr_Sss) = wasm::i32_rotr;
    fn i32_rotr_ssi(I32Rotr_Ssi) = eval::wasmi_i32_rotr_ssi;
    fn i32_rotr_sis(I32Rotr_Sis) = wasm::i32_rotr;
    // i64
    // i64: commutative
    fn i64_eq_sss(I64Eq_Sss) = wasm::i64_eq;
    fn i64_eq_ssi(I64Eq_Ssi) = wasm::i64_eq;
    fn i64_and_sss(I64And_Sss) = eval::wasmi_i64_and;
    fn i64_and_ssi(I64And_Ssi) = eval::wasmi_i64_and;
    fn i64_or_sss(I64Or_Sss) = eval::wasmi_i64_or;
    fn i64_or_ssi(I64Or_Ssi) = eval::wasmi_i64_or;
    fn i64_not_and_sss(I64NotAnd_Sss) = eval::wasmi_i64_not_and;
    fn i64_not_and_ssi(I64NotAnd_Ssi) = eval::wasmi_i64_not_and;
    fn i64_not_or_sss(I64NotOr_Sss) = eval::wasmi_i64_not_or;
    fn i64_not_or_ssi(I64NotOr_Ssi) = eval::wasmi_i64_not_or;
    fn i64_not_eq_sss(I64NotEq_Sss) = wasm::i64_ne;
    fn i64_not_eq_ssi(I64NotEq_Ssi) = wasm::i64_ne;
    fn i64_add_sss(I64Add_Sss) = wasm::i64_add;
    fn i64_add_ssi(I64Add_Ssi) = wasm::i64_add;
    fn i64_mul_sss(I64Mul_Sss) = wasm::i64_mul;
    fn i64_mul_ssi(I64Mul_Ssi) = wasm::i64_mul;
    fn i64_bitand_sss(I64BitAnd_Sss) = wasm::i64_bitand;
    fn i64_bitand_ssi(I64BitAnd_Ssi) = wasm::i64_bitand;
    fn i64_bitor_sss(I64BitOr_Sss) = wasm::i64_bitor;
    fn i64_bitor_ssi(I64BitOr_Ssi) = wasm::i64_bitor;
    fn i64_bitxor_sss(I64BitXor_Sss) = wasm::i64_bitxor;
    fn i64_bitxor_ssi(I64BitXor_Ssi) = wasm::i64_bitxor;
    // i64: non-commutative
    fn i64_sub_sss(I64Sub_Sss) = wasm::i64_sub;
    fn i64_sub_ssi(I64Sub_Ssi) = wasm::i64_sub;
    fn i64_sub_sis(I64Sub_Sis) = wasm::i64_sub;
    fn i64_div_sss(I64Div_Sss) = wasm::i64_div_s;
    fn i64_div_ssi(I64Div_Ssi) = eval::wasmi_i64_div_ssi;
    fn i64_div_sis(I64Div_Sis) = wasm::i64_div_s;
    fn u64_div_sss(U64Div_Sss) = wasm::i64_div_u;
    fn u64_div_ssi(U64Div_Ssi) = eval::wasmi_u64_div_ssi;
    fn u64_div_sis(U64Div_Sis) = wasm::i64_div_u;
    fn i64_rem_sss(I64Rem_Sss) = wasm::i64_rem_s;
    fn i64_rem_ssi(I64Rem_Ssi) = eval::wasmi_i64_rem_ssi;
    fn i64_rem_sis(I64Rem_Sis) = wasm::i64_rem_s;
    fn u64_rem_sss(U64Rem_Sss) = wasm::i64_rem_u;
    fn u64_rem_ssi(U64Rem_Ssi) = eval::wasmi_u64_rem_ssi;
    fn u64_rem_sis(U64Rem_Sis) = wasm::i64_rem_u;
    // i64: comparisons
    fn i64_le_sss(I64Le_Sss) = wasm::i64_le_s;
    fn i64_le_ssi(I64Le_Ssi) = wasm::i64_le_s;
    fn i64_le_sis(I64Le_Sis) = wasm::i64_le_s;
    fn i64_lt_sss(I64Lt_Sss) = wasm::i64_lt_s;
    fn i64_lt_ssi(I64Lt_Ssi) = wasm::i64_lt_s;
    fn i64_lt_sis(I64Lt_Sis) = wasm::i64_lt_s;
    fn u64_le_sss(U64Le_Sss) = wasm::i64_le_u;
    fn u64_le_ssi(U64Le_Ssi) = wasm::i64_le_u;
    fn u64_le_sis(U64Le_Sis) = wasm::i64_le_u;
    fn u64_lt_sss(U64Lt_Sss) = wasm::i64_lt_u;
    fn u64_lt_ssi(U64Lt_Ssi) = wasm::i64_lt_u;
    fn u64_lt_sis(U64Lt_Sis) = wasm::i64_lt_u;
    // i64: shift + rotate
    fn i64_shl_sss(I64Shl_Sss) = wasm::i64_shl;
    fn i64_shl_ssi(I64Shl_Ssi) = eval::wasmi_i64_shl_ssi;
    fn i64_shl_sis(I64Shl_Sis) = wasm::i64_shl;
    fn i64_shr_sss(I64Shr_Sss) = wasm::i64_shr_s;
    fn i64_shr_ssi(I64Shr_Ssi) = eval::wasmi_i64_shr_ssi;
    fn i64_shr_sis(I64Shr_Sis) = wasm::i64_shr_s;
    fn u64_shr_sss(U64Shr_Sss) = wasm::i64_shr_u;
    fn u64_shr_ssi(U64Shr_Ssi) = eval::wasmi_u64_shr_ssi;
    fn u64_shr_sis(U64Shr_Sis) = wasm::i64_shr_u;
    fn i64_rotl_sss(I64Rotl_Sss) = wasm::i64_rotl;
    fn i64_rotl_ssi(I64Rotl_Ssi) = eval::wasmi_i64_rotl_ssi;
    fn i64_rotl_sis(I64Rotl_Sis) = wasm::i64_rotl;
    fn i64_rotr_sss(I64Rotr_Sss) = wasm::i64_rotr;
    fn i64_rotr_ssi(I64Rotr_Ssi) = eval::wasmi_i64_rotr_ssi;
    fn i64_rotr_sis(I64Rotr_Sis) = wasm::i64_rotr;
    // f32
    // f32: binary
    fn f32_add_sss(F32Add_Sss) = wasm::f32_add;
    fn f32_add_ssi(F32Add_Ssi) = wasm::f32_add;
    fn f32_add_sis(F32Add_Sis) = wasm::f32_add;
    fn f32_sub_sss(F32Sub_Sss) = wasm::f32_sub;
    fn f32_sub_ssi(F32Sub_Ssi) = wasm::f32_sub;
    fn f32_sub_sis(F32Sub_Sis) = wasm::f32_sub;
    fn f32_mul_sss(F32Mul_Sss) = wasm::f32_mul;
    fn f32_mul_ssi(F32Mul_Ssi) = wasm::f32_mul;
    fn f32_mul_sis(F32Mul_Sis) = wasm::f32_mul;
    fn f32_div_sss(F32Div_Sss) = wasm::f32_div;
    fn f32_div_ssi(F32Div_Ssi) = wasm::f32_div;
    fn f32_div_sis(F32Div_Sis) = wasm::f32_div;
    fn f32_min_sss(F32Min_Sss) = wasm::f32_min;
    fn f32_min_ssi(F32Min_Ssi) = wasm::f32_min;
    fn f32_min_sis(F32Min_Sis) = wasm::f32_min;
    fn f32_max_sss(F32Max_Sss) = wasm::f32_max;
    fn f32_max_ssi(F32Max_Ssi) = wasm::f32_max;
    fn f32_max_sis(F32Max_Sis) = wasm::f32_max;
    fn f32_copysign_sss(F32Copysign_Sss) = wasm::f32_copysign;
    fn f32_copysign_ssi(F32Copysign_Ssi) = eval::wasmi_f32_copysign_ssi;
    fn f32_copysign_sis(F32Copysign_Sis) = wasm::f32_copysign;
    // f32: comparisons
    fn f32_eq_sss(F32Eq_Sss) = wasm::f32_eq;
    fn f32_eq_ssi(F32Eq_Ssi) = wasm::f32_eq;
    fn f32_lt_sss(F32Lt_Sss) = wasm::f32_lt;
    fn f32_lt_ssi(F32Lt_Ssi) = wasm::f32_lt;
    fn f32_lt_sis(F32Lt_Sis) = wasm::f32_lt;
    fn f32_le_sss(F32Le_Sss) = wasm::f32_le;
    fn f32_le_ssi(F32Le_Ssi) = wasm::f32_le;
    fn f32_le_sis(F32Le_Sis) = wasm::f32_le;
    fn f32_not_eq_sss(F32NotEq_Sss) = wasm::f32_ne;
    fn f32_not_eq_ssi(F32NotEq_Ssi) = wasm::f32_ne;
    fn f32_not_lt_sss(F32NotLt_Sss) = eval::wasmi_f32_not_lt;
    fn f32_not_lt_ssi(F32NotLt_Ssi) = eval::wasmi_f32_not_lt;
    fn f32_not_lt_sis(F32NotLt_Sis) = eval::wasmi_f32_not_lt;
    fn f32_not_le_sss(F32NotLe_Sss) = eval::wasmi_f32_not_le;
    fn f32_not_le_ssi(F32NotLe_Ssi) = eval::wasmi_f32_not_le;
    fn f32_not_le_sis(F32NotLe_Sis) = eval::wasmi_f32_not_le;
    // f64
    // f64: binary
    fn f64_add_sss(F64Add_Sss) = wasm::f64_add;
    fn f64_add_ssi(F64Add_Ssi) = wasm::f64_add;
    fn f64_add_sis(F64Add_Sis) = wasm::f64_add;
    fn f64_sub_sss(F64Sub_Sss) = wasm::f64_sub;
    fn f64_sub_ssi(F64Sub_Ssi) = wasm::f64_sub;
    fn f64_sub_sis(F64Sub_Sis) = wasm::f64_sub;
    fn f64_mul_sss(F64Mul_Sss) = wasm::f64_mul;
    fn f64_mul_ssi(F64Mul_Ssi) = wasm::f64_mul;
    fn f64_mul_sis(F64Mul_Sis) = wasm::f64_mul;
    fn f64_div_sss(F64Div_Sss) = wasm::f64_div;
    fn f64_div_ssi(F64Div_Ssi) = wasm::f64_div;
    fn f64_div_sis(F64Div_Sis) = wasm::f64_div;
    fn f64_min_sss(F64Min_Sss) = wasm::f64_min;
    fn f64_min_ssi(F64Min_Ssi) = wasm::f64_min;
    fn f64_min_sis(F64Min_Sis) = wasm::f64_min;
    fn f64_max_sss(F64Max_Sss) = wasm::f64_max;
    fn f64_max_ssi(F64Max_Ssi) = wasm::f64_max;
    fn f64_max_sis(F64Max_Sis) = wasm::f64_max;
    fn f64_copysign_sss(F64Copysign_Sss) = wasm::f64_copysign;
    fn f64_copysign_ssi(F64Copysign_Ssi) = eval::wasmi_f64_copysign_ssi;
    fn f64_copysign_sis(F64Copysign_Sis) = wasm::f64_copysign;
    // f64: comparisons
    fn f64_eq_sss(F64Eq_Sss) = wasm::f64_eq;
    fn f64_eq_ssi(F64Eq_Ssi) = wasm::f64_eq;
    fn f64_lt_sss(F64Lt_Sss) = wasm::f64_lt;
    fn f64_lt_ssi(F64Lt_Ssi) = wasm::f64_lt;
    fn f64_lt_sis(F64Lt_Sis) = wasm::f64_lt;
    fn f64_le_sss(F64Le_Sss) = wasm::f64_le;
    fn f64_le_ssi(F64Le_Ssi) = wasm::f64_le;
    fn f64_le_sis(F64Le_Sis) = wasm::f64_le;
    fn f64_not_eq_sss(F64NotEq_Sss) = wasm::f64_ne;
    fn f64_not_eq_ssi(F64NotEq_Ssi) = wasm::f64_ne;
    fn f64_not_lt_sss(F64NotLt_Sss) = eval::wasmi_f64_not_lt;
    fn f64_not_lt_ssi(F64NotLt_Ssi) = eval::wasmi_f64_not_lt;
    fn f64_not_lt_sis(F64NotLt_Sis) = eval::wasmi_f64_not_lt;
    fn f64_not_le_sss(F64NotLe_Sss) = eval::wasmi_f64_not_le;
    fn f64_not_le_ssi(F64NotLe_Ssi) = eval::wasmi_f64_not_le;
    fn f64_not_le_sis(F64NotLe_Sis) = eval::wasmi_f64_not_le;
}

macro_rules! handler_cmp_branch {
    ( $( fn $handler:ident($decode:ident) = $eval:expr );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (next_ip, $crate::ir::decode::$decode { offset, lhs, rhs }) = unsafe { decode_op(ip) };
                    let lhs = get_value(lhs, sp, ireg, freg32, freg64);
                    let rhs = get_value(rhs, sp, ireg, freg32, freg64);
                    let ip = match $eval(lhs, rhs) {
                        true => offset_ip(ip, offset),
                        false => next_ip,
                    };
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
handler_cmp_branch! {
    // i32
    fn branch_i32_eq_ss(BranchI32Eq_Ss) = wasm::i32_eq;
    fn branch_i32_eq_si(BranchI32Eq_Si) = wasm::i32_eq;
    fn branch_i32_and_ss(BranchI32And_Ss) = eval::wasmi_i32_and;
    fn branch_i32_and_si(BranchI32And_Si) = eval::wasmi_i32_and;
    fn branch_i32_or_ss(BranchI32Or_Ss) = eval::wasmi_i32_or;
    fn branch_i32_or_si(BranchI32Or_Si) = eval::wasmi_i32_or;
    fn branch_i32_not_eq_ss(BranchI32NotEq_Ss) = wasm::i32_ne;
    fn branch_i32_not_eq_si(BranchI32NotEq_Si) = wasm::i32_ne;
    fn branch_i32_not_and_ss(BranchI32NotAnd_Ss) = eval::wasmi_i32_not_and;
    fn branch_i32_not_and_si(BranchI32NotAnd_Si) = eval::wasmi_i32_not_and;
    fn branch_i32_not_or_ss(BranchI32NotOr_Ss) = eval::wasmi_i32_not_or;
    fn branch_i32_not_or_si(BranchI32NotOr_Si) = eval::wasmi_i32_not_or;
    fn branch_i32_le_ss(BranchI32Le_Ss) = wasm::i32_le_s;
    fn branch_i32_le_si(BranchI32Le_Si) = wasm::i32_le_s;
    fn branch_i32_le_is(BranchI32Le_Is) = wasm::i32_le_s;
    fn branch_i32_lt_ss(BranchI32Lt_Ss) = wasm::i32_lt_s;
    fn branch_i32_lt_si(BranchI32Lt_Si) = wasm::i32_lt_s;
    fn branch_i32_lt_is(BranchI32Lt_Is) = wasm::i32_lt_s;
    fn branch_u32_le_ss(BranchU32Le_Ss) = wasm::i32_le_u;
    fn branch_u32_le_si(BranchU32Le_Si) = wasm::i32_le_u;
    fn branch_u32_le_is(BranchU32Le_Is) = wasm::i32_le_u;
    fn branch_u32_lt_ss(BranchU32Lt_Ss) = wasm::i32_lt_u;
    fn branch_u32_lt_si(BranchU32Lt_Si) = wasm::i32_lt_u;
    fn branch_u32_lt_is(BranchU32Lt_Is) = wasm::i32_lt_u;
    // i64
    fn branch_i64_eq_ss(BranchI64Eq_Ss) = wasm::i64_eq;
    fn branch_i64_eq_si(BranchI64Eq_Si) = wasm::i64_eq;
    fn branch_i64_and_ss(BranchI64And_Ss) = eval::wasmi_i64_and;
    fn branch_i64_and_si(BranchI64And_Si) = eval::wasmi_i64_and;
    fn branch_i64_or_ss(BranchI64Or_Ss) = eval::wasmi_i64_or;
    fn branch_i64_or_si(BranchI64Or_Si) = eval::wasmi_i64_or;
    fn branch_i64_not_eq_ss(BranchI64NotEq_Ss) = wasm::i64_ne;
    fn branch_i64_not_eq_si(BranchI64NotEq_Si) = wasm::i64_ne;
    fn branch_i64_not_and_ss(BranchI64NotAnd_Ss) = eval::wasmi_i64_not_and;
    fn branch_i64_not_and_si(BranchI64NotAnd_Si) = eval::wasmi_i64_not_and;
    fn branch_i64_not_or_ss(BranchI64NotOr_Ss) = eval::wasmi_i64_not_or;
    fn branch_i64_not_or_si(BranchI64NotOr_Si) = eval::wasmi_i64_not_or;
    fn branch_i64_le_ss(BranchI64Le_Ss) = wasm::i64_le_s;
    fn branch_i64_le_si(BranchI64Le_Si) = wasm::i64_le_s;
    fn branch_i64_le_is(BranchI64Le_Is) = wasm::i64_le_s;
    fn branch_i64_lt_ss(BranchI64Lt_Ss) = wasm::i64_lt_s;
    fn branch_i64_lt_si(BranchI64Lt_Si) = wasm::i64_lt_s;
    fn branch_i64_lt_is(BranchI64Lt_Is) = wasm::i64_lt_s;
    fn branch_u64_le_ss(BranchU64Le_Ss) = wasm::i64_le_u;
    fn branch_u64_le_si(BranchU64Le_Si) = wasm::i64_le_u;
    fn branch_u64_le_is(BranchU64Le_Is) = wasm::i64_le_u;
    fn branch_u64_lt_ss(BranchU64Lt_Ss) = wasm::i64_lt_u;
    fn branch_u64_lt_si(BranchU64Lt_Si) = wasm::i64_lt_u;
    fn branch_u64_lt_is(BranchU64Lt_Is) = wasm::i64_lt_u;
    // f32
    fn branch_f32_eq_ss(BranchF32Eq_Ss) = wasm::f32_eq;
    fn branch_f32_eq_si(BranchF32Eq_Si) = wasm::f32_eq;
    fn branch_f32_le_ss(BranchF32Le_Ss) = wasm::f32_le;
    fn branch_f32_le_si(BranchF32Le_Si) = wasm::f32_le;
    fn branch_f32_le_is(BranchF32Le_Is) = wasm::f32_le;
    fn branch_f32_lt_ss(BranchF32Lt_Ss) = wasm::f32_lt;
    fn branch_f32_lt_si(BranchF32Lt_Si) = wasm::f32_lt;
    fn branch_f32_lt_is(BranchF32Lt_Is) = wasm::f32_lt;
    fn branch_f32_not_eq_ss(BranchF32NotEq_Ss) = wasm::f32_ne;
    fn branch_f32_not_eq_si(BranchF32NotEq_Si) = wasm::f32_ne;
    fn branch_f32_not_le_ss(BranchF32NotLe_Ss) = eval::wasmi_f32_not_le;
    fn branch_f32_not_le_si(BranchF32NotLe_Si) = eval::wasmi_f32_not_le;
    fn branch_f32_not_le_is(BranchF32NotLe_Is) = eval::wasmi_f32_not_le;
    fn branch_f32_not_lt_ss(BranchF32NotLt_Ss) = eval::wasmi_f32_not_lt;
    fn branch_f32_not_lt_si(BranchF32NotLt_Si) = eval::wasmi_f32_not_lt;
    fn branch_f32_not_lt_is(BranchF32NotLt_Is) = eval::wasmi_f32_not_lt;
    // f64
    fn branch_f64_eq_ss(BranchF64Eq_Ss) = wasm::f64_eq;
    fn branch_f64_eq_si(BranchF64Eq_Si) = wasm::f64_eq;
    fn branch_f64_le_ss(BranchF64Le_Ss) = wasm::f64_le;
    fn branch_f64_le_si(BranchF64Le_Si) = wasm::f64_le;
    fn branch_f64_le_is(BranchF64Le_Is) = wasm::f64_le;
    fn branch_f64_lt_ss(BranchF64Lt_Ss) = wasm::f64_lt;
    fn branch_f64_lt_si(BranchF64Lt_Si) = wasm::f64_lt;
    fn branch_f64_lt_is(BranchF64Lt_Is) = wasm::f64_lt;
    fn branch_f64_not_eq_ss(BranchF64NotEq_Ss) = wasm::f64_ne;
    fn branch_f64_not_eq_si(BranchF64NotEq_Si) = wasm::f64_ne;
    fn branch_f64_not_le_ss(BranchF64NotLe_Ss) = eval::wasmi_f64_not_le;
    fn branch_f64_not_le_si(BranchF64NotLe_Si) = eval::wasmi_f64_not_le;
    fn branch_f64_not_le_is(BranchF64NotLe_Is) = eval::wasmi_f64_not_le;
    fn branch_f64_not_lt_ss(BranchF64NotLt_Ss) = eval::wasmi_f64_not_lt;
    fn branch_f64_not_lt_si(BranchF64NotLt_Si) = eval::wasmi_f64_not_lt;
    fn branch_f64_not_lt_is(BranchF64NotLt_Is) = eval::wasmi_f64_not_lt;
}

macro_rules! handler_select {
    ( $( fn $handler:ident($decode:ident) = $width:ty );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (
                        ip,
                        $crate::ir::decode::$decode {
                            result,
                            condition,
                            true_val,
                            false_val,
                        },
                    ) = unsafe { decode_op(ip) };
                    let condition: i32 = get_value(condition, sp, ireg, freg32, freg64);
                    let true_val: $width = get_value(true_val, sp, ireg, freg32, freg64);
                    let false_val: $width = get_value(false_val, sp, ireg, freg32, freg64);
                    let selected = match condition {
                        0 => get_value(false_val, sp, ireg, freg32, freg64),
                        _ => get_value(true_val, sp, ireg, freg32, freg64),
                    };
                    set_value!(result, selected, sp, ireg, freg32, freg64);
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
handler_select! {
    fn u64_select_ssss(U64Select_Ssss) = u64;
    fn u32_select_sssi(U32Select_Sssi) = u32;
    fn u32_select_ssis(U32Select_Ssis) = u32;
    fn u32_select_ssii(U32Select_Ssii) = u32;
    fn u64_select_sssi(U64Select_Sssi) = u64;
    fn u64_select_ssis(U64Select_Ssis) = u64;
    fn u64_select_ssii(U64Select_Ssii) = u64;
}

handler_load_ss! {
    fn u32_load_ss(U32Load_Ss) = wasm::load32;
    fn u64_load_ss(U64Load_Ss) = wasm::load64;
    fn i32_load_extend8_ss(I32LoadExtend8_Ss) = wasm::i32_load8_s;
    fn u32_load_extend8_ss(U32LoadExtend8_Ss) = wasm::i32_load8_u;
    fn i32_load_extend16_ss(I32LoadExtend16_Ss) = wasm::i32_load16_s;
    fn u32_load_extend16_ss(U32LoadExtend16_Ss) = wasm::i32_load16_u;
    fn i64_load_extend8_ss(I64LoadExtend8_Ss) = wasm::i64_load8_s;
    fn u64_load_extend8_ss(U64LoadExtend8_Ss) = wasm::i64_load8_u;
    fn i64_load_extend16_ss(I64LoadExtend16_Ss) = wasm::i64_load16_s;
    fn u64_load_extend16_ss(U64LoadExtend16_Ss) = wasm::i64_load16_u;
    fn i64_load_extend32_ss(I64LoadExtend32_Ss) = wasm::i64_load32_s;
    fn u64_load_extend32_ss(U64LoadExtend32_Ss) = wasm::i64_load32_u;
}

macro_rules! handler_load_si {
    ( $( fn $handler:ident($decode:ident) = $load:expr );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (
                        ip,
                        crate::ir::decode::$decode {
                            result,
                            address,
                            memory,
                        },
                    ) = unsafe { decode_op(ip) };
                    let address = get_value(address, sp, ireg, freg32, freg64);
                    let mem_bytes = memory_bytes(memory, mem0, mem0_len, instance, state);
                    let loaded = $load(mem_bytes, usize::from(address)).into_control()?;
                    set_value!(result, loaded, sp, ireg, freg32, freg64);
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
handler_load_si! {
    fn u32_load_si(U32Load_Si) = wasm::load32_at;
    fn u64_load_si(U64Load_Si) = wasm::load64_at;
    fn i32_load_extend8_si(I32LoadExtend8_Si) = wasm::i32_load8_s_at;
    fn u32_load_extend8_si(U32LoadExtend8_Si) = wasm::i32_load8_u_at;
    fn i32_load_extend16_si(I32LoadExtend16_Si) = wasm::i32_load16_s_at;
    fn u32_load_extend16_si(U32LoadExtend16_Si) = wasm::i32_load16_u_at;
    fn i64_load_extend8_si(I64LoadExtend8_Si) = wasm::i64_load8_s_at;
    fn u64_load_extend8_si(U64LoadExtend8_Si) = wasm::i64_load8_u_at;
    fn i64_load_extend16_si(I64LoadExtend16_Si) = wasm::i64_load16_s_at;
    fn u64_load_extend16_si(U64LoadExtend16_Si) = wasm::i64_load16_u_at;
    fn i64_load_extend32_si(I64LoadExtend32_Si) = wasm::i64_load32_s_at;
    fn u64_load_extend32_si(U64LoadExtend32_Si) = wasm::i64_load32_u_at;
}

handler_load_mem0_offset16_ss! {
    fn u32_load_mem0_offset16_ss(U32LoadMem0Offset16_Ss) = wasm::load32;
    fn u64_load_mem0_offset16_ss(U64LoadMem0Offset16_Ss) = wasm::load64;
    fn i32_load_extend8_mem0_offset16_ss(I32LoadExtend8Mem0Offset16_Ss) = wasm::i32_load8_s;
    fn u32_load_extend8_mem0_offset16_ss(U32LoadExtend8Mem0Offset16_Ss) = wasm::i32_load8_u;
    fn i32_load_extend16_mem0_offset16_ss(I32LoadExtend16Mem0Offset16_Ss) = wasm::i32_load16_s;
    fn u32_load_extend16_mem0_offset16_ss(U32LoadExtend16Mem0Offset16_Ss) = wasm::i32_load16_u;
    fn i64_load_extend8_mem0_offset16_ss(I64LoadExtend8Mem0Offset16_Ss) = wasm::i64_load8_s;
    fn u64_load_extend8_mem0_offset16_ss(U64LoadExtend8Mem0Offset16_Ss) = wasm::i64_load8_u;
    fn i64_load_extend16_mem0_offset16_ss(I64LoadExtend16Mem0Offset16_Ss) = wasm::i64_load16_s;
    fn u64_load_extend16_mem0_offset16_ss(U64LoadExtend16Mem0Offset16_Ss) = wasm::i64_load16_u;
    fn i64_load_extend32_mem0_offset16_ss(I64LoadExtend32Mem0Offset16_Ss) = wasm::i64_load32_s;
    fn u64_load_extend32_mem0_offset16_ss(U64LoadExtend32Mem0Offset16_Ss) = wasm::i64_load32_u;
}

handler_store_sx! {
    fn u32_store_ss(U32Store_Ss, u32) = wasm::store32;
    fn u32_store_si(U32Store_Si, u32) = wasm::store32;
    fn u64_store_ss(U64Store_Ss, u64) = wasm::store64;
    fn u64_store_si(U64Store_Si, u64) = wasm::store64;
    fn i32_store_wrap8_ss(I32StoreWrap8_Ss, i8) = wasm::i32_store8;
    fn i32_store_wrap8_si(I32StoreWrap8_Si, i8) = wasm::i32_store8;
    fn i32_store_wrap16_ss(I32StoreWrap16_Ss, i16) = wasm::i32_store16;
    fn i32_store_wrap16_si(I32StoreWrap16_Si, i16) = wasm::i32_store16;
    fn i64_store_wrap8_ss(I64StoreWrap8_Ss, i8) = wasm::i64_store8;
    fn i64_store_wrap8_si(I64StoreWrap8_Si, i8) = wasm::i64_store8;
    fn i64_store_wrap16_ss(I64StoreWrap16_Ss, i16) = wasm::i64_store16;
    fn i64_store_wrap16_si(I64StoreWrap16_Si, i16) = wasm::i64_store16;
    fn i64_store_wrap32_ss(I64StoreWrap32_Ss, i32) = wasm::i64_store32;
    fn i64_store_wrap32_si(I64StoreWrap32_Si, i32) = wasm::i64_store32;
}

macro_rules! handler_store_ix {
    ( $( fn $handler:ident($decode:ident, $hint:ty) = $store:expr );* $(;)? ) => {
        $(
            execution_handler! {
                fn $handler(
                    state: &mut VmState,
                    ip: Ip,
                    sp: Sp,
                    mem0: Mem0Ptr,
                    mem0_len: Mem0Len,
                    instance: Inst,
                    ireg: Ireg,
                    freg32: Freg32,
                    freg64: Freg64,
                ) -> Done = {
                    let (
                        ip,
                        crate::ir::decode::$decode {
                            address,
                            value,
                            memory,
                        },
                    ) = unsafe { decode_op(ip) };
                    let address = get_value(address, sp, ireg, freg32, freg64);
                    let value: $hint = get_value(value, sp, ireg, freg32, freg64);
                    let mem_bytes = memory_bytes(memory, mem0, mem0_len, instance, state);
                    $store(mem_bytes, usize::from(address), value.into()).into_control()?;
                    dispatch!(state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64)
                }
            }
        )*
    };
}
handler_store_ix! {
    fn u32_store_is(U32Store_Is, u32) = wasm::store32_at;
    fn u32_store_ii(U32Store_Ii, u32) = wasm::store32_at;
    fn u64_store_is(U64Store_Is, u64) = wasm::store64_at;
    fn u64_store_ii(U64Store_Ii, u64) = wasm::store64_at;
    fn i32_store_wrap8_is(I32StoreWrap8_Is, i8) = wasm::i32_store8_at;
    fn i32_store_wrap8_ii(I32StoreWrap8_Ii, i8) = wasm::i32_store8_at;
    fn i32_store_wrap16_is(I32StoreWrap16_Is, i16) = wasm::i32_store16_at;
    fn i32_store_wrap16_ii(I32StoreWrap16_Ii, i16) = wasm::i32_store16_at;
    fn i64_store_wrap8_is(I64StoreWrap8_Is, i8) = wasm::i64_store8_at;
    fn i64_store_wrap8_ii(I64StoreWrap8_Ii, i8) = wasm::i64_store8_at;
    fn i64_store_wrap16_is(I64StoreWrap16_Is, i16) = wasm::i64_store16_at;
    fn i64_store_wrap16_ii(I64StoreWrap16_Ii, i16) = wasm::i64_store16_at;
    fn i64_store_wrap32_is(I64StoreWrap32_Is, i32) = wasm::i64_store32_at;
    fn i64_store_wrap32_ii(I64StoreWrap32_Ii, i32) = wasm::i64_store32_at;
}

handler_store_mem0_offset16_sx! {
    fn u32_store_mem0_offset16_ss(U32StoreMem0Offset16_Ss, u32) = wasm::store32;
    fn u32_store_mem0_offset16_si(U32StoreMem0Offset16_Si, u32) = wasm::store32;
    fn u64_store_mem0_offset16_ss(U64StoreMem0Offset16_Ss, u64) = wasm::store64;
    fn u64_store_mem0_offset16_si(U64StoreMem0Offset16_Si, u64) = wasm::store64;
    fn i32_store_wrap8_mem0_offset16_ss(I32StoreWrap8Mem0Offset16_Ss, i8) = wasm::i32_store8;
    fn i32_store_wrap8_mem0_offset16_si(I32StoreWrap8Mem0Offset16_Si, i8) = wasm::i32_store8;
    fn i32_store_wrap16_mem0_offset16_ss(I32StoreWrap16Mem0Offset16_Ss, i16) = wasm::i32_store16;
    fn i32_store_wrap16_mem0_offset16_si(I32StoreWrap16Mem0Offset16_Si, i16) = wasm::i32_store16;
    fn i64_store_wrap8_mem0_offset16_ss(I64StoreWrap8Mem0Offset16_Ss, i8) = wasm::i64_store8;
    fn i64_store_wrap8_mem0_offset16_si(I64StoreWrap8Mem0Offset16_Si, i8) = wasm::i64_store8;
    fn i64_store_wrap16_mem0_offset16_ss(I64StoreWrap16Mem0Offset16_Ss, i16) = wasm::i64_store16;
    fn i64_store_wrap16_mem0_offset16_si(I64StoreWrap16Mem0Offset16_Si, i16) = wasm::i64_store16;
    fn i64_store_wrap32_mem0_offset16_ss(I64StoreWrap32Mem0Offset16_Ss, i32) = wasm::i64_store32;
    fn i64_store_wrap32_mem0_offset16_si(I64StoreWrap32Mem0Offset16_Si, i32) = wasm::i64_store32;
}

Graph