From d04d4da652cebc65020035822b79c6afc6da8c09 Mon Sep 17 00:00:00 2001 From: Bine Brank <b.brank@fz-juelich.de> Date: Mon, 9 Mar 2020 14:34:33 +0100 Subject: [PATCH] bitwise ops --- .ycm_extra_conf.py | 4 + Makefile | 1 + backend/sve/sve_complex_double.hpp | 21 +++++ backend/sve/sve_complex_float.hpp | 21 +++++ backend/sve/sve_double.hpp | 9 +++ backend/sve/sve_float.hpp | 9 +++ backend/sve/sve_int.hpp | 9 +++ bitwise/generic/gen_bit_ops.hpp | 76 ++++++++++++++++++ bitwise/sve/sve_bit_ops.hpp | 122 ++++++++++++++++++++++++++++- simd.hpp | 2 - vector_test.x | Bin 0 -> 72080 bytes 11 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 .ycm_extra_conf.py create mode 100755 vector_test.x diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py new file mode 100644 index 0000000..1072f83 --- /dev/null +++ b/.ycm_extra_conf.py @@ -0,0 +1,4 @@ +def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c++', '-march=armv8-a_sve', 'O3', '-Wall', '-Wextra', '-Werror'], + } diff --git a/Makefile b/Makefile index de5e44d..38f15a2 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ HEADERS = simd.hpp \ matrix_type.hpp matrix_overloading.hpp \ arithmetics/generic/generic_vector_ops.hpp arithmetics/generic/generic.hpp arithmetics/generic/generic_matrix_ops.hpp \ arithmetics/sve/sve_vector_ops.hpp backend/sve/sve_backend.hpp arithmetics/sve/sve.hpp arithmetics/sve/sve_matrix_ops.hpp \ + backend/sve/sve_complex_double.hpp backend/sve/sve_complex_float.hpp backend/sve/sve_double.hpp backend/sve/sve_float.hpp \ loadstore/sve/sve_load.hpp all: ${TARGET} diff --git a/backend/sve/sve_complex_double.hpp b/backend/sve/sve_complex_double.hpp index 7292b48..04a58e3 100644 --- a/backend/sve/sve_complex_double.hpp +++ b/backend/sve/sve_complex_double.hpp @@ -94,6 +94,13 @@ struct svetype<complex<double>>{ return {re, im}; } + static inline vec and_z(svbool_t pg, vec op1, vec op2) + { + svuint64_t re = svand_z(pg, svreinterpret_u64(op1.v0), svreinterpret_u64(op2.v0)); + svuint64_t im = svand_z(pg, svreinterpret_u64(op1.v1), svreinterpret_u64(op2.v1)); + return {svreinterpret_f64(re), svreinterpret_f64(im)}; + } + static inline vec orr_z(svbool_t pg, vec op1, vec op2) { svuint64_t re = svorr_z(pg, svreinterpret_u64(op1.v0), svreinterpret_u64(op2.v0)); @@ -101,6 +108,20 @@ struct svetype<complex<double>>{ return {svreinterpret_f64(re), svreinterpret_f64(im)}; } + static inline vec eor_z(svbool_t pg, vec op1, vec op2) + { + svuint64_t re = sveor_z(pg, svreinterpret_u64(op1.v0), svreinterpret_u64(op2.v0)); + svuint64_t im = sveor_z(pg, svreinterpret_u64(op1.v1), svreinterpret_u64(op2.v1)); + return {svreinterpret_f64(re), svreinterpret_f64(im)}; + } + + static inline vec not_z(svbool_t pg, vec op1, vec op2) + { + svuint64_t re = svnot_z(pg, svreinterpret_u64(op1.v0), svreinterpret_u64(op2.v0)); + svuint64_t im = svnot_z(pg, svreinterpret_u64(op1.v1), svreinterpret_u64(op2.v1)); + return {svreinterpret_f64(re), svreinterpret_f64(im)}; + } + // vector vector operations static inline vec add_z(svbool_t pg, vec op1, vec op2) { diff --git a/backend/sve/sve_complex_float.hpp b/backend/sve/sve_complex_float.hpp index d14e338..aeef9b1 100644 --- a/backend/sve/sve_complex_float.hpp +++ b/backend/sve/sve_complex_float.hpp @@ -94,6 +94,13 @@ struct svetype<complex<float>>{ return {re, im}; } + static inline vec and_z(svbool_t pg, vec op1, vec op2) + { + svuint32_t re = svand_z(pg, svreinterpret_u32(op1.v0), svreinterpret_u32(op2.v0)); + svuint32_t im = svand_z(pg, svreinterpret_u32(op1.v1), svreinterpret_u32(op2.v1)); + return {svreinterpret_f32(re), svreinterpret_f32(im)}; + } + static inline vec orr_z(svbool_t pg, vec op1, vec op2) { svuint32_t re = svorr_z(pg, svreinterpret_u32(op1.v0), svreinterpret_u32(op2.v0)); @@ -101,6 +108,20 @@ struct svetype<complex<float>>{ return {svreinterpret_f32(re), svreinterpret_f32(im)}; } + static inline vec eor_z(svbool_t pg, vec op1, vec op2) + { + svuint32_t re = sveor_z(pg, svreinterpret_u32(op1.v0), svreinterpret_u32(op2.v0)); + svuint32_t im = sveor_z(pg, svreinterpret_u32(op1.v1), svreinterpret_u32(op2.v1)); + return {svreinterpret_f32(re), svreinterpret_f32(im)}; + } + + static inline vec not_z(svbool_t pg, vec op1, vec op2) + { + svuint32_t re = svnot_z(pg, svreinterpret_u32(op1.v0), svreinterpret_u32(op2.v0)); + svuint32_t im = svnot_z(pg, svreinterpret_u32(op1.v1), svreinterpret_u32(op2.v1)); + return {svreinterpret_f32(re), svreinterpret_f32(im)}; + } + // vector vector operations static inline vec add_z(svbool_t pg, vec op1, vec op2) { diff --git a/backend/sve/sve_double.hpp b/backend/sve/sve_double.hpp index 2040924..a4f9b97 100644 --- a/backend/sve/sve_double.hpp +++ b/backend/sve/sve_double.hpp @@ -78,9 +78,18 @@ struct svetype<double>{ static inline vec conj_z(svbool_t pg, vec op1) { return op1; } + static inline vec and_z(svbool_t pg, vec op1, vec op2) + { return svreinterpret_f64(svand_z(pg, svreinterpret_u64(op1), svreinterpret_u64(op2))); } + static inline vec orr_z(svbool_t pg, vec op1, vec op2) { return svreinterpret_f64(svorr_z(pg, svreinterpret_u64(op1), svreinterpret_u64(op2))); } + static inline vec eor_z(svbool_t pg, vec op1, vec op2) + { return svreinterpret_f64(sveor_z(pg, svreinterpret_u64(op1), svreinterpret_u64(op2))); } + + static inline vec not_z(svbool_t pg, vec op1, vec op2) + { return svreinterpret_f64(svnot_z(pg, svreinterpret_u64(op1), svreinterpret_u64(op2))); } + // vector vector operations static inline vec add_z(svbool_t pg, vec op1, vec op2) { return svadd_f64_z(pg, op1, op2); } diff --git a/backend/sve/sve_float.hpp b/backend/sve/sve_float.hpp index 0694342..525cd43 100644 --- a/backend/sve/sve_float.hpp +++ b/backend/sve/sve_float.hpp @@ -79,9 +79,18 @@ struct svetype<float>{ static inline vec conj_z(svbool_t pg, vec op1) { return op1; } + static inline vec and_z(svbool_t pg, vec op1, vec op2) + { return svreinterpret_f32(svand_z(pg, svreinterpret_u32(op1), svreinterpret_u32(op2))); } + static inline vec orr_z(svbool_t pg, vec op1, vec op2) { return svreinterpret_f32(svorr_z(pg, svreinterpret_u32(op1), svreinterpret_u32(op2))); } + static inline vec eor_z(svbool_t pg, vec op1, vec op2) + { return svreinterpret_f32(sveor_z(pg, svreinterpret_u32(op1), svreinterpret_u32(op2))); } + + static inline vec not_z(svbool_t pg, vec op1, vec op2) + { return svreinterpret_f32(svnot_z(pg, svreinterpret_u32(op1), svreinterpret_u32(op2))); } + // vector vector operations static inline vec add_z(svbool_t pg, vec op1, vec op2) { return svadd_f32_z(pg, op1, op2); } diff --git a/backend/sve/sve_int.hpp b/backend/sve/sve_int.hpp index cf3a5fd..7d3e4a3 100644 --- a/backend/sve/sve_int.hpp +++ b/backend/sve/sve_int.hpp @@ -76,9 +76,18 @@ struct svetype<int>{ static inline vec mulscal_z(svbool_t pg, vec op1, scal op2) { return svmul_n_s32_z(pg, op1, op2); } + static inline vec and_z(svbool_t pg, vec op1, vec op2) + { return svand_z(pg, op1, op2); } + static inline vec orr_z(svbool_t pg, vec op1, vec op2) { return svorr_z(pg, op1, op2); } + static inline vec eor_z(svbool_t pg, vec op1, vec op2) + { return sveor_z(pg, op1, op2); } + + static inline vec not_z(svbool_t pg, vec op1, vec op2) + { return svnot_z(pg, op1, op2); } + // vector vector operations static inline vec add_z(svbool_t pg, vec op1, vec op2) { return svadd_s32_z(pg, op1, op2); } diff --git a/bitwise/generic/gen_bit_ops.hpp b/bitwise/generic/gen_bit_ops.hpp index e6a0bdd..842e1e2 100644 --- a/bitwise/generic/gen_bit_ops.hpp +++ b/bitwise/generic/gen_bit_ops.hpp @@ -40,6 +40,25 @@ #define VECTOR_FOR(i, w, inc) \ for (unsigned int i = 0; i < w; i += inc) +// bitwise and two vectors +struct bitwise_and_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + vector_t<T> vres(a.size); + + std::cout << "[Generic vector (bitwise and) used]" << endl; + + VECTOR_FOR(i, vres.size, 1) + { + vres.elem[i] = a.elem[i] & b.elem[i]; + } + return vres; + } +}; + // bitwise or two vectors struct bitwise_or_vector_vector{ template <typename T> @@ -59,6 +78,63 @@ struct bitwise_or_vector_vector{ } }; +// bitwise xor two vectors +struct bitwise_xor_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + vector_t<T> vres(a.size); + + std::cout << "[Generic vector (bitwise xor) used]" << endl; + + VECTOR_FOR(i, vres.size, 1) + { + vres.elem[i] = a.elem[i] ^ b.elem[i]; + } + return vres; + } +}; + +// bitwise not two vectors +struct bitwise_not_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + vector_t<T> vres(a.size); + + std::cout << "[Generic vector (bitwise not) used]" << endl; + + VECTOR_FOR(i, vres.size, 1) + { + vres.elem[i] = a.elem[i] ~ b.elem[i]; + } + return vres; + } +}; + +// bitwise logical shift left two vectors +struct bitwise_logical_shift_left_vector_vector{ template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const int &b) + { + assert(a.size == b.size); + + vector_t<T> vres(a.size); + + std::cout << "[Generic vector (bitwise not) used]" << endl; + + VECTOR_FOR(i, vres.size, 1) + { + vres.elem[i] = a.elem[i] << b; + } + return vres; + } +}; + + #endif // GEN_BIT_OPS_HPP diff --git a/bitwise/sve/sve_bit_ops.hpp b/bitwise/sve/sve_bit_ops.hpp index c108629..6adad5b 100644 --- a/bitwise/sve/sve_bit_ops.hpp +++ b/bitwise/sve/sve_bit_ops.hpp @@ -37,6 +37,36 @@ #include "sve_bitwise.hpp" +// bitwise_and two vectors +struct bitwise_and_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + int64_t i = 0; + vector_t<T> vres(a.size); + svbool_t ig = svetype<T>::svwhile(i, vres.size); + + std::cout << "[SVE vector (bitwise and) used]" << endl; + do + { + typename svetype<T>::vec x0 = svetype<T>::vload(ig, (const T *) &a.elem[i]); + typename svetype<T>::vec x1 = svetype<T>::vload(ig, (const T *) &b.elem[i]); + + typename svetype<T>::vec res = svetype<T>::and_z(ig, x0, x1); + + svetype<T>::vstore(ig, (T *) &vres.elem[i], res); + + i += svetype<T>::count(); + ig = svetype<T>::svwhile(i, vres.size); + } + while (svptest_any(svetype<T>::svtrue(), ig)); + + return vres; + } +}; + // bitwise_or two vectors struct bitwise_or_vector_vector{ template <typename T> @@ -48,7 +78,7 @@ struct bitwise_or_vector_vector{ vector_t<T> vres(a.size); svbool_t ig = svetype<T>::svwhile(i, vres.size); - std::cout << "[SVE vector (SUM) used]" << endl; + std::cout << "[SVE vector (bitwise or) used]" << endl; do { typename svetype<T>::vec x0 = svetype<T>::vload(ig, (const T *) &a.elem[i]); @@ -67,6 +97,96 @@ struct bitwise_or_vector_vector{ } }; +// bitwise_xor two vectors +struct bitwise_xor_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + int64_t i = 0; + vector_t<T> vres(a.size); + svbool_t ig = svetype<T>::svwhile(i, vres.size); + + std::cout << "[SVE vector (bitwise xor) used]" << endl; + do + { + typename svetype<T>::vec x0 = svetype<T>::vload(ig, (const T *) &a.elem[i]); + typename svetype<T>::vec x1 = svetype<T>::vload(ig, (const T *) &b.elem[i]); + + typename svetype<T>::vec res = svetype<T>::eor_z(ig, x0, x1); + + svetype<T>::vstore(ig, (T *) &vres.elem[i], res); + + i += svetype<T>::count(); + ig = svetype<T>::svwhile(i, vres.size); + } + while (svptest_any(svetype<T>::svtrue(), ig)); + + return vres; + } +}; + +// bitwise_not two vectors +struct bitwise_not_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + int64_t i = 0; + vector_t<T> vres(a.size); + svbool_t ig = svetype<T>::svwhile(i, vres.size); + + std::cout << "[SVE vector (bitwise not) used]" << endl; + do + { + typename svetype<T>::vec x0 = svetype<T>::vload(ig, (const T *) &a.elem[i]); + typename svetype<T>::vec x1 = svetype<T>::vload(ig, (const T *) &b.elem[i]); + + typename svetype<T>::vec res = svetype<T>::not_z(ig, x0, x1); + + svetype<T>::vstore(ig, (T *) &vres.elem[i], res); + + i += svetype<T>::count(); + ig = svetype<T>::svwhile(i, vres.size); + } + while (svptest_any(svetype<T>::svtrue(), ig)); + + return vres; + } +}; + +// bitwise_not two vectors +struct bitwise_not_vector_vector{ + template <typename T> + inline vector_t<T> operator()(const vector_t<T> &a, const vector_t<T> &b) + { + assert(a.size == b.size); + + int64_t i = 0; + vector_t<T> vres(a.size); + svbool_t ig = svetype<T>::svwhile(i, vres.size); + + std::cout << "[SVE vector (bitwise not) used]" << endl; + do + { + typename svetype<T>::vec x0 = svetype<T>::vload(ig, (const T *) &a.elem[i]); + typename svetype<T>::vec x1 = svetype<T>::vload(ig, (const T *) &b.elem[i]); + + typename svetype<T>::vec res = svetype<T>::not_z(ig, x0, x1); + + svetype<T>::vstore(ig, (T *) &vres.elem[i], res); + + i += svetype<T>::count(); + ig = svetype<T>::svwhile(i, vres.size); + } + while (svptest_any(svetype<T>::svtrue(), ig)); + + return vres; + } +}; + #endif #endif // SVE_BIT_OPS_HPP diff --git a/simd.hpp b/simd.hpp index fb0dba3..3bbee82 100644 --- a/simd.hpp +++ b/simd.hpp @@ -72,6 +72,4 @@ using namespace std; #include "vector_overloading.hpp" #include "matrix_overloading.hpp" - - #endif // SIMD_HPP diff --git a/vector_test.x b/vector_test.x new file mode 100755 index 0000000000000000000000000000000000000000..6a93d8592d73df0bc002bce37f23492ae8660a42 GIT binary patch literal 72080 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 YA>}VZ1j3pr2vPX~!iAF%Q8<?Y0B8&1eE<Le literal 0 HcmV?d00001 -- GitLab