use crate::{
    cpu::{CPU, crossbar}, instruction_set::{
        Instruction, InstructionData, InstructionStatus, InstructionType, VectorBitWith,
        helper::add_all,
    }, memory_manager::{
        MemoryStorable,
        type_traits::{FromFloat, UpcastDestTraits, UpcastSlice},
    }, tracing::TRACER, utility::{add_offset_r1, add_offset_r2, add_offset_rd}
};
use aligned_vec::{AVec, ConstAlign};
use anyhow::{Context, Result, ensure};

use paste::paste;
use std::{borrow::Cow, cell::OnceCell, collections::HashMap};
use std::{collections::HashSet, sync::LazyLock};

macro_rules! add_name {
    ($storage:ident, $id:ident) => {
        $storage.insert($id as *const () as usize, stringify!($id));
    };
}
macro_rules! add_name_simd {
    ($storage:ident, $id:ident) => {
        paste! {
        $storage.insert([<$id _impl>]::<f32,f32> as *const () as usize, concat!(stringify!($id), "::<f32, f32>"));
        $storage.insert([<$id _impl>]::<f64,f64> as *const () as usize, concat!(stringify!($id), "::<f64, f64>"));
        $storage.insert([<$id _impl>]::<f32,f64> as *const () as usize, concat!(stringify!($id), "::<f32, f64>"));
        $storage.insert([<$id _impl>]::<f64,f32> as *const () as usize, concat!(stringify!($id), "::<f64, f32>"));
        }
    };
}

static NAMES: LazyLock<HashMap<usize, &'static str>> = LazyLock::new(|| {
    let mut hash = HashMap::new();
    add_name!(hash, sldi);
    add_name!(hash, sld);
    add_name!(hash, sadd);
    add_name!(hash, ssub);
    add_name!(hash, smul);
    add_name!(hash, saddi);
    add_name!(hash, smuli);
    add_name!(hash, setbw);
    add_name_simd!(hash, mvmul);
    add_name_simd!(hash, vvadd);
    add_name_simd!(hash, vvsub);
    add_name_simd!(hash, vvmul);
    add_name_simd!(hash, vvdmul);
    add_name_simd!(hash, vvmax);
    add_name!(hash, vvsll);
    add_name!(hash, vvsra);
    add_name_simd!(hash, vavg);
    add_name_simd!(hash, vrelu);
    add_name_simd!(hash, vtanh);
    add_name_simd!(hash, vsigm);
    add_name!(hash, vmv);
    add_name!(hash, vrsu);
    add_name!(hash, vrsl);
    add_name!(hash, ld);
    add_name!(hash, st);
    add_name!(hash, lldi);
    add_name!(hash, lmv);
    add_name!(hash, send);
    add_name!(hash, recv);
    add_name!(hash, wait);
    add_name!(hash, sync);
    hash
});

pub fn functor_to_name(functor: usize) -> &'static str {
    NAMES
        .get(&functor)
        .unwrap_or_else(|| panic!("Function not found"))
}

///////////////////////////////////////////////////////////////
/////////////////Scalar/register Instructions//////////////////
///////////////////////////////////////////////////////////////
pub fn sldi(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_sldi(cores, data);
    let (core_indx, rd, imm) = data.get_core_rd_imm();
    let core = cores.core(core_indx);
    core.set_register(rd, imm);
    TRACER.lock().unwrap().post_sldi(cores, data);
    Ok(InstructionStatus::Completed)
}

pub fn sld(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_sld(cores, data);
    let (core_indx, rd, r1) = data.get_core_rd_r1();
    let offset_value = data.offset_value();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let host = cores.host();
    let num = host.load::<i32>((r1_val + offset_value), 4)?[0][0];
    let core = cores.core(core_indx);
    core.set_register(rd, num);
    TRACER.lock().unwrap().post_sld(cores, data);
    Ok(InstructionStatus::Completed)
}

pub fn sadd(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_sadd(cores, data);
    let (core_indx, rd, r1, r2) = data.get_core_rd_r1_r2();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    core.set_register(rd, r1_val + r2_val);
    Ok(InstructionStatus::Completed)
}

pub fn ssub(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_ssub(cores, data);
    let (core_indx, rd, r1, r2) = data.get_core_rd_r1_r2();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    core.set_register(rd, r1_val - r2_val);
    Ok(InstructionStatus::Completed)
}

pub fn smul(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_smul(cores, data);
    let (core_indx, rd, r1, r2) = data.get_core_rd_r1_r2();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    core.set_register(rd, r1_val * r2_val);
    Ok(InstructionStatus::Completed)
}

pub fn saddi(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_saddi(cores, data);
    let (core_indx, rd, r1, imm) = data.get_core_rd_r1_imm();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    core.set_register(rd, r1_val - imm);
    Ok(InstructionStatus::Completed)
}

