9a770ce906e81538253a56ef56be570136d7c783
[alexxy/gromacs.git] / src / gromacs / simd / simd_memory.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \libinternal \file
36  * \brief Declares SimdArrayRef
37  *
38  * \author Roland Schulz <roland.schulz@intel.com>
39  * \inlibraryapi
40  * \ingroup module_simd
41  */
42 #ifndef GMX_SIMD_SIMD_MEMORY_H
43 #define GMX_SIMD_SIMD_MEMORY_H
44
45 #include "gromacs/utility/arrayref.h"
46 #include "gromacs/utility/gmxassert.h"
47
48 namespace gmx
49 {
50 namespace internal
51 {
52
53 template<typename T>
54 class SimdReference
55 {
56 private:
57     using non_const_T = std::remove_const_t<T>;
58     using pointer     = SimdTraitsT<T>*;
59
60 public:
61     //! \brief Constructor
62     explicit SimdReference(pointer m) : m_(m) {}
63     //! \brief Conversion method that will execute load
64     operator non_const_T() const { return load<non_const_T>(m_); }
65     //! \brief Assignment operator that will execute store
66     SimdReference operator=(T o) // NOLINT(misc-unconventional-assign-operator,cppcoreguidelines-c-copy-assignment-signature)
67     {
68         store(m_, o);
69         return *this;
70     }
71     //! \brief Addition assignment operator that will execute load+store
72     SimdReference operator+=(T o)
73     {
74         store(m_, load<non_const_T>(m_) + o);
75         return *this;
76     }
77     //! \brief Subtraction assignment operator that will execute load+store
78     SimdReference operator-=(T o)
79     {
80         store(m_, load<non_const_T>(m_) - o);
81         return *this;
82     }
83     //! \brief Multiplication assignment operator that will execute load+store
84     SimdReference operator*=(T o)
85     {
86         store(m_, load<non_const_T>(m_) * o);
87         return *this;
88     }
89
90 private:
91     pointer const m_; //!< The pointer used to load memory
92 };
93
94 template<typename T>
95 class SimdIterator :
96     public boost::stl_interfaces::iterator_interface<SimdIterator<T>, std::random_access_iterator_tag, T, SimdReference<T>>
97 {
98     using Base =
99             boost::stl_interfaces::iterator_interface<SimdIterator<T>, std::random_access_iterator_tag, T, SimdReference<T>>;
100     // pointer is T*
101     using DataPointer = SimdTraitsT<T>*;
102
103 public:
104     explicit SimdIterator(DataPointer p = 0) : p_(p)
105     {
106         GMX_ASSERT((reinterpret_cast<size_t>(p) / sizeof(*p)) % simdWidth == 0,
107                    "Trying to create aligned iterator for non aligned address.");
108     }
109     SimdIterator& operator+=(typename Base::difference_type d)
110     {
111         p_ += simdWidth * d;
112         return *this;
113     }
114     typename Base::difference_type operator-(SimdIterator o) { return (p_ - o.p_) / simdWidth; }
115     typename Base::reference       operator*() const { return typename Base::reference(p_); }
116
117 private:
118     DataPointer          p_;
119     static constexpr int simdWidth = SimdTraits<T>::width;
120 };
121
122 /*! \internal
123  * \brief STL-like container for aligned SIMD type. Used as ArrayRef<SimdReal>.
124  *
125  * Should provide the same interface as ArrayRef. Any missing functions should be
126  * added as needed. The pointer type (used e.g. for initialization) is a real
127  * pointer. The reference type (used e.g. for operator[] and iterator dereference)
128  * is SimdReference which executes the aligned load/store as is appropriate. For
129  * both iterator and element access, the access happens in blocks of SIMD width.
130  * Meaning that a[1] refers to the 2nd SIMD vector and thus reals 8-15 for 8-wide
131  * SIMD. The starting address has to be aligned and the length has to be multiple
132  * of the SIMD width.
133  *
134  * \tparam T SIMD type (e.g. SimdReal)
135  */
136 template<typename T>
137 class SimdArrayRef
138 {
139 public:
140     //! Type for representing size of the container.
141     using size_type = size_t;
142     //! Type for representing difference between two container indices.
143     using difference_type = std::ptrdiff_t;
144     //! Type of values stored in the container.
145     using value_type = T;
146     //! Pointer to a container element.
147     using pointer = SimdTraitsT<T>*;
148     //! Reference to a container element.
149     using reference = internal::SimdReference<T>;
150     //! Iterator type for the container.
151     using iterator = SimdIterator<T>;
152     //! Standard reverse iterator.
153     using reverse_iterator = std::reverse_iterator<iterator>;
154
155     //! \copydoc ArrayRef::ArrayRef(pointer, pointer)
156     SimdArrayRef(pointer begin, pointer end) : begin_(begin), end_(end)
157     {
158         GMX_ASSERT(end >= begin, "Invalid range");
159         GMX_ASSERT((reinterpret_cast<size_type>(begin) / sizeof(*begin)) % simdWidth == 0,
160                    "Aligned ArrayRef requires aligned starting address");
161         GMX_ASSERT((reinterpret_cast<size_type>(end) / sizeof(*end)) % simdWidth == 0,
162                    "Size of ArrayRef needs to be divisible by type size");
163     }
164     //! \copydoc ArrayRef::ArrayRef(U)
165     template<typename U, typename = std::enable_if_t<std::is_convertible_v<typename std::remove_reference_t<U>::pointer, pointer>>>
166     SimdArrayRef(U&& o) :
167         begin_(reinterpret_cast<pointer>(o.data())),
168         end_(reinterpret_cast<pointer>(o.data() + o.size()))
169     {
170     }
171     // reinterpret_cast is only needed for const conversion of SimdArrayRef itself.
172     // All other containers have type(o.data())==U::pointer (the cast does nothing).
173
174     //! Returns the size of the container.
175     size_type size() const { return (end_ - begin_) / simdWidth; }
176     //! Whether the container is empty.
177     bool empty() const { return begin_ == end_; }
178     //! Returns an iterator to the beginning of the container.
179     iterator begin() const { return iterator(begin_); }
180     //! Returns an iterator to the end of the container.
181     iterator end() const { return iterator(end_); }
182
183     //! Access container element.
184     reference operator[](size_type n) { return reference(begin_ + n * simdWidth); }
185
186     //! Returns the first element in the container.
187     reference front() const { return reference(begin_); }
188     //! Returns the first element in the container.
189     reference back() const { return reference(end_ - simdWidth); }
190
191 private:
192     static constexpr int simdWidth = SimdTraits<T>::width;
193     using pack_type                = SimdTraitsT<T>[simdWidth];
194     // Private because dereferencing return value is undefined behavior (strict aliasing rule)
195     // Only use is conversion constructor above which immediately casts it back.
196     // Return type is not "pointer" because then data()+size() would be ill defined.
197     // Has to be pack_type and not value_type in case
198     // sizeof(value_type)/sizeof(pointer)!=simdWidth (e.g. int32 for double SSE2).
199     pack_type* data() const { return reinterpret_cast<pack_type*>(begin_); }
200
201     template<typename U>
202     friend class SimdArrayRef;
203
204     pointer const begin_;
205     pointer const end_;
206 };
207
208 } // namespace internal
209
210 /* Specialize ArraryRef<SimdReal>
211  * So far only an aligned version is implemented. The constructor verifies that
212  * a ArrayRef<SimdReal> is constructed only for properly aligned data.
213  */
214 #if GMX_SIMD_HAVE_FLOAT
215 template<>
216 class ArrayRef<SimdFloat> : public internal::SimdArrayRef<SimdFloat>
217 {
218     using Base = internal::SimdArrayRef<SimdFloat>;
219     using Base::Base;
220 };
221 template<>
222 class ArrayRef<const SimdFloat> : public internal::SimdArrayRef<const SimdFloat>
223 {
224     using Base = internal::SimdArrayRef<const SimdFloat>;
225     using Base::Base;
226 };
227 #endif
228 #if GMX_SIMD_HAVE_DOUBLE
229 template<>
230 class ArrayRef<SimdDouble> : public internal::SimdArrayRef<SimdDouble>
231 {
232     using Base = internal::SimdArrayRef<SimdDouble>;
233     using Base::Base;
234 };
235 template<>
236 class ArrayRef<const SimdDouble> : public internal::SimdArrayRef<const SimdDouble>
237 {
238     using Base = internal::SimdArrayRef<const SimdDouble>;
239     using Base::Base;
240 };
241 #endif
242
243 } // namespace gmx
244
245 #endif