diff options
| author | Matti Picus <matti.picus@gmail.com> | 2023-01-29 08:50:08 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-01-29 08:50:08 +0200 |
| commit | 5749d39a52048761555e384cd5c3575ebe8988b7 (patch) | |
| tree | b6bc8c4e3c55343550b3301b905561449ad754a5 /numpy/core/src | |
| parent | 6615d6bc26c30bf618538230ded86c09381a3e28 (diff) | |
| parent | 37b8a536ac6644d4fb71c03cede62896c6de13e9 (diff) | |
| download | numpy-5749d39a52048761555e384cd5c3575ebe8988b7.tar.gz | |
Merge pull request #22165 from Developer-Ecosystem-Engineering/simd_isnan_isinf_isfinite_signbit
ENH: Implement SIMD versions of isnan,isinf, isfinite and signbit
Diffstat (limited to 'numpy/core/src')
| -rw-r--r-- | numpy/core/src/umath/loops.c.src | 24 | ||||
| -rw-r--r-- | numpy/core/src/umath/loops.h.src | 23 | ||||
| -rw-r--r-- | numpy/core/src/umath/loops_unary_fp_le.dispatch.c.src | 565 | ||||
| -rw-r--r-- | numpy/core/src/umath/simd.inc.src | 284 |
4 files changed, 592 insertions, 304 deletions
diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 7b070a084..2b70a4b9a 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1306,6 +1306,8 @@ TIMEDELTA_mm_qm_divmod(char **args, npy_intp const *dimensions, npy_intp const * * #TYPE = FLOAT, DOUBLE, LONGDOUBLE# * #c = f, , l# * #C = F, , L# + * #fd = 1, 1, 0# + * #VCHK = 1, 1, 0# */ /**begin repeat1 * #kind = logical_and, logical_or# @@ -1342,32 +1344,22 @@ NPY_NO_EXPORT void } } +#if !@fd@ /**begin repeat1 * #kind = isnan, isinf, isfinite, signbit# * #func = npy_isnan, npy_isinf, npy_isfinite, npy_signbit# **/ - -/**begin repeat2 - * #ISA = , _avx512_skx# - * #isa = simd, avx512_skx# - * #CHK = 1, defined(HAVE_ATTRIBUTE_TARGET_AVX512_SKX)# - **/ - -#if @CHK@ NPY_NO_EXPORT void -@TYPE@_@kind@@ISA@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) +@TYPE@_@kind@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) { - if (!run_@kind@_@isa@_@TYPE@(args, dimensions, steps)) { - UNARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - *((npy_bool *)op1) = @func@(in1) != 0; - } + UNARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + *((npy_bool *)op1) = @func@(in1) != 0; } npy_clear_floatstatus_barrier((char*)dimensions); } -#endif -/**end repeat2**/ /**end repeat1**/ +#endif NPY_NO_EXPORT void @TYPE@_spacing(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index 411d53e94..26742946f 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -249,6 +249,20 @@ NPY_CPU_DISPATCH_DECLARE(NPY_NO_EXPORT void @TYPE@_@kind@, /**end repeat**/ #ifndef NPY_DISABLE_OPTIMIZATION + #include "loops_unary_fp_le.dispatch.h" +#endif +/**begin repeat + * #TYPE = FLOAT, DOUBLE# + */ +/**begin repeat1 + * #kind = isnan, isinf, isfinite, signbit# + */ +NPY_CPU_DISPATCH_DECLARE(NPY_NO_EXPORT void @TYPE@_@kind@, + (char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(data))) +/**end repeat1**/ +/**end repeat**/ + +#ifndef NPY_DISABLE_OPTIMIZATION #include "loops_unary.dispatch.h" #endif /**begin repeat @@ -400,6 +414,7 @@ NPY_CPU_DISPATCH_DECLARE(NPY_NO_EXPORT void @TYPE@_@kind@, ( * #c = f, f, , l# * #C = F, F, , L# * #half = 1, 0, 0, 0# + * #fd = 0, 1, 1, 0# */ /**begin repeat1 @@ -428,13 +443,13 @@ NPY_NO_EXPORT void /**begin repeat1 * #kind = isnan, isinf, isfinite, signbit, copysign, nextafter, spacing# * #func = npy_isnan, npy_isinf, npy_isfinite, npy_signbit, npy_copysign, nextafter, spacing# + * #dispatched = 1, 1, 1, 1, 0, 0, 0# **/ -/**begin repeat2 - * #ISA = , _avx512_skx# - **/ +#if !@fd@ || !@dispatched@ NPY_NO_EXPORT void -@TYPE@_@kind@@ISA@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); +@TYPE@_@kind@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); +#endif /**end repeat2**/ /**end repeat1**/ diff --git a/numpy/core/src/umath/loops_unary_fp_le.dispatch.c.src b/numpy/core/src/umath/loops_unary_fp_le.dispatch.c.src new file mode 100644 index 000000000..ba133dc1e --- /dev/null +++ b/numpy/core/src/umath/loops_unary_fp_le.dispatch.c.src @@ -0,0 +1,565 @@ +/*@targets + ** $maxopt baseline + ** sse2 sse41 + ** vsx2 + ** neon asimd + **/ + +/** + * Force use SSE only on x86, even if AVX2 or AVX512F are enabled + * through the baseline, since scatter(AVX512F) and gather very costly + * to handle non-contiguous memory access comparing with SSE for + * such small operations that this file covers. + */ +#define NPY_SIMD_FORCE_128 +#define _UMATHMODULE +#define _MULTIARRAYMODULE +#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#include <float.h> +#include "numpy/npy_math.h" +#include "simd/simd.h" +#include "loops_utils.h" +#include "loops.h" +#include "lowlevel_strided_loops.h" +// Provides the various *_LOOP macros +#include "fast_loop_macros.h" + +/** + * This code should really be merged into loops_unary_fp.dispatch.c.src + * However there is an issue with enabling the code here for VX and VXE + * as the shifts don't behave as expected. + * See the code below that references NPY__CPU_TARGET_VX and + * NPY_BIG_ENDIAN. Suspect that this is a big endian vector issue. + * + * Splitting the files out allows us to keep loops_unary_fp.dispatch.c.src + * building for VX and VXE so we don't regress performance while adding this + * code for other platforms. + */ +// TODO(@seiko2plus): add support for big-endian +#if NPY_SIMD_BIGENDIAN + #undef NPY_SIMD + #undef NPY_SIMD_F32 + #undef NPY_SIMD_F64 + #define NPY_SIMD 0 + #define NPY_SIMD_F32 0 + #define NPY_SIMD_F64 0 +#endif + +/******************************************************************************* + ** extra SIMD intrinsics + ******************************************************************************/ + +#if NPY_SIMD + +/** + * We define intrinsics for isnan, isinf, isfinite, and signbit below. There's + * a few flavors of each. We'll use f32 as an example although f64 versions + * are also defined. + * + * npyv_u32 npyv_KIND_f32(npyv_f32 v) + * These are mainly used for the single vector loops. As such, result should + * be bool true / false, ready to write back. + * + * npyv_b32 _npyv_KIND_f32(npyv_f32 v) + * These are used by the geneal intrinsics above as well as the multi-vector + * packing intrinsics. The multi-vector packing intrinsics are the ones + * utilized in the unrolled vector loops. Results should be vector masks + * of 0x00/0xff. + * + * npyv_u8 npyv_pack_KIND_f32(npyv_f32 v0, npyv_f32 v1, npyv_f32 v2, npyv_f32 v3) + * These are the multi-vector packing intrinsics utilized by unrolled vector + * loops. They perform the operation on all input vectors and pack the + * results to a single npyv_u8. Assuming NPY_SIMD == 128, that means we + * can pack results from 4x npyv_f32 or 8x npyv_64 in a single npyv_u8. + * Result should be bool true / false, ready to write back. + */ + +#if NPY_SIMD_F32 +NPY_FINLINE npyv_u32 +npyv_isnan_f32(npyv_f32 v) +{ + const npyv_u8 truemask = npyv_reinterpret_u8_u32(npyv_setall_u32(1==1)); + npyv_u8 notnan = npyv_reinterpret_u8_u32(npyv_cvt_u32_b32(npyv_notnan_f32(v))); + return npyv_reinterpret_u32_u8(npyv_andc_u8(truemask, notnan)); +} +NPY_FINLINE npyv_u8 +npyv_pack_isnan_f32(npyv_f32 v0, npyv_f32 v1, npyv_f32 v2, npyv_f32 v3) +{ + const npyv_u8 truemask = npyv_setall_u8(1==1); + npyv_b32 b0 = npyv_notnan_f32(v0); + npyv_b32 b1 = npyv_notnan_f32(v1); + npyv_b32 b2 = npyv_notnan_f32(v2); + npyv_b32 b3 = npyv_notnan_f32(v3); + npyv_b8 notnan = npyv_pack_b8_b32(b0, b1, b2, b3); + return npyv_andc_u8(truemask, npyv_cvt_u8_b8(notnan)); +} +#endif +#if NPY_SIMD_F64 +NPY_FINLINE npyv_u64 +npyv_isnan_f64(npyv_f64 v) +{ + const npyv_u8 truemask = npyv_reinterpret_u8_u64(npyv_setall_u64(1==1)); + npyv_u8 notnan = npyv_reinterpret_u8_u64(npyv_cvt_u64_b64(npyv_notnan_f64(v))); + return npyv_reinterpret_u64_u8(npyv_andc_u8(truemask, notnan)); +} +NPY_FINLINE npyv_u8 +npyv_pack_isnan_f64(npyv_f64 v0, npyv_f64 v1, npyv_f64 v2, npyv_f64 v3, + npyv_f64 v4, npyv_f64 v5, npyv_f64 v6, npyv_f64 v7) +{ + const npyv_u8 truemask = npyv_setall_u8(1==1); + npyv_b64 b0 = npyv_notnan_f64(v0); + npyv_b64 b1 = npyv_notnan_f64(v1); + npyv_b64 b2 = npyv_notnan_f64(v2); + npyv_b64 b3 = npyv_notnan_f64(v3); + npyv_b64 b4 = npyv_notnan_f64(v4); + npyv_b64 b5 = npyv_notnan_f64(v5); + npyv_b64 b6 = npyv_notnan_f64(v6); + npyv_b64 b7 = npyv_notnan_f64(v7); + npyv_b8 notnan = npyv_pack_b8_b64(b0, b1, b2, b3, b4, b5, b6, b7); + return npyv_andc_u8(truemask, npyv_cvt_u8_b8(notnan)); +} +#endif + +#if NPY_SIMD_F32 +NPY_FINLINE npyv_b32 +_npyv_isinf_f32(npyv_f32 v) +{ +#if defined(NPY_HAVE_NEON) + // abs(v) > FLT_MAX + const npyv_f32 fltmax = npyv_setall_f32(FLT_MAX); + return vcagtq_f32(v, fltmax); +#else + // cast out the sign and check if all exponent bits are set. + const npyv_u32 exp_mask = npyv_setall_u32(0xff000000); + npyv_u32 bits = npyv_shli_u32(npyv_reinterpret_u32_f32(v), 1); + return npyv_cmpeq_u32(bits, exp_mask); +#endif +} +NPY_FINLINE npyv_u32 +npyv_isinf_f32(npyv_f32 v) +{ + const npyv_u32 truemask = npyv_setall_u32(1==1); + return npyv_and_u32(truemask, npyv_cvt_u32_b32(_npyv_isinf_f32(v))); +} +NPY_FINLINE npyv_u8 +npyv_pack_isinf_f32(npyv_f32 v0, npyv_f32 v1, npyv_f32 v2, npyv_f32 v3) +{ + const npyv_u8 truemask = npyv_setall_u8(1==1); + npyv_b32 b0 = _npyv_isinf_f32(v0); + npyv_b32 b1 = _npyv_isinf_f32(v1); + npyv_b32 b2 = _npyv_isinf_f32(v2); + npyv_b32 b3 = _npyv_isinf_f32(v3); + npyv_b8 isinf = npyv_pack_b8_b32(b0, b1, b2, b3); + return npyv_and_u8(truemask, npyv_cvt_u8_b8(isinf)); +} +#endif // NPY_SIMD_F32 +#if NPY_SIMD_F64 +NPY_FINLINE npyv_b64 +_npyv_isinf_f64(npyv_f64 v) +{ +#if defined(NPY_HAVE_NEON) + // abs(v) > DBL_MAX + const npyv_f64 fltmax = npyv_setall_f64(DBL_MAX); + return vcagtq_f64(v, fltmax); +#else + // cast out the sign and check if all exponent bits are set. + const npyv_u64 exp_mask = npyv_setall_u64(0xffe0000000000000); + npyv_u64 bits = npyv_shli_u64(npyv_reinterpret_u64_f64(v), 1); + return npyv_cmpeq_u64(bits, exp_mask); +#endif +} +NPY_FINLINE npyv_u64 +npyv_isinf_f64(npyv_f64 v) +{ + const npyv_u64 truemask = npyv_setall_u64(1==1); + return npyv_and_u64(truemask, npyv_cvt_u64_b64(_npyv_isinf_f64(v))); +} +NPY_FINLINE npyv_u8 +npyv_pack_isinf_f64(npyv_f64 v0, npyv_f64 v1, npyv_f64 v2, npyv_f64 v3, + npyv_f64 v4, npyv_f64 v5, npyv_f64 v6, npyv_f64 v7) +{ + const npyv_u8 truemask = npyv_setall_u8(1==1); + npyv_b64 b0 = _npyv_isinf_f64(v0); + npyv_b64 b1 = _npyv_isinf_f64(v1); + npyv_b64 b2 = _npyv_isinf_f64(v2); + npyv_b64 b3 = _npyv_isinf_f64(v3); + npyv_b64 b4 = _npyv_isinf_f64(v4); + npyv_b64 b5 = _npyv_isinf_f64(v5); + npyv_b64 b6 = _npyv_isinf_f64(v6); + npyv_b64 b7 = _npyv_isinf_f64(v7); + npyv_b8 isinf = npyv_pack_b8_b64(b0, b1, b2, b3, b4, b5, b6, b7); + return npyv_and_u8(truemask, npyv_cvt_u8_b8(isinf)); +} +#endif // NPY_SIMD_F64 + + +#if NPY_SIMD_F32 +NPY_FINLINE npyv_b32 +npyv_notfinite_f32(npyv_f32 v) +{ + // cast out the sign and check if all exponent bits are set + // no matter the mentissa is. + const npyv_u32 exp_mask = npyv_setall_u32(0x7f800000); + npyv_u32 bits = npyv_reinterpret_u32_f32(v); + bits = npyv_and_u32(bits, exp_mask); + return npyv_cmpeq_u32(bits, exp_mask); +} +NPY_FINLINE npyv_u32 +npyv_isfinite_f32(npyv_f32 v) +{ + const npyv_u8 truemask = npyv_reinterpret_u8_u32(npyv_setall_u32(1==1)); + npyv_u8 notfinite = npyv_reinterpret_u8_u32(npyv_cvt_u32_b32(npyv_notfinite_f32(v))); + return npyv_reinterpret_u32_u8(npyv_andc_u8(truemask, notfinite)); +} +NPY_FINLINE npyv_u8 +npyv_pack_isfinite_f32(npyv_f32 v0, npyv_f32 v1, npyv_f32 v2, npyv_f32 v3) +{ +#if defined(NPY_HAVE_NEON) && defined(__aarch64__) + // F32 exponent is 8-bits, which means we can pack multiple into + // a single vector. We shift out sign bit so that we're left + // with only exponent in high byte. If not all bits are set, + // then we've got a finite number. + uint8x16x4_t tbl; + tbl.val[0] = npyv_reinterpret_u8_u32(npyv_shli_u32(npyv_reinterpret_u32_f32(v0), 1)); + tbl.val[1] = npyv_reinterpret_u8_u32(npyv_shli_u32(npyv_reinterpret_u32_f32(v1), 1)); + tbl.val[2] = npyv_reinterpret_u8_u32(npyv_shli_u32(npyv_reinterpret_u32_f32(v2), 1)); + tbl.val[3] = npyv_reinterpret_u8_u32(npyv_shli_u32(npyv_reinterpret_u32_f32(v3), 1)); + + const npyv_u8 permute = {3,7,11,15, 19,23,27,31, 35,39,43,47, 51,55,59,63}; + npyv_u8 r = vqtbl4q_u8(tbl, permute); + + const npyv_u8 expmask = npyv_setall_u8(0xff); + r = npyv_cmpneq_u8(r, expmask); + r = vshrq_n_u8(r, 7); + return r; +#else + const npyv_u8 truemask = npyv_setall_u8(1==1); + npyv_b32 b0 = npyv_notfinite_f32(v0); + npyv_b32 b1 = npyv_notfinite_f32(v1); + npyv_b32 b2 = npyv_notfinite_f32(v2); + npyv_b32 b3 = npyv_notfinite_f32(v3); + npyv_b8 notfinite = npyv_pack_b8_b32(b0, b1, b2, b3); + return npyv_andc_u8(truemask, npyv_cvt_u8_b8(notfinite)); +#endif +} +#endif // NPY_SIMD_F32 +#if NPY_SIMD_F64 +NPY_FINLINE npyv_b64 +npyv_notfinite_f64(npyv_f64 v) +{ + // cast out the sign and check if all exponent bits are set + // no matter the mantissa is. + const npyv_u64 exp_mask = npyv_setall_u64(0x7ff0000000000000); + npyv_u64 bits = npyv_reinterpret_u64_f64(v); + bits = npyv_and_u64(bits, exp_mask); + return npyv_cmpeq_u64(bits, exp_mask); +} +NPY_FINLINE npyv_u64 +npyv_isfinite_f64(npyv_f64 v) +{ + const npyv_u8 truemask = npyv_reinterpret_u8_u64(npyv_setall_u64(1==1)); + npyv_u8 notfinite = npyv_reinterpret_u8_u64(npyv_cvt_u64_b64(npyv_notfinite_f64(v))); + return npyv_reinterpret_u64_u8(npyv_andc_u8(truemask, notfinite)); +} +NPY_FINLINE npyv_u8 +npyv_pack_isfinite_f64(npyv_f64 v0, npyv_f64 v1, npyv_f64 v2, npyv_f64 v3, + npyv_f64 v4, npyv_f64 v5, npyv_f64 v6, npyv_f64 v7) +{ +#if defined(NPY_HAVE_NEON) && defined(__aarch64__) + // F64 exponent is 11-bits, which means we can pack multiple into + // a single vector. We'll need to use u16 to fit all exponent + // bits. If not all bits are set, then we've got a finite number. + uint8x16x4_t t0123, t4567; + t0123.val[0] = npyv_reinterpret_u8_f64(v0); + t0123.val[1] = npyv_reinterpret_u8_f64(v1); + t0123.val[2] = npyv_reinterpret_u8_f64(v2); + t0123.val[3] = npyv_reinterpret_u8_f64(v3); + t4567.val[0] = npyv_reinterpret_u8_f64(v4); + t4567.val[1] = npyv_reinterpret_u8_f64(v5); + t4567.val[2] = npyv_reinterpret_u8_f64(v6); + t4567.val[3] = npyv_reinterpret_u8_f64(v7); + + const npyv_u8 permute = {6,7,14,15, 22,23,30,31, 38,39,46,47, 54,55,62,63}; + npyv_u16 r0 = npyv_reinterpret_u16_u8(vqtbl4q_u8(t0123, permute)); + npyv_u16 r1 = npyv_reinterpret_u16_u8(vqtbl4q_u8(t4567, permute)); + + const npyv_u16 expmask = npyv_setall_u16(0x7ff0); + r0 = npyv_and_u16(r0, expmask); + r0 = npyv_cmpneq_u16(r0, expmask); + r0 = npyv_shri_u16(r0, 15); + r1 = npyv_and_u16(r1, expmask); + r1 = npyv_cmpneq_u16(r1, expmask); + r1 = npyv_shri_u16(r1, 15); + + npyv_u8 r = npyv_pack_b8_b16(r0, r1); + return r; +#else + const npyv_u8 truemask = npyv_setall_u8(1==1); + npyv_b64 b0 = npyv_notfinite_f64(v0); + npyv_b64 b1 = npyv_notfinite_f64(v1); + npyv_b64 b2 = npyv_notfinite_f64(v2); + npyv_b64 b3 = npyv_notfinite_f64(v3); + npyv_b64 b4 = npyv_notfinite_f64(v4); + npyv_b64 b5 = npyv_notfinite_f64(v5); + npyv_b64 b6 = npyv_notfinite_f64(v6); + npyv_b64 b7 = npyv_notfinite_f64(v7); + npyv_b8 notfinite = npyv_pack_b8_b64(b0, b1, b2, b3, b4, b5, b6, b7); + return npyv_andc_u8(truemask, npyv_cvt_u8_b8(notfinite)); +#endif +} +#endif // NPY_SIMD_F64 + +#if NPY_SIMD_F32 +NPY_FINLINE npyv_u32 +npyv_signbit_f32(npyv_f32 v) +{ + return npyv_shri_u32(npyv_reinterpret_u32_f32(v), (sizeof(npyv_lanetype_f32)*8)-1); +} +NPY_FINLINE npyv_u8 +npyv_pack_signbit_f32(npyv_f32 v0, npyv_f32 v1, npyv_f32 v2, npyv_f32 v3) +{ +#if defined(NPY_HAVE_NEON) && defined(__aarch64__) + // We only need high byte for signbit, which means we can pack + // multiple inputs into a single vector. + uint8x16x4_t tbl; + tbl.val[0] = npyv_reinterpret_u8_f32(v0); + tbl.val[1] = npyv_reinterpret_u8_f32(v1); + tbl.val[2] = npyv_reinterpret_u8_f32(v2); + tbl.val[3] = npyv_reinterpret_u8_f32(v3); + + const npyv_u8 permute = {3,7,11,15, 19,23,27,31, 35,39,43,47, 51,55,59,63}; + npyv_u8 r = vqtbl4q_u8(tbl, permute); + r = vshrq_n_u8(r, 7); + return r; +#else + npyv_b32 b0 = npyv_cvt_b32_u32(npyv_signbit_f32(v0)); + npyv_b32 b1 = npyv_cvt_b32_u32(npyv_signbit_f32(v1)); + npyv_b32 b2 = npyv_cvt_b32_u32(npyv_signbit_f32(v2)); + npyv_b32 b3 = npyv_cvt_b32_u32(npyv_signbit_f32(v3)); + npyv_b8 signbit = npyv_pack_b8_b32(b0, b1, b2, b3); + return npyv_cvt_u8_b8(signbit); +#endif +} +#endif // NPY_SIMD_F32 +#if NPY_SIMD_F64 +NPY_FINLINE npyv_u64 +npyv_signbit_f64(npyv_f64 v) +{ + return npyv_shri_u64(npyv_reinterpret_u64_f64(v), (sizeof(npyv_lanetype_f64)*8)-1); +} +NPY_FINLINE npyv_u8 +npyv_pack_signbit_f64(npyv_f64 v0, npyv_f64 v1, npyv_f64 v2, npyv_f64 v3, + npyv_f64 v4, npyv_f64 v5, npyv_f64 v6, npyv_f64 v7) +{ +#if defined(NPY_HAVE_NEON) && defined(__aarch64__) + // We only need high byte for signbit, which means we can pack + // multiple inputs into a single vector. + + // vuzp2 faster than vtbl for f64 + npyv_u32 v01 = vuzp2q_u32(npyv_reinterpret_u32_f64(v0), npyv_reinterpret_u32_f64(v1)); + npyv_u32 v23 = vuzp2q_u32(npyv_reinterpret_u32_f64(v2), npyv_reinterpret_u32_f64(v3)); + npyv_u32 v45 = vuzp2q_u32(npyv_reinterpret_u32_f64(v4), npyv_reinterpret_u32_f64(v5)); + npyv_u32 v67 = vuzp2q_u32(npyv_reinterpret_u32_f64(v6), npyv_reinterpret_u32_f64(v7)); + + npyv_u16 v0123 = vuzp2q_u16(npyv_reinterpret_u16_u32(v01), npyv_reinterpret_u16_u32(v23)); + npyv_u16 v4567 = vuzp2q_u16(npyv_reinterpret_u16_u32(v45), npyv_reinterpret_u16_u32(v67)); + + npyv_u8 r = vuzp2q_u8(npyv_reinterpret_u8_u16(v0123), npyv_reinterpret_u8_u16(v4567)); + r = vshrq_n_u8(r, 7); + return r; +#else + npyv_b64 b0 = npyv_cvt_b64_u64(npyv_signbit_f64(v0)); + npyv_b64 b1 = npyv_cvt_b64_u64(npyv_signbit_f64(v1)); + npyv_b64 b2 = npyv_cvt_b64_u64(npyv_signbit_f64(v2)); + npyv_b64 b3 = npyv_cvt_b64_u64(npyv_signbit_f64(v3)); + npyv_b64 b4 = npyv_cvt_b64_u64(npyv_signbit_f64(v4)); + npyv_b64 b5 = npyv_cvt_b64_u64(npyv_signbit_f64(v5)); + npyv_b64 b6 = npyv_cvt_b64_u64(npyv_signbit_f64(v6)); + npyv_b64 b7 = npyv_cvt_b64_u64(npyv_signbit_f64(v7)); + npyv_b8 signbit = npyv_pack_b8_b64(b0, b1, b2, b3, b4, b5, b6, b7); + return npyv_cvt_u8_b8(signbit); +#endif +} +#endif // NPY_SIMD_F64 + +#endif // NPY_SIMD + +/******************************************************************************** + ** Defining the SIMD kernels + ********************************************************************************/ +/** Notes: + * - avoid the use of libmath to unify fp/domain errors + * for both scalars and vectors among all compilers/architectures. + * - use intrinsic npyv_load_till_* instead of npyv_load_tillz_ + * to fill the remind lanes with 1.0 to avoid divide by zero fp + * exception in reciprocal. + */ +#define CONTIG 0 +#define NCONTIG 1 + +/**begin repeat + * #TYPE = FLOAT, DOUBLE# + * #sfx = f32, f64# + * #VCHK = NPY_SIMD_F32, NPY_SIMD_F64# + * #ssfx = 32, 64# + */ +#if @VCHK@ +/**begin repeat1 + * #kind = isnan, isinf, isfinite, signbit# + */ +/**begin repeat2 + * #STYPE = CONTIG, NCONTIG, CONTIG, NCONTIG# + * #DTYPE = CONTIG, CONTIG, NCONTIG, NCONTIG# + */ +static void simd_unary_@kind@_@TYPE@_@STYPE@_@DTYPE@ +(const void *src, npy_intp istride, void *dst, npy_intp ostride, npy_intp len) +{ + const npyv_lanetype_@sfx@ *ip = src; + npy_bool *op = dst; + + // How many vectors can be packed into a u8 / bool vector? + #define PACK_FACTOR (NPY_SIMD_WIDTH / npyv_nlanes_@sfx@) + assert(PACK_FACTOR == 4 || PACK_FACTOR == 8); + + const int vstep = npyv_nlanes_@sfx@; + const int wstep = vstep * PACK_FACTOR; + + // unrolled iterations + for (; len >= wstep; len -= wstep, ip += istride*wstep, op += ostride*wstep) { + // Load vectors + #if @STYPE@ == CONTIG + // contiguous input + npyv_@sfx@ v0 = npyv_load_@sfx@(ip + vstep * 0); + npyv_@sfx@ v1 = npyv_load_@sfx@(ip + vstep * 1); + npyv_@sfx@ v2 = npyv_load_@sfx@(ip + vstep * 2); + npyv_@sfx@ v3 = npyv_load_@sfx@(ip + vstep * 3); + #if PACK_FACTOR == 8 + npyv_@sfx@ v4 = npyv_load_@sfx@(ip + vstep * 4); + npyv_@sfx@ v5 = npyv_load_@sfx@(ip + vstep * 5); + npyv_@sfx@ v6 = npyv_load_@sfx@(ip + vstep * 6); + npyv_@sfx@ v7 = npyv_load_@sfx@(ip + vstep * 7); + #endif + #else + // non-contiguous input + npyv_@sfx@ v0 = npyv_loadn_@sfx@(ip + istride * vstep * 0, istride); + npyv_@sfx@ v1 = npyv_loadn_@sfx@(ip + istride * vstep * 1, istride); + npyv_@sfx@ v2 = npyv_loadn_@sfx@(ip + istride * vstep * 2, istride); + npyv_@sfx@ v3 = npyv_loadn_@sfx@(ip + istride * vstep * 3, istride); + #if PACK_FACTOR == 8 + npyv_@sfx@ v4 = npyv_loadn_@sfx@(ip + istride * vstep * 4, istride); + npyv_@sfx@ v5 = npyv_loadn_@sfx@(ip + istride * vstep * 5, istride); + npyv_@sfx@ v6 = npyv_loadn_@sfx@(ip + istride * vstep * 6, istride); + npyv_@sfx@ v7 = npyv_loadn_@sfx@(ip + istride * vstep * 7, istride); + #endif + #endif + + #if PACK_FACTOR == 4 + npyv_u8 r = npyv_pack_@kind@_@sfx@(v0, v1, v2, v3); + #elif PACK_FACTOR == 8 + npyv_u8 r = npyv_pack_@kind@_@sfx@(v0, v1, v2, v3, v4, v5, v6, v7); + #endif + + #if @DTYPE@ == CONTIG + npyv_store_u8(op, r); + #else // @DTYPE@ == CONTIG + // Results are packed, so we can just loop over them + npy_uint8 lane[npyv_nlanes_u8]; + npyv_store_u8(lane, r); + for (int ln=0; (ln * sizeof(npyv_lanetype_@sfx@)) < npyv_nlanes_u8; ++ln){ + op[ln * ostride] = lane[ln * sizeof(npyv_lanetype_@sfx@)]; + } + #endif // @DTYPE@ == CONTIG + } + + // vector-sized iterations + for (; len >= vstep; len -= vstep, ip += istride*vstep, op += ostride*vstep) { + #if @STYPE@ == CONTIG + npyv_@sfx@ v = npyv_load_@sfx@(ip); + #else + npyv_@sfx@ v = npyv_loadn_@sfx@(ip, istride); + #endif + + npyv_u@ssfx@ r = npyv_@kind@_@sfx@(v); + + npy_uint8 lane[npyv_nlanes_u8]; + npyv_store_u8(lane, npyv_reinterpret_u8_u@ssfx@(r)); + + op[0 * ostride] = lane[0 * sizeof(npyv_lanetype_@sfx@)]; + op[1 * ostride] = lane[1 * sizeof(npyv_lanetype_@sfx@)]; + #if npyv_nlanes_@sfx@ == 4 + op[2 * ostride] = lane[2 * sizeof(npyv_lanetype_@sfx@)]; + op[3 * ostride] = lane[3 * sizeof(npyv_lanetype_@sfx@)]; + #endif + } + + #undef PACK_FACTOR + + // Scalar loop to finish off + for (; len > 0; --len, ip += istride, op += ostride) { + *op = (npy_@kind@(*ip) != 0); + } + + npyv_cleanup(); +} +/**end repeat2**/ +/**end repeat1**/ + +#endif // @VCHK@ +/**end repeat**/ + +/******************************************************************************** + ** Defining ufunc inner functions + ********************************************************************************/ +/**begin repeat + * #TYPE = FLOAT, DOUBLE# + * #sfx = f32, f64# + * #VCHK = NPY_SIMD_F32, NPY_SIMD_F64# + */ + +/**begin repeat1 + * #kind = isnan, isinf, isfinite, signbit# + **/ +NPY_NO_EXPORT void NPY_CPU_DISPATCH_CURFX(@TYPE@_@kind@) +(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) +{ +#if @VCHK@ + const char *ip = args[0]; + char *op = args[1]; + const npy_intp istep = steps[0]; + const npy_intp ostep = steps[1]; + npy_intp len = dimensions[0]; + const int ilsize = sizeof(npyv_lanetype_@sfx@); + const int olsize = sizeof(npy_bool); + const npy_intp istride = istep / ilsize; + const npy_intp ostride = ostep / olsize; + assert(len <= 1 || ostep % olsize == 0); + + if ((istep % ilsize == 0) && + !is_mem_overlap(ip, istep, op, ostep, len) && + npyv_loadable_stride_@sfx@(istride) && + npyv_storable_stride_@sfx@(ostride)) + { + if (istride == 1 && ostride == 1) { + simd_unary_@kind@_@TYPE@_CONTIG_CONTIG(ip, 1, op, 1, len); + } + else if (ostride == 1) { + simd_unary_@kind@_@TYPE@_NCONTIG_CONTIG(ip, istride, op, 1, len); + } + else if (istride == 1) { + simd_unary_@kind@_@TYPE@_CONTIG_NCONTIG(ip, 1, op, ostride, len); + } else { + simd_unary_@kind@_@TYPE@_NCONTIG_NCONTIG(ip, istride, op, ostride, len); + } + } else +#endif // @VCHK@ + { + UNARY_LOOP { + const npyv_lanetype_@sfx@ in = *(npyv_lanetype_@sfx@ *)ip1; + *((npy_bool *)op1) = (npy_@kind@(in) != 0); + } + } + + npy_clear_floatstatus_barrier((char*)dimensions); +} +/**end repeat1**/ +/**end repeat**/ diff --git a/numpy/core/src/umath/simd.inc.src b/numpy/core/src/umath/simd.inc.src index 10c44ce30..803c45948 100644 --- a/numpy/core/src/umath/simd.inc.src +++ b/numpy/core/src/umath/simd.inc.src @@ -82,78 +82,6 @@ run_unary_avx512f_@func@_@TYPE@(char **args, const npy_intp *dimensions, const n /**end repeat1**/ /**end repeat**/ -/* - ***************************************************************************** - ** FLOAT DISPATCHERS - ***************************************************************************** - */ - -/**begin repeat - * #type = npy_float, npy_double, npy_longdouble# - * #TYPE = FLOAT, DOUBLE, LONGDOUBLE# - * #EXISTS = 1, 1, 0# - */ - -/**begin repeat1 - * #func = isnan, isfinite, isinf, signbit# - */ - -#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS && @EXISTS@ -static inline NPY_GCC_TARGET_AVX512_SKX void -AVX512_SKX_@func@_@TYPE@(npy_bool*, @type@*, const npy_intp n, const npy_intp stride); -#endif - -static inline int -run_@func@_avx512_skx_@TYPE@(char **args, npy_intp const *dimensions, npy_intp const *steps) -{ -#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS && @EXISTS@ - if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(@type@), sizeof(npy_bool), 64)) { - AVX512_SKX_@func@_@TYPE@((npy_bool*)args[1], (@type@*)args[0], dimensions[0], steps[0]); - return 1; - } - else { - return 0; - } -#endif - return 0; -} - - -/**end repeat1**/ -/**end repeat**/ - -/**begin repeat - * Float types - * #type = npy_float, npy_double, npy_longdouble# - * #TYPE = FLOAT, DOUBLE, LONGDOUBLE# - * #vector = 1, 1, 0# - * #VECTOR = NPY_SIMD, NPY_SIMD_F64, 0 # - */ -/**begin repeat1 - * #kind = isnan, isfinite, isinf, signbit# - */ -#if @vector@ && defined NPY_HAVE_SSE2_INTRINSICS - -static void -sse2_@kind@_@TYPE@(npy_bool * op, @type@ * ip1, npy_intp n); - -#endif - -static inline int -run_@kind@_simd_@TYPE@(char **args, npy_intp const *dimensions, npy_intp const *steps) -{ -#if @vector@ && defined NPY_HAVE_SSE2_INTRINSICS - if (steps[0] == sizeof(@type@) && steps[1] == 1 && - npy_is_aligned(args[0], sizeof(@type@))) { - sse2_@kind@_@TYPE@((npy_bool*)args[1], (@type@*)args[0], dimensions[0]); - return 1; - } -#endif - return 0; -} -/**end repeat1**/ -/**end repeat**/ - #ifdef NPY_HAVE_SSE2_INTRINSICS /* @@ -189,139 +117,6 @@ NPY_FINLINE npy_double sse2_horizontal_@VOP@___m128d(__m128d v) } /**end repeat**/ -/**begin repeat - * #type = npy_float, npy_double# - * #TYPE = FLOAT, DOUBLE# - * #scalarf = npy_sqrtf, npy_sqrt# - * #c = f, # - * #vtype = __m128, __m128d# - * #vtype256 = __m256, __m256d# - * #vtype512 = __m512, __m512d# - * #vpre = _mm, _mm# - * #vpre256 = _mm256, _mm256# - * #vpre512 = _mm512, _mm512# - * #vsuf = ps, pd# - * #vsufs = ss, sd# - * #nan = NPY_NANF, NPY_NAN# - * #double = 0, 1# - * #cast = _mm_castps_si128, _mm_castpd_si128# - */ -/* - * compress 4 vectors to 4/8 bytes in op with filled with 0 or 1 - * the last vector is passed as a pointer as MSVC 2010 is unable to ignore the - * calling convention leading to C2719 on 32 bit, see #4795 - */ -NPY_FINLINE void -sse2_compress4_to_byte_@TYPE@(@vtype@ r1, @vtype@ r2, @vtype@ r3, @vtype@ * r4, - npy_bool * op) -{ - const __m128i mask = @vpre@_set1_epi8(0x1); - __m128i ir1 = @vpre@_packs_epi32(@cast@(r1), @cast@(r2)); - __m128i ir2 = @vpre@_packs_epi32(@cast@(r3), @cast@(*r4)); - __m128i rr = @vpre@_packs_epi16(ir1, ir2); -#if @double@ - rr = @vpre@_packs_epi16(rr, rr); - rr = @vpre@_and_si128(rr, mask); - @vpre@_storel_epi64((__m128i*)op, rr); -#else - rr = @vpre@_and_si128(rr, mask); - @vpre@_storeu_si128((__m128i*)op, rr); -#endif -} - -static void -sse2_signbit_@TYPE@(npy_bool * op, @type@ * ip1, npy_intp n) -{ - LOOP_BLOCK_ALIGN_VAR(ip1, @type@, VECTOR_SIZE_BYTES) { - op[i] = npy_signbit(ip1[i]) != 0; - } - LOOP_BLOCKED(@type@, VECTOR_SIZE_BYTES) { - @vtype@ a = @vpre@_load_@vsuf@(&ip1[i]); - int r = @vpre@_movemask_@vsuf@(a); - if (sizeof(@type@) == 8) { - op[i] = r & 1; - op[i + 1] = (r >> 1); - } - else { - op[i] = r & 1; - op[i + 1] = (r >> 1) & 1; - op[i + 2] = (r >> 2) & 1; - op[i + 3] = (r >> 3); - } - } - LOOP_BLOCKED_END { - op[i] = npy_signbit(ip1[i]) != 0; - } -} - -/**begin repeat1 - * #kind = isnan, isfinite, isinf# - * #var = 0, 1, 2# - */ - -static void -sse2_@kind@_@TYPE@(npy_bool * op, @type@ * ip1, npy_intp n) -{ -#if @var@ != 0 /* isinf/isfinite */ - /* signbit mask 0x7FFFFFFF after andnot */ - const @vtype@ mask = @vpre@_set1_@vsuf@(-0.@c@); - const @vtype@ ones = @vpre@_cmpeq_@vsuf@(@vpre@_setzero_@vsuf@(), - @vpre@_setzero_@vsuf@()); -#if @double@ - const @vtype@ fltmax = @vpre@_set1_@vsuf@(DBL_MAX); -#else - const @vtype@ fltmax = @vpre@_set1_@vsuf@(FLT_MAX); -#endif -#endif - LOOP_BLOCK_ALIGN_VAR(ip1, @type@, VECTOR_SIZE_BYTES) { - op[i] = npy_@kind@(ip1[i]) != 0; - } - LOOP_BLOCKED(@type@, 4 * VECTOR_SIZE_BYTES) { - @vtype@ a = @vpre@_load_@vsuf@(&ip1[i + 0 * VECTOR_SIZE_BYTES / sizeof(@type@)]); - @vtype@ b = @vpre@_load_@vsuf@(&ip1[i + 1 * VECTOR_SIZE_BYTES / sizeof(@type@)]); - @vtype@ c = @vpre@_load_@vsuf@(&ip1[i + 2 * VECTOR_SIZE_BYTES / sizeof(@type@)]); - @vtype@ d = @vpre@_load_@vsuf@(&ip1[i + 3 * VECTOR_SIZE_BYTES / sizeof(@type@)]); - @vtype@ r1, r2, r3, r4; -#if @var@ != 0 /* isinf/isfinite */ - /* fabs via masking of sign bit */ - r1 = @vpre@_andnot_@vsuf@(mask, a); - r2 = @vpre@_andnot_@vsuf@(mask, b); - r3 = @vpre@_andnot_@vsuf@(mask, c); - r4 = @vpre@_andnot_@vsuf@(mask, d); -#if @var@ == 1 /* isfinite */ - /* negative compare against max float, nan is always true */ - r1 = @vpre@_cmpnle_@vsuf@(r1, fltmax); - r2 = @vpre@_cmpnle_@vsuf@(r2, fltmax); - r3 = @vpre@_cmpnle_@vsuf@(r3, fltmax); - r4 = @vpre@_cmpnle_@vsuf@(r4, fltmax); -#else /* isinf */ - r1 = @vpre@_cmpnlt_@vsuf@(fltmax, r1); - r2 = @vpre@_cmpnlt_@vsuf@(fltmax, r2); - r3 = @vpre@_cmpnlt_@vsuf@(fltmax, r3); - r4 = @vpre@_cmpnlt_@vsuf@(fltmax, r4); -#endif - /* flip results to what we want (andnot as there is no sse not) */ - r1 = @vpre@_andnot_@vsuf@(r1, ones); - r2 = @vpre@_andnot_@vsuf@(r2, ones); - r3 = @vpre@_andnot_@vsuf@(r3, ones); - r4 = @vpre@_andnot_@vsuf@(r4, ones); -#endif -#if @var@ == 0 /* isnan */ - r1 = @vpre@_cmpneq_@vsuf@(a, a); - r2 = @vpre@_cmpneq_@vsuf@(b, b); - r3 = @vpre@_cmpneq_@vsuf@(c, c); - r4 = @vpre@_cmpneq_@vsuf@(d, d); -#endif - sse2_compress4_to_byte_@TYPE@(r1, r2, r3, &r4, &op[i]); - } - LOOP_BLOCKED_END { - op[i] = npy_@kind@(ip1[i]) != 0; - } -} - -/**end repeat1**/ -/**end repeat**/ - /* bunch of helper functions used in ISA_exp/log_FLOAT*/ #if defined HAVE_ATTRIBUTE_TARGET_AVX2_WITH_INTRINSICS @@ -714,85 +509,6 @@ NPY_FINLINE NPY_GCC_OPT_3 NPY_GCC_TARGET_@ISA@ @vtype@d /**end repeat**/ /**begin repeat - * #type = npy_float, npy_double# - * #TYPE = FLOAT, DOUBLE# - * #num_lanes = 16, 8# - * #vsuffix = ps, pd# - * #mask = __mmask16, __mmask8# - * #vtype = __m512, __m512d# - * #scale = 4, 8# - * #vindextype = __m512i, __m256i# - * #vindexload = _mm512_loadu_si512, _mm256_loadu_si256# - * #episize = epi32, epi64# - */ - -/**begin repeat1 - * #func = isnan, isfinite, isinf, signbit# - * #IMM8 = 0x81, 0x99, 0x18, 0x04# - * #is_finite = 0, 1, 0, 0# - * #is_signbit = 0, 0, 0, 1# - */ - -#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS -static inline NPY_GCC_TARGET_AVX512_SKX void -AVX512_SKX_@func@_@TYPE@(npy_bool* op, @type@* ip, const npy_intp array_size, const npy_intp steps) -{ - const npy_intp stride_ip = steps/(npy_intp)sizeof(@type@); - npy_intp num_remaining_elements = array_size; - - @mask@ load_mask = avx512_get_full_load_mask_@vsuffix@(); -#if @is_signbit@ - @vtype@ signbit = _mm512_set1_@vsuffix@(-0.0); -#endif - - /* - * Note: while generally indices are npy_intp, we ensure that our maximum - * index will fit in an int32 as a precondition for this function via - * IS_OUTPUT_BLOCKABLE_UNARY - */ - - npy_int32 index_ip[@num_lanes@]; - for (npy_int32 ii = 0; ii < @num_lanes@; ii++) { - index_ip[ii] = ii*stride_ip; - } - @vindextype@ vindex_ip = @vindexload@((@vindextype@*)&index_ip[0]); - @vtype@ zeros_f = _mm512_setzero_@vsuffix@(); - __m512i ones = _mm512_set1_@episize@(1); - - while (num_remaining_elements > 0) { - if (num_remaining_elements < @num_lanes@) { - load_mask = avx512_get_partial_load_mask_@vsuffix@( - num_remaining_elements, @num_lanes@); - } - @vtype@ x1; - if (stride_ip == 1) { - x1 = avx512_masked_load_@vsuffix@(load_mask, ip); - } - else { - x1 = avx512_masked_gather_@vsuffix@(zeros_f, ip, vindex_ip, load_mask); - } -#if @is_signbit@ - x1 = _mm512_and_@vsuffix@(x1,signbit); -#endif - - @mask@ fpclassmask = _mm512_fpclass_@vsuffix@_mask(x1, @IMM8@); -#if @is_finite@ - fpclassmask = _mm512_knot(fpclassmask); -#endif - - __m128i out =_mm512_maskz_cvts@episize@_epi8(fpclassmask, ones); - _mm_mask_storeu_epi8(op, load_mask, out); - - ip += @num_lanes@*stride_ip; - op += @num_lanes@; - num_remaining_elements -= @num_lanes@; - } -} -#endif -/**end repeat1**/ -/**end repeat**/ - -/**begin repeat * #TYPE = CFLOAT, CDOUBLE# * #type = npy_float, npy_double# * #num_lanes = 16, 8# |