pub fn smuli(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_smuli(cores, data);
    let (core_indx, rd, r1, imm) = data.get_core_rd_r1_imm();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    core.set_register(rd, r1_val * imm);
    Ok(InstructionStatus::Completed)
}

///////////////////////////////////////////////////////////////
/////////////////Matrix/vector Instructions////////////////////
///////////////////////////////////////////////////////////////
macro_rules! add_simd_to_map {
    ($storage:ident, $id:ident) => {
        paste! {
        let mut tmp   = HashMap::new();
        tmp.insert((32_usize,32_usize), ([<$id _impl>]::<f32, f32> as InstructionType));
        tmp.insert((32_usize,64_usize), ([<$id _impl>]::<f32, f64> as InstructionType));
        tmp.insert((64_usize,32_usize), ([<$id _impl>]::<f64, f32> as InstructionType));
        tmp.insert((64_usize,64_usize), ([<$id _impl>]::<f64, f64> as InstructionType));
        //TODO WTF WHY
        tmp.insert((8_usize,8_usize), ([<$id _impl>]::<f32, f32> as InstructionType));
        $storage.insert($id as *const () as usize, tmp);

        }
    };
}

static SIMD: LazyLock<HashMap<usize, HashMap<(usize, usize), InstructionType>>> =
    LazyLock::new(|| {
        let mut storage = HashMap::new();
        add_simd_to_map!(storage, vvadd);
        add_simd_to_map!(storage, vvsub);
        add_simd_to_map!(storage, vvmul);
        add_simd_to_map!(storage, vvdmul);
        add_simd_to_map!(storage, vvmax);
        add_simd_to_map!(storage, vavg);
        add_simd_to_map!(storage, vrelu);
        add_simd_to_map!(storage, vtanh);
        add_simd_to_map!(storage, vsigm);
        add_simd_to_map!(storage, mvmul);
        storage
    });

pub fn isa_simd(functor: InstructionType) -> bool {
    SIMD.contains_key(&(functor as usize))
}

pub fn dispatch_simd(
    functor: InstructionType,
    vector_bit_with: VectorBitWith,
) -> Result<InstructionType> {
    let VectorBitWith {
        vector_input_bitwith,
        vector_output_bitwith,
    } = vector_bit_with;
    let res = SIMD
        .get(&(functor as usize))
        .context("Request a non present simd")?
        .get(&(vector_input_bitwith, vector_output_bitwith))
        .with_context(|| {
            format!(
                "Function not found for the requested size input:{} output:{}",
                vector_input_bitwith, vector_output_bitwith
            )
        })?;
    Ok(*res)
}

pub fn is_setbw(functor: InstructionType) -> bool {
    functor as usize == setbw as *const () as usize
}

pub fn setbw(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, this instruction is resolved in the construction phase");
}

pub fn mvmul(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn mvm_impl_internal<F, M, T>(
    cores: &mut CPU,
    data: InstructionData,
) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T> + UpcastSlice<M>,
    [M]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    M: UpcastDestTraits<M> + MemoryStorable + FromFloat,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_mvm::<F,M,T>(cores, data);
    let (core_indx, rd, r1, mbiw, relu, group) = data.get_core_rd_r1_mbiw_immrelu_immgroup();
    let group: usize = group.try_into().context("group can not be negative")?;
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let (memory, crossbars) = core.get_memory_crossbar();
    let crossbar = crossbars.get_mut(group).unwrap();
    let crossbar_stored_bytes = crossbar.stored_bytes();
    let crossbar_byte_width = crossbar.width();
    //Fix this
    let crossbar_elem_width = crossbar_byte_width / size_of::<M>();
    ensure!(
        crossbar_byte_width & size_of::<M>() == 0,
        "M not divisor of the crosbbar size"
    );
    let crossbar_height = crossbar.height();
    let crossbar_byte_size = crossbar_byte_width * crossbar_height;

    let loads = memory
        .reserve_load(r1_val, crossbar_height * size_of::<F>())?
        .execute_load::<F>()?;
    let load = loads[0];
    let vec: Cow<[M]> = load.up();
    let matrix = crossbar.load::<M>(crossbar_byte_size)?[0];
    let mut res = Vec::with_capacity(crossbar_elem_width);
    let mut partial :AVec<M, _> = AVec::<M, ConstAlign<64>>::with_capacity(64, vec.len());
    partial.resize(vec.len(), M::from_f32(0.0));

    for x in 0..crossbar_elem_width {
        partial[0] = vec[0] * matrix[x];
        for y in 1..crossbar_height {
            partial[y] = vec[y] * matrix[y * crossbar_elem_width + x];
        }

        let mut acc = add_all(partial.as_slice());
        res.push(acc);
    }
    if relu != 0 {
        res.iter_mut().for_each(|x| {
            if *x < M::from_f32(0.0) {
                *x = M::from_f32(0.0)
            }
        });
    }
    ensure!(
        res.len() == crossbar_elem_width,
        "mvm generate a vector bigger thant it's  requested elements"
    );
    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    TRACER.lock().unwrap().post_mvm::<F,M,T>(cores, data);
    Ok(InstructionStatus::Completed)
}

