/* OpenCL extension specification states extensions should define the extension
   and to use it we need to enable is the following way using the pragma.
   Link to specification: https://www.khronos.org/registry/OpenCL/specs/2.2/html/OpenCL_Ext.html
*/
#ifdef cl_khr_int64_base_atomics
  #pragma OPENCL EXTENSION cl_khr_int64_base_atomics: enable
  #ifdef cl_khr_fp64
    #pragma OPENCL EXTENSION cl_khr_fp64: enable

    double numba_dppy_atomic_cmpxchg_f64_local(volatile __generic double *p, double cmp, double val) {
        union {
            ulong  u64;
            double f64;
        } cmp_union, val_union, old_union;

        cmp_union.f64 = cmp;
        val_union.f64 = val;
        old_union.u64 = atom_cmpxchg((volatile __local ulong *) p, cmp_union.u64, val_union.u64);
        return old_union.f64;
    }

    double numba_dppy_atomic_cmpxchg_f64_global(volatile __generic double *p, double cmp, double val) {
        union {
            ulong  u64;
            double f64;
        } cmp_union, val_union, old_union;

        cmp_union.f64 = cmp;
        val_union.f64 = val;
        old_union.u64 = atom_cmpxchg((volatile __global ulong *) p, cmp_union.u64, val_union.u64);
        return old_union.f64;
    }

    double numba_dppy_atomic_add_f64_local(volatile __generic double *p, double val) {
        double  found = *p;
        double  expected;
        do {
            expected = found;
            found = numba_dppy_atomic_cmpxchg_f64_local(p, expected, expected + val);
        } while (found != expected);
        return found;
    }

    double numba_dppy_atomic_add_f64_global(volatile __generic double *p, double val) {
        double  found = *p;
        double  expected;
        do {
            expected = found;
            found = numba_dppy_atomic_cmpxchg_f64_global(p, expected, expected + val);
        } while (found != expected);
        return found;
    }


    double numba_dppy_atomic_sub_f64_local(volatile __generic double *p, double val) {
        double  found = *p;
        double  expected;
        do {
            expected = found;
            found = numba_dppy_atomic_cmpxchg_f64_local(p, expected, expected - val);
        } while (found != expected);
        return found;
    }

    double numba_dppy_atomic_sub_f64_global(volatile __generic double *p, double val) {
        double  found = *p;
        double  expected;
        do {
            expected = found;
            found = numba_dppy_atomic_cmpxchg_f64_global(p, expected, expected - val);
        } while (found != expected);
        return found;
    }
  #endif
#endif

float numba_dppy_atomic_cmpxchg_f32_local(volatile __generic float *p, float cmp, float val) {
    union {
        unsigned int u32;
        float        f32;
    } cmp_union, val_union, old_union;

    cmp_union.f32 = cmp;
    val_union.f32 = val;
    old_union.u32 = atomic_cmpxchg((volatile __local unsigned int *) p, cmp_union.u32, val_union.u32);
    return old_union.f32;
}

float numba_dppy_atomic_cmpxchg_f32_global(volatile __generic float *p, float cmp, float val) {
    union {
        unsigned int u32;
        float        f32;
    } cmp_union, val_union, old_union;

    cmp_union.f32 = cmp;
    val_union.f32 = val;
    old_union.u32 = atomic_cmpxchg((volatile __global unsigned int *) p, cmp_union.u32, val_union.u32);
    return old_union.f32;
}

float numba_dppy_atomic_add_f32_local(volatile __generic float *p, float val) {
    float found = *p;
    float expected;
    do {
        expected = found;
        found = numba_dppy_atomic_cmpxchg_f32_local(p, expected, expected + val);
    } while (found != expected);
    return found;
}

float numba_dppy_atomic_add_f32_global(volatile __generic float *p, float val) {
    float found = *p;
    float expected;
    do {
        expected = found;
        found = numba_dppy_atomic_cmpxchg_f32_global(p, expected, expected + val);
    } while (found != expected);
    return found;
}

float numba_dppy_atomic_sub_f32_local(volatile __generic float *p, float val) {
    float found = *p;
    float expected;
    do {
        expected = found;
        found = numba_dppy_atomic_cmpxchg_f32_local(p, expected, expected - val);
    } while (found != expected);
    return found;
}

float numba_dppy_atomic_sub_f32_global(volatile __generic float *p, float val) {
    float found = *p;
    float expected;
    do {
        expected = found;
        found = numba_dppy_atomic_cmpxchg_f32_global(p, expected, expected - val);
    } while (found != expected);
    return found;
}