pub(super) fn mvmul_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T> + UpcastSlice<f32> + UpcastSlice<f64>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
    [f32]: UpcastSlice<T>,
    [f64]: UpcastSlice<T>,
{
    let mbiw = data.mbiw();
    match mbiw {
        32 => mvm_impl_internal::<F, f32, T>(cores, data),
        64 => mvm_impl_internal::<F, f64, T>(cores, data),
        //TODO i don't know why
        8 => mvm_impl_internal::<F, f32, T>(cores, data),
        n => {
            panic!("mvm size {} not handled ", n)
        }
    }
}

pub fn vvadd(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vvadd_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_vvadd::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let r2_val = add_offset_r2(r2_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let imm_len: usize = imm_len.try_into().context("imm_len can not be negative")?;

    let loads = core
        .reserve_load(r1_val, imm_len)?
        .reserve_load(r2_val, imm_len)?
        .execute_load::<F>()?;
    let (load1, load2) = (loads[0], loads[1]);
    let res: Vec<F> = load1
        .iter()
        .zip(load2.iter())
        .map(|(&a, &b)| a + b)
        .collect();
    ensure!(
        imm_len / size_of::<F>() == res.len(),
        "vvadd generate a vector bigger thant it's  requested elements"
    );
    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    TRACER.lock().unwrap().post_vvadd::<F,T>(cores, data);
    Ok(InstructionStatus::Completed)
}

pub fn vvsub(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vvsub_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_vvsub::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let r2_val = add_offset_r2(r2_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let imm_len: usize = imm_len.try_into().context("imm_len can not be negative")?;

    let loads = core
        .reserve_load(r1_val, imm_len)?
        .reserve_load(r2_val, imm_len)?
        .execute_load::<F>()?;
    let (load1, load2) = (loads[0], loads[1]);
    let res: Vec<F> = load1
        .iter()
        .zip(load2.iter())
        .map(|(&a, &b)| a - b)
        .collect();
    ensure!(
        imm_len / size_of::<F>() == res.len(),
        "vvadd generate a vector bigger thant it's  requested elements"
    );
    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vvmul(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vvmul_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_vvmul::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let r2_val = add_offset_r2(r2_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let imm_len: usize = imm_len.try_into().context("imm_len can not be negative")?;
    let loads = core
        .reserve_load(r1_val, imm_len)?
        .reserve_load(r2_val, imm_len)?
        .execute_load::<F>()?;
    let (load1, load2) = (loads[0], loads[1]);
    let res: Vec<F> = load1
        .iter()
        .zip(load2.iter())
        .map(|(&a, &b)| a * b)
        .collect();
    ensure!(
        imm_len / size_of::<F>() == res.len(),
        "vvadd generate a vector bigger thant it's  requested elements"
    );
    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vvdmul(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vvdmul_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_vvdmul::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let r2_val = add_offset_r2(r2_val, offset_select, offset_value);
    let loads = core
        .reserve_load(r1_val, imm_len)?
        .reserve_load(r2_val, imm_len)?
        .execute_load::<F>()?;
    let (load1, load2) = (loads[0], loads[1]);
    let res: [F; 1] = [load1
        .iter()
        .zip(load2.iter())
        .map(|(&a, &b)| a * b)
        .reduce(|a, b| a + b)
        .unwrap()];

    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vvmax(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vvmax_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_vvmax::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = core.register(r2);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let r2_val = add_offset_r2(r2_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);

    let loads = core
        .reserve_load(r1_val, imm_len)?
        .reserve_load(r2_val, imm_len)?
        .execute_load::<F>()?;
    let (load1, load2) = (loads[0], loads[1]);
    let res: Vec<F> = load1
        .iter()
        .zip(load2.iter())
        .map(|(&a, &b)| if (a > b) { a } else { b })
        .collect();

    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vvsll(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!(
        "Shift left on floating point what does it means? who has generated this instruction???"
    );
}

pub fn vvsra(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!(
        "Shift right on floating point what does it means? who has generated this instruction???"
    );
}

pub fn vavg(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vavg_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable,
{
    TRACER.lock().unwrap().pre_vavg::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let r2_val = r2;
    ensure!(r2_val == 1, "Stride different than 1 not supported");
    let rd_val = core.register(rd);
    ensure!(offset_select == 1, "Offset select cannot be different from 1");
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let loads = core.reserve_load(r1_val, imm_len)?.execute_load::<F>()?;
    let load1 = loads[0];
    let len = load1.len();
    let res: [F; _] =
        [load1.iter().copied().reduce(|a: F, b: F| a + b).unwrap() / F::from_usize(len)];
    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vrelu(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vrelu_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable + From<f32>,
{
    TRACER.lock().unwrap().pre_vrelu::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let loads = core.reserve_load(r1_val, imm_len)?.execute_load::<F>()?;
    let load1 = loads[0];
    let res: Vec<F> = load1
        .iter()
        .map(|&a| if (a > 0.0.into()) { a } else { 0.0.into() })
        .collect();

    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vtanh(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vtanh_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable + From<f32>,
{
    TRACER.lock().unwrap().pre_vtanh::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);

    let loads = core.reserve_load(r1_val, imm_len)?.execute_load::<F>()?;
    let load1 = loads[0];
    let res: Vec<F> = load1.iter().map(|&a| a.tanh()).collect();

    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vsigm(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    panic!("You are calling a placeholder, the real call is the generic version");
}

pub(super) fn vsigm_impl<F, T>(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus>
where
    [F]: UpcastSlice<T>,
    T: UpcastDestTraits<T> + MemoryStorable,
    F: UpcastDestTraits<F> + MemoryStorable + From<f32>,
{
    TRACER.lock().unwrap().pre_vsigm::<F,T>(cores, data);
    let (core_indx, rd, r1, r2, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core_indx);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let loads = core.reserve_load(r1_val, imm_len)?.execute_load::<F>()?;
    let load1 = loads[0];
    let res: Vec<F> = load1.iter().map(|&a| a.sigm()).collect();
    let res_up: Cow<[T]> = res.as_slice().up();
    core.execute_store(rd_val, res_up.as_ref());
    Ok(InstructionStatus::Completed)
}

pub fn vmv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    todo!()
}

pub fn vrsu(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    todo!()
}

pub fn vrsl(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    todo!()
}

///////////////////////////////////////////////////////////////
///Communication/synchronization Instructions/////////////////
///////////////////////////////////////////////////////////////
pub fn ld(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_ld(cores, data);
    let (core, rd, r1, _, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    ensure!(core != 0, "LD cannot be used to move from host to host");
    let (host, core) = cores.host_and_cores(core);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let global_memory = host.load::<u8>(r1_val, imm_len)?;
    core.execute_store(rd_val, global_memory[0])?;
    TRACER.lock().unwrap().post_ld(cores, data);
    Ok(InstructionStatus::Completed)
}

pub fn st(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_st(cores, data);
    let (core, rd, r1, _, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    ensure!(core != 0, "ST cannot be used to move from host to host");
    let (host, core) = cores.host_and_cores(core);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let local_memory = core.load::<u8>(r1_val, imm_len)?;
    host.execute_store(rd_val, local_memory[0]);
    TRACER.lock().unwrap().post_st(cores, data);
    Ok(InstructionStatus::Completed)
}

pub fn lldi(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_lldi(cores, data);
    let (core, rd, imm) = data.get_core_rd_imm();
    let offset_value = data.offset_value();
    let imm_len = data.imm_len();
    let core = cores.core(core);
    let rd_val = core.register(rd);
    let rd_val = add_offset_rd(rd_val, 4, offset_value);
    core.memset(
        rd_val,
        imm_len,
        u8::try_from(imm).context("lldi with value bigger than u8")?,
    );
    Ok(InstructionStatus::Completed)
}

pub fn lmv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_lmv(cores, data);
    let (core, rd, r1, _, imm_len, offset_select, offset_value) =
        data.get_core_rd_r1_r2_immlen_offset();
    let core = cores.core(core);
    let r1_val = core.register(r1);
    let rd_val = core.register(rd);
    let r1_val = add_offset_r1(r1_val, offset_select, offset_value);
    let rd_val = add_offset_rd(rd_val, offset_select, offset_value);
    let local_memory = core.load::<u8>(r1_val, imm_len)?;
    let tmp = local_memory[0].to_vec();
    core.execute_store(rd_val, tmp.as_slice());
    TRACER.lock().unwrap().post_lmv(cores, data);
    Ok(InstructionStatus::Completed)
}

pub fn send(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_send(cores, data);
    Ok(InstructionStatus::Sending(data))
}

pub fn recv(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    TRACER.lock().unwrap().pre_recv(cores, data);
    Ok(InstructionStatus::Reciving(data))
}

pub fn wait(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    Ok(InstructionStatus::Waiting(data))
}

pub fn sync(cores: &mut CPU, data: InstructionData) -> Result<InstructionStatus> {
    Ok(InstructionStatus::Sync(data))
}
