From a69a371c16925c1c671e8c9e8f62ca6f31872004 Mon Sep 17 00:00:00 2001 From: "Brendan K. Krueger" Date: Mon, 26 Aug 2024 17:03:26 -0600 Subject: [PATCH 1/6] Empty placeholder file for static_vector.hpp (to be transferred over from Singe). --- ports-of-call/static_vector.hpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ports-of-call/static_vector.hpp diff --git a/ports-of-call/static_vector.hpp b/ports-of-call/static_vector.hpp new file mode 100644 index 00000000..e69de29b From 8c6a6e2cc0bbba672a2a5e4cc326ebb9854dd219 Mon Sep 17 00:00:00 2001 From: "Brendan K. Krueger" Date: Wed, 2 Oct 2024 16:34:24 -0600 Subject: [PATCH 2/6] Copy files over from Singe. --- ports-of-call/static_vector.hpp | 436 +++++++++++++++++++++++++++ test/CMakeLists.txt | 9 +- test/test_static_vector.cpp | 211 +++++++++++++ test/test_static_vector_iterator.cpp | 230 ++++++++++++++ test/test_static_vector_kokkos.cpp | 113 +++++++ 5 files changed, 998 insertions(+), 1 deletion(-) create mode 100644 test/test_static_vector.cpp create mode 100644 test/test_static_vector_iterator.cpp create mode 100644 test/test_static_vector_kokkos.cpp diff --git a/ports-of-call/static_vector.hpp b/ports-of-call/static_vector.hpp index e69de29b..396e849b 100644 --- a/ports-of-call/static_vector.hpp +++ b/ports-of-call/static_vector.hpp @@ -0,0 +1,436 @@ +#ifndef _PORTS_OF_CALL_STATIC_VECTOR_HPP_ +#define _PORTS_OF_CALL_STATIC_VECTOR_HPP_ + +#include "ports-of-call/portability.hpp" + +#include +#include +#include +#include + +namespace PortsOfCall { + +/* The static_vector has an interface modeled after std::vector, but it uses + * statically-allocated memory to (a) avoid dynamic memory allocation, deallocation, or + * reallocation; and (b) ensure that the data is contained entirely inside the + * static_vector so that this type can be memcopied to a CPU. We had an initial + * implementation that provided additional features, but was not portable to GPUs. This + * implementation is simpler and less feature-rich, but works on GPUs. Tests have been + * added to the test suite to ensure that this is true and to enforce that it continues to + * be true. Features that are missing: + * -- The insert, emplace, and erase methods from std::vector were included in the initial + * implementation, but have not been included in this implementation. These methods + * could likely be added to this implementation if needed. + * -- The initial implementation could handle a size of N = 0, while this implementation + * will fail to compile if the size is zero. Support for zero-size static_vector could + * likely be added to this implementation if needed. + * -- The initial implementation propagated triviality. If the type held by the + * static_vector (Element) is trivially-destructible, then the initial implementation of + * static_vector would also be trivially-destructible. We have not yet been able to + * reproduce this behavior in a GPU-compatible way, so the new implementation of + * static_vector is never trivially-destructible. + * -- The initial implementation could adapt to holding types that are not copyable and/or + * not movable. We have not yet been able to reproduce this behavior in a GPU-compatible + * way, so the new implementation assumes that objects of type Element are both copyable + * and movable. + * -- The initial implementation was able to perform some optimizations on triviality of + * the Element type. We have not yet been able to reproduce this behavior in a + * GPU-compatible way, so the new implementation may not perform as fast in some cases. + */ + +template +class static_vector { + public: + // iterator stolen from CJ's static_vector + // ---------------------------------------------------- + template + class iterator_type { + friend class static_vector; + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = Element_; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + using const_iterator_type = iterator_type; + + PORTABLE_FUNCTION constexpr iterator_type() : it_{nullptr} {} + + PORTABLE_FUNCTION constexpr iterator_type(Element_ *it) : it_{it} {} + + // const iterator from non-const iterator + template ::value> * = nullptr> + PORTABLE_FUNCTION constexpr iterator_type(iterator_type> it) + : it_{it.it_} {} + + private: + Element_ *it_; + + // non-const iterator from const iterator...generally dangerous + // (used for convinience in static_vector implementation) + template ::value> * = nullptr> + PORTABLE_FUNCTION constexpr iterator_type(iterator_type> it) + : it_{const_cast(it.it_)} {} + + public: + PORTABLE_FUNCTION constexpr iterator_type &operator++() { + ++(this->it_); + return *this; + } + + PORTABLE_FUNCTION constexpr iterator_type operator++(int) const { + iterator_type temp{*this}; + ++temp; + return temp; + } + + PORTABLE_FUNCTION constexpr iterator_type &operator--() { + --(this->it_); + return *this; + } + + PORTABLE_FUNCTION constexpr iterator_type operator--(int) const { + iterator_type temp{*this}; + --temp; + return temp; + } + + PORTABLE_FUNCTION constexpr iterator_type &operator+=(difference_type m) { + this->it_ += m; + return *this; + } + + PORTABLE_FUNCTION constexpr iterator_type &operator-=(difference_type m) { + return *this += -m; + } + + PORTABLE_FUNCTION constexpr iterator_type operator+(difference_type m) const { + iterator_type temp{*this}; + return temp += m; + } + + PORTABLE_FUNCTION constexpr iterator_type operator-(difference_type m) const { + iterator_type temp{*this}; + return temp -= m; + } + + PORTABLE_FUNCTION constexpr difference_type + operator-(const_iterator_type other) const { + return this->it_ - other.it_; + } + + PORTABLE_FUNCTION constexpr bool operator==(const_iterator_type other) const { + return this->it_ == other.it_; + } + + PORTABLE_FUNCTION constexpr bool operator!=(const_iterator_type other) const { + return not(this->it_ == other.it_); + } + + PORTABLE_FUNCTION constexpr bool operator<(const_iterator_type other) const { + return this->it_ < other.it_; + } + + PORTABLE_FUNCTION constexpr bool operator>(const_iterator_type other) const { + return other < *this; + } + + PORTABLE_FUNCTION constexpr bool operator>=(const_iterator_type other) const { + return not(*this < other); + } + + PORTABLE_FUNCTION constexpr bool operator<=(const_iterator_type other) const { + return not(*this > other); + } + + PORTABLE_FUNCTION constexpr reference operator*() const { return *(this->it_); } + + PORTABLE_FUNCTION constexpr pointer operator->() const { return this->it_; } + + PORTABLE_FUNCTION constexpr reference operator[](difference_type n) const { + return *(this->it_ + n); + } + + template ::value> * = nullptr> + PORTABLE_FUNCTION friend constexpr void advance(iterator_type &it, IntT n) { + it += n; + } + + PORTABLE_FUNCTION friend constexpr iterator_type next(iterator_type it, + difference_type n) { + advance(it, n); + return it; + } + + PORTABLE_FUNCTION friend constexpr iterator_type prev(iterator_type it, + difference_type n) { + advance(it, -n); + return it; + } + + PORTABLE_FUNCTION friend constexpr iterator_type::difference_type + distance(iterator_type first, iterator_type last) { + return last - first; + } + }; + // end of iterator + // ---------------------------------------------------------------------------- + + public: + using value_type = Element; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = Element &; + using const_reference = Element const &; + using pointer = Element *; + using const_pointer = Element const *; + using iterator = iterator_type; + using const_iterator = iterator_type; + + private: + size_type count_ = 0; + using byte = unsigned char; + byte storage_[sizeof(Element) * N]; + PORTABLE_FUNCTION constexpr pointer ptr() { + return reinterpret_cast(storage_); + } + PORTABLE_FUNCTION constexpr pointer ptr(size_type const i) { return ptr() + i; } + PORTABLE_FUNCTION constexpr reference ref() { return *ptr(); } + PORTABLE_FUNCTION constexpr reference ref(size_type const i) { return *ptr(i); } + PORTABLE_FUNCTION constexpr const_pointer ptr() const { + return reinterpret_cast(storage_); + } + PORTABLE_FUNCTION constexpr const_pointer ptr(size_type const i) const { + return ptr() + i; + } + PORTABLE_FUNCTION constexpr const_reference ref() const { return *ptr(); } + PORTABLE_FUNCTION constexpr const_reference ref(size_type const i) const { + return *ptr(i); + } + + public: + // constructor + // -------------------------------------------------------------------------------- + // PORTABLE_FUNCTION constexpr static_vector() = default; + PORTABLE_FUNCTION constexpr static_vector() {} + + PORTABLE_FUNCTION constexpr static_vector(std::initializer_list const &list) { + assert(list.size() <= N); + for (auto &element : list) { + push_back(element); + } + } + + PORTABLE_FUNCTION constexpr static_vector(std::initializer_list &&list) { + assert(list.size() <= N); + for (auto &&element : list) { + push_back(element); + } + } + + // copy constructor + // --------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr static_vector(static_vector const &other) { + clear(); + for (auto const &element : other) { + push_back(element); + } + } + + // move constructor + // --------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr static_vector(static_vector &&other) noexcept { + clear(); + for (auto &&element : other) { + push_back(element); + } + } + + // copy assignment + // ---------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr static_vector &operator=(static_vector const &other) { + clear(); + for (auto const &element : other) { + push_back(element); + } + return *this; + } + + // move assignment + // ---------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr static_vector &operator=(static_vector &&other) noexcept { + clear(); + for (auto &&element : other) { + push_back(element); + } + return *this; + } + + // destructor + // --------------------------------------------------------------------------------- + PORTABLE_FUNCTION ~static_vector() { clear(); } + + // operator[] + // --------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr reference operator[](size_type const i) & { return ref(i); } + + PORTABLE_FUNCTION constexpr const_reference operator[](size_type const i) const & { + return ref(i); + } + + PORTABLE_FUNCTION constexpr Element &&operator[](size_type const i) && { + return std::move(ref(i)); + } + + PORTABLE_FUNCTION constexpr Element const &&operator[](size_type const i) const && { + return std::move(ref(i)); + } + + // front + // -------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr reference front() & { return ref(); } + + PORTABLE_FUNCTION constexpr const_reference front() const & { return ref(); } + + PORTABLE_FUNCTION constexpr Element &&front() && { return ref(); } + + PORTABLE_FUNCTION constexpr Element const &&front() const && { return ref(); } + + // back + // --------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr reference back() & { return ref(count_ - 1); } + + PORTABLE_FUNCTION constexpr const_reference back() const & { return ref(count_ - 1); } + + PORTABLE_FUNCTION constexpr Element &&back() && { return ref(count_ - 1); } + + PORTABLE_FUNCTION constexpr Element const &&back() const && { return ref(count_ - 1); } + + // data + // --------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr pointer data() { return ptr(); } + + PORTABLE_FUNCTION constexpr const_pointer data() const { return ptr(); } + + // begin + // -------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr iterator begin() { return {ptr()}; } + + PORTABLE_FUNCTION constexpr const_iterator begin() const { return {ptr()}; } + + PORTABLE_FUNCTION constexpr const_iterator cbegin() const { return {ptr()}; } + + // end + // ---------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr iterator end() { return next(begin(), count_); } + + PORTABLE_FUNCTION constexpr const_iterator end() const { return next(begin(), count_); } + + PORTABLE_FUNCTION constexpr const_iterator cend() const { + return next(cbegin(), count_); + } + + // empty + // -------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr bool empty() const { return count_ == 0; } + + // size + // --------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr size_type size() const { return count_; } + + // max_size + // ----------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr size_type max_size() const { return N; } + + // capacity + // ----------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr size_type capacity() const { return max_size(); } + + // clear + // -------------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr void clear() noexcept { + for (size_type i = 0; i < count_; ++i) { + ptr(i)->~value_type(); + } + count_ = 0; + } + + // insert + // ------------------------------------------------------------------------------------- + // TODO: In order to simplify, I removed all versions of insert for now. + + // emplace + // ------------------------------------------------------------------------------------ + // TODO: In order to simplify, I removed all versions of emplace for now. + + // erase + // -------------------------------------------------------------------------------------- + // TODO: In order to simplify, I removed all versions of erase for now. + + // push_back + // ---------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr void push_back(value_type const &value) { + assert(count_ < N); + new (ptr(count_)) value_type{value}; + ++count_; + } + + PORTABLE_FUNCTION constexpr void push_back(value_type &&value) { + assert(count_ < N); + new (ptr(count_)) value_type{std::move(value)}; + ++count_; + } + + // emplace_back + // ------------------------------------------------------------------------------- + template + PORTABLE_FUNCTION constexpr reference emplace_back(Args &&...args) { + assert(count_ < N); + new (ptr(count_)) value_type(std::forward(args)...); + ++count_; + return back(); + } + + // pop_back + // ----------------------------------------------------------------------------------- + PORTABLE_FUNCTION constexpr void pop_back() { + if (count_ > 0) { + ptr(count_ - 1)->~value_type(); + --count_; + } + } + + // free function begin + // ------------------------------------------------------------------------ + PORTABLE_FUNCTION friend constexpr iterator begin(static_vector &sv) { + return sv.begin(); + } + + PORTABLE_FUNCTION friend constexpr const_iterator begin(static_vector const &sv) { + return sv.begin(); + } + + PORTABLE_FUNCTION friend constexpr const_iterator cbegin(static_vector const &sv) { + return sv.cbegin(); + } + + // free function end + // -------------------------------------------------------------------------- + PORTABLE_FUNCTION friend constexpr iterator end(static_vector &sv) { return sv.end(); } + + PORTABLE_FUNCTION friend constexpr const_iterator end(static_vector const &sv) { + return sv.end(); + } + + PORTABLE_FUNCTION friend constexpr const_iterator cend(static_vector const &sv) { + return sv.cend(); + } +}; + +} // namespace PortsOfCall + +#endif // ifndef _PORTS_OF_CALL_STATIC_VECTOR_HPP_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bd69b4ae..c9acf348 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -64,4 +64,11 @@ target_link_libraries(test_portsofcall include(Catch) catch_discover_tests(test_portsofcall) -target_sources(test_portsofcall PRIVATE test_portability.cpp test_array.cpp) +target_sources(test_portsofcall + PRIVATE + test_portability.cpp + test_array.cpp + test_static_vector.cpp + test_static_vector_iterator.cpp + "$<$:test_static_vector_kokkos.cpp>" +) diff --git a/test/test_static_vector.cpp b/test/test_static_vector.cpp new file mode 100644 index 00000000..88548b27 --- /dev/null +++ b/test/test_static_vector.cpp @@ -0,0 +1,211 @@ +#include "ports-of-call/portability.hpp" +#include "ports-of-call/static_vector.hpp" + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +#include +#include + +namespace static_vector_test { + +template +constexpr static bool really_const = std::is_const>::value; +} + +TEST_CASE("static_vector", "[util][static_vector]") { + using std::begin; + using std::cbegin; + using std::cend; + using std::end; + + using static_vector_test::really_const; + + SECTION("begin/end iteration") { + SECTION("with non-zero size") { + using test_t = PortsOfCall::static_vector; + test_t data = {1, 2, 3}; + + auto b = begin(data); + auto e = end(data); + + CHECK(std::distance(b, e) == 3); + CHECK(b + 3 == e); + CHECK(not really_const); + CHECK(not really_const); + + auto cb = cbegin(data); + auto ce = cend(data); + + CHECK(really_const); + CHECK(really_const); + } + } + + SECTION("operator[]") { + SECTION("with non-zero size") { + using test_t = PortsOfCall::static_vector; + + SECTION("non-const data") { + test_t data = {1, 2, 3}; + + for (int i = 0; i < 3; ++i) { + CHECK(data[i] == i + 1); + } + CHECK(not really_const); + } + + SECTION("with const data") { + test_t const data = {1, 2, 3}; + + for (int i = 0; i < 3; ++i) { + CHECK(data[i] == i + 1); + } + CHECK(really_const); + } + } + } + + SECTION("front/back") { + using test_t = PortsOfCall::static_vector; + + SECTION("non-const data") { + test_t data = {1, 2, 3}; + + CHECK(data.front() == 1); + CHECK(data.back() == 3); + CHECK(not really_const); + CHECK(not really_const); + } + + SECTION("with const data") { + test_t const data = {1, 2, 3}; + + CHECK(data.front() == 1); + CHECK(data.back() == 3); + CHECK(really_const); + CHECK(really_const); + } + } + + SECTION("data") { + SECTION("with non-zero size") { + using test_t = PortsOfCall::static_vector; + + SECTION("non-const data") { + test_t data = {1, 2, 3}; + + CHECK(data.data() == std::addressof(data[0])); + } + + SECTION("const data") { + test_t const data = {1, 2, 3}; + + CHECK(data.data() == std::addressof(data[0])); + } + } + } + + SECTION("empty/size/max_size/capacity") { + SECTION("with non-zero size") { + using test_t = PortsOfCall::static_vector; + + test_t const data1 = {}; + + CHECK(data1.empty()); + CHECK(data1.size() == 0); + CHECK(data1.max_size() == 5); + CHECK(data1.capacity() == 5); + + test_t const data2 = {1, 2, 3}; + + CHECK(not data2.empty()); + CHECK(data2.size() == 3); + CHECK(data2.max_size() == 5); + CHECK(data2.capacity() == 5); + } + } + + SECTION("clear") { + SECTION("with trivial type") { + using test_t = PortsOfCall::static_vector; + + test_t data = {1, 2, 3}; + CHECK(data.size() == 3); + data.clear(); + CHECK(data.size() == 0); + } + + SECTION("with non-trivial type") { + using test_t = PortsOfCall::static_vector, 5>; + + test_t data = {std::vector(2), std::vector(4), + std::vector(6)}; + CHECK(data.size() == 3); + data.clear(); + CHECK(data.size() == 0); + } + } + + SECTION("push_back/emplace_back") { + using test_t = PortsOfCall::static_vector, 5>; + + test_t data; + + auto insert = std::vector(1); + data.push_back(insert); + + // Note: Some compilers struggle to parse the Catch2 macros, and will generate errors + // if you declare a lambda inside a CHECK macro. Therefore we move the line with the + // lambda outside of the CHECK. + auto expected1 = {std::vector(1)}; + auto size_check1 = + std::equal(begin(data), end(data), begin(expected1), end(expected1), + [](auto const &l, auto const &r) { return l.size() == r.size(); }); + CHECK(size_check1); + + data.push_back(std::vector(2)); + auto expected2 = {std::vector(1), std::vector(2)}; + auto size_check2 = + std::equal(begin(data), end(data), begin(expected2), end(expected2), + [](auto const &l, auto const &r) { return l.size() == r.size(); }); + CHECK(size_check2); + + data.emplace_back(3); + auto expected3 = {std::vector(1), std::vector(2), + std::vector(3)}; + auto size_check3 = + std::equal(begin(data), end(data), begin(expected3), end(expected3), + [](auto const &l, auto const &r) { return l.size() == r.size(); }); + CHECK(size_check3); + } + + SECTION("pop_back") { + SECTION("with trivial type") { + using test_t = PortsOfCall::static_vector; + test_t data = {1, 2, 3, 4, 5}; + + data.pop_back(); + test_t const expected = {1, 2, 3, 4}; + CHECK(std::equal(begin(data), end(data), begin(expected))); + } + + SECTION("with non-trivial type") { + using test_t = PortsOfCall::static_vector, 5>; + test_t data = {std::vector(1), std::vector(2), + std::vector(3), std::vector(4), + std::vector(5)}; + + data.pop_back(); + test_t const expected = {std::vector(1), std::vector(2), + std::vector(3), std::vector(4)}; + + auto size_check = + std::equal(begin(data), end(data), begin(expected), end(expected), + [](auto const &l, auto const &r) { return l.size() == r.size(); }); + CHECK(size_check); + } + } +} diff --git a/test/test_static_vector_iterator.cpp b/test/test_static_vector_iterator.cpp new file mode 100644 index 00000000..478a1caa --- /dev/null +++ b/test/test_static_vector_iterator.cpp @@ -0,0 +1,230 @@ +#include "ports-of-call/portability.hpp" +#include "ports-of-call/static_vector.hpp" + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +#include +#include +#include + +namespace static_vector_iterator_test { + +template +constexpr static bool really_const = std::is_const>::value; +} + +TEST_CASE("static_vector iterator", "[util][static_vector]") { + using std::begin; + using std::cbegin; + using std::cend; + using std::end; + + using PortsOfCall::static_vector; + + SECTION("non-const iterator") { + static_vector sv{1, 2, 3}; + auto it = begin(sv); + auto end_it = end(sv); + + SECTION("construct const iterator") { + using static_vector_iterator_test::really_const; + decltype(cbegin(sv)) const_it{it}; + REQUIRE(really_const); + } + + SECTION("operator++ and mutate") { + while (it != end_it) { + *it *= 2; + ++it; + } + std::array expected{{2, 4, 6}}; + REQUIRE(std::equal(begin(sv), end(sv), begin(expected))); + } + + SECTION("operator++(int)") { + auto next_it = it++; + REQUIRE(*next_it == 2); + } + + SECTION("operator--") { + auto next_it = it++; + --next_it; + REQUIRE(*next_it == 1); + } + + SECTION("operator--(int)") { + auto next_it = it++; + auto prev_it = next_it--; + REQUIRE(*prev_it == 1); + } + + SECTION("operator+=") { + it += 2; + REQUIRE(*it == 3); + } + + SECTION("operator+") { + auto next_it = it + 2; + REQUIRE(*next_it == 3); + } + + SECTION("operator-=") { + end_it -= 2; + REQUIRE(*end_it == 2); + } + + SECTION("operator-") { + auto prev_it = end_it - 2; + REQUIRE(*prev_it == 2); + } + + SECTION("difference operator-") { REQUIRE(end_it - it == 3); } + + SECTION("operator==") { REQUIRE(it + 3 == end_it); } + + SECTION("operator!=") { REQUIRE(it != end_it); } + + SECTION("operator<") { + REQUIRE(it < end_it); + REQUIRE(not(it + 3 < end_it)); + } + + SECTION("operator<=") { + REQUIRE(it <= end_it); + REQUIRE(it + 3 <= end_it); + } + + SECTION("operator>") { + REQUIRE(end_it > it); + REQUIRE(not(end_it > it + 3)); + } + + SECTION("operator>=") { + REQUIRE(end_it >= it); + REQUIRE(end_it >= it + 3); + } + + SECTION("operator->") { + REQUIRE(std::is_same()), int *>::value); + } + + SECTION("operator[]") { REQUIRE(it[1] == 2); } + + SECTION("advance") { + advance(it, 2); + REQUIRE(*it == 3); + } + + SECTION("next") { + auto next_it = next(it, 2); + REQUIRE(*next_it == 3); + } + + SECTION("prev") { + auto prev_it = prev(end_it, 2); + REQUIRE(*prev_it == 2); + } + + SECTION("distance") { REQUIRE(distance(it, end_it) == 3); } + } + + SECTION("const iterator") { + static_vector sv{1, 2, 3}; + auto it = cbegin(sv); + auto end_it = cend(sv); + + SECTION("operator++ and mutate") { + ++it; + REQUIRE(*it == 2); + } + + SECTION("operator++(int)") { + auto next_it = it++; + REQUIRE(*next_it == 2); + } + + SECTION("operator--") { + auto next_it = it++; + --next_it; + REQUIRE(*next_it == 1); + } + + SECTION("operator--(int)") { + auto next_it = it++; + auto prev_it = next_it--; + REQUIRE(*prev_it == 1); + } + + SECTION("operator+=") { + it += 2; + REQUIRE(*it == 3); + } + + SECTION("operator+") { + auto next_it = it + 2; + REQUIRE(*next_it == 3); + } + + SECTION("operator-=") { + end_it -= 2; + REQUIRE(*end_it == 2); + } + + SECTION("operator-") { + auto prev_it = end_it - 2; + REQUIRE(*prev_it == 2); + } + + SECTION("difference operator-") { REQUIRE(end_it - it == 3); } + + SECTION("operator==") { REQUIRE(it + 3 == end_it); } + + SECTION("operator!=") { REQUIRE(it != end_it); } + + SECTION("operator<") { + REQUIRE(it < end_it); + REQUIRE(not(it + 3 < end_it)); + } + + SECTION("operator<=") { + REQUIRE(it <= end_it); + REQUIRE(it + 3 <= end_it); + } + + SECTION("operator>") { + REQUIRE(end_it > it); + REQUIRE(not(end_it > it + 3)); + } + + SECTION("operator>=") { + REQUIRE(end_it >= it); + REQUIRE(end_it >= it + 3); + } + + SECTION("operator->") { + REQUIRE(std::is_same()), int const *>::value); + } + + SECTION("operator[]") { REQUIRE(it[1] == 2); } + + SECTION("advance") { + advance(it, 2); + REQUIRE(*it == 3); + } + + SECTION("next") { + auto next_it = next(it, 2); + REQUIRE(*next_it == 3); + } + + SECTION("prev") { + auto prev_it = prev(end_it, 2); + REQUIRE(*prev_it == 2); + } + + SECTION("distance") { REQUIRE(distance(it, end_it) == 3); } + } +} diff --git a/test/test_static_vector_kokkos.cpp b/test/test_static_vector_kokkos.cpp new file mode 100644 index 00000000..9957f2c4 --- /dev/null +++ b/test/test_static_vector_kokkos.cpp @@ -0,0 +1,113 @@ +#include "ports-of-call/portability.hpp" +#include "ports-of-call/static_vector.hpp" + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +#include + +#include + +// ================================================================================================ + +template +PORTABLE_FUNCTION void print(PortsOfCall::static_vector const &sv) { + switch (sv.size()) { + case 0: + printf("[%s] {}\n", Space::name()); + break; + case 1: + printf("[%s] {%d}\n", Space::name(), sv[0]); + break; + case 2: + printf("[%s] {%d,%d}\n", Space::name(), sv[0], sv[1]); + break; + case 3: + printf("[%s] {%d,%d,%d}\n", Space::name(), sv[0], sv[1], sv[2]); + break; + case 4: + printf("[%s] {%d,%d,%d,%d}\n", Space::name(), sv[0], sv[1], sv[2], sv[3]); + break; + case 5: + printf("[%s] {%d,%d,%d,%d,%d}\n", Space::name(), sv[0], sv[1], sv[2], sv[3], sv[4]); + break; + case 6: + printf("[%s] {%d,%d,%d,%d,%d,%d}\n", Space::name(), sv[0], sv[1], sv[2], sv[3], sv[4], + sv[5]); + break; + default: + printf("[%s] {%d,%d,...,%d,%d} (size=%d)\n", Space::name(), sv[0], sv[1], + sv[sv.size() - 2], sv[sv.size() - 1], static_cast(sv.size())); + break; + } +} + +// ================================================================================================ + +template +PORTABLE_FUNCTION void do_stuff(int const n, + PortsOfCall::static_vector &sv) { + printf("[%s] start\n", Space::name()); + print(sv); + sv.push_back(n + 0); + print(sv); + sv.push_back(n + 1); + print(sv); + sv.push_back(n + 2); + print(sv); +} + +// ================================================================================================ + +TEST_CASE("static vector GPU", "[reaction_network]") { + std::cout << "start" << std::endl; + + using HostSpace = Kokkos::HostSpace; + using ExecSpace = Kokkos::DefaultExecutionSpace::memory_space; + + constexpr int N{10}; + + std::cout << "declare sv_list_c" << std::endl; + Kokkos::View *, HostSpace> sv_list_c("sv_list", + N); + + std::cout << "print sv_list_c" << std::endl; + for (int n{0}; n < N; ++n) { + for (int i{0}; i < n; ++i) { + sv_list_c(n).push_back(i); + } + print(sv_list_c(n)); + } + + std::cout << "build GPU mirror (sv_list_g)" << std::endl; + auto sv_list_g = Kokkos::create_mirror_view_and_copy(ExecSpace(), sv_list_c); + + std::cout << "run on the GPU" << std::endl; + Kokkos::fence(); // ensure the data has arrived on the GPU + Kokkos::parallel_for( + N, KOKKOS_LAMBDA(int const n) { do_stuff(n, sv_list_g(n)); }); + + std::cout << "copy back to the CPU" << std::endl; + Kokkos::fence(); // ensure the calculation is complete + Kokkos::deep_copy(sv_list_c, sv_list_g); + + std::cout << "print again" << std::endl; + Kokkos::fence(); // ensure the data has arrived back on the CPU + for (int n{0}; n < N; ++n) { + print(sv_list_c(n)); + } + + std::cout << "check results" << std::endl; + for (int n{0}; n < N; ++n) { + REQUIRE(sv_list_c(n).size() == static_cast(n + 3)); + for (std::size_t i{0}; i < sv_list_c(n).size(); ++i) { + CHECK(sv_list_c(n)[i] == static_cast(i)); + } + } + + std::cout << "done" << std::endl; +} + +// ================================================================================================ From 5274205f6dbe45b897573cb5c2914d339c1540da Mon Sep 17 00:00:00 2001 From: "Brendan K. Krueger" Date: Wed, 2 Oct 2024 16:11:34 -0600 Subject: [PATCH 3/6] Remove TODO notes --- ports-of-call/static_vector.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ports-of-call/static_vector.hpp b/ports-of-call/static_vector.hpp index 396e849b..f4fbd382 100644 --- a/ports-of-call/static_vector.hpp +++ b/ports-of-call/static_vector.hpp @@ -359,18 +359,6 @@ class static_vector { count_ = 0; } - // insert - // ------------------------------------------------------------------------------------- - // TODO: In order to simplify, I removed all versions of insert for now. - - // emplace - // ------------------------------------------------------------------------------------ - // TODO: In order to simplify, I removed all versions of emplace for now. - - // erase - // -------------------------------------------------------------------------------------- - // TODO: In order to simplify, I removed all versions of erase for now. - // push_back // ---------------------------------------------------------------------------------- PORTABLE_FUNCTION constexpr void push_back(value_type const &value) { From 5ee2d7c6c6d73aeea46baf41529fca7fa23ae92c Mon Sep 17 00:00:00 2001 From: "Brendan K. Krueger" Date: Wed, 2 Oct 2024 16:20:34 -0600 Subject: [PATCH 4/6] Add static_vector to documentation. --- doc/sphinx/src/using.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/sphinx/src/using.rst b/doc/sphinx/src/using.rst index 40a9d7a5..26de496f 100644 --- a/doc/sphinx/src/using.rst +++ b/doc/sphinx/src/using.rst @@ -230,3 +230,14 @@ not yet ``constexpr``, so even with the "relaxed ``constexpr``" compilation mode not feature-complete on GPUs. This will change when those member functions become ``constexpr`` in C++20. +static_vector.hpp +^^^^^^^^^^^^^^^^^ + +``PortsOfCall::static_vector`` is a GPU-compatible data structure that provides a +``std::vector``-like interface, but uses ``std::array``-like backing storage. That means that the +size is variable, but the capacity is fixed at runtime. This allows the creation of a data +structure of non-default-constructible objects like with a ``std::vector``. This also allows the +type to be self-contained: no pointers, so a ``PortsOfCall::static_vector`` can be memcopied +between CPU and GPU. It is related to a `proposed data structure +https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0843r8.html`_ that may be included in a +future C++ standard. From 5aaafc69dac1b5338f115f68c29463dac8f7b684 Mon Sep 17 00:00:00 2001 From: "Brendan K. Krueger" Date: Wed, 9 Oct 2024 11:54:05 -0600 Subject: [PATCH 5/6] Remove extraneous braces --- ports-of-call/static_vector.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports-of-call/static_vector.hpp b/ports-of-call/static_vector.hpp index f4fbd382..cccd0c03 100644 --- a/ports-of-call/static_vector.hpp +++ b/ports-of-call/static_vector.hpp @@ -318,11 +318,11 @@ class static_vector { // begin // -------------------------------------------------------------------------------------- - PORTABLE_FUNCTION constexpr iterator begin() { return {ptr()}; } + PORTABLE_FUNCTION constexpr iterator begin() { return ptr(); } - PORTABLE_FUNCTION constexpr const_iterator begin() const { return {ptr()}; } + PORTABLE_FUNCTION constexpr const_iterator begin() const { return ptr(); } - PORTABLE_FUNCTION constexpr const_iterator cbegin() const { return {ptr()}; } + PORTABLE_FUNCTION constexpr const_iterator cbegin() const { return ptr(); } // end // ---------------------------------------------------------------------------------------- From d4f072ac91546f040183f607fff31e67c2297892 Mon Sep 17 00:00:00 2001 From: "Brendan K. Krueger" Date: Thu, 10 Oct 2024 11:09:20 -0600 Subject: [PATCH 6/6] Wrap in ifdef --- test/test_static_vector_kokkos.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_static_vector_kokkos.cpp b/test/test_static_vector_kokkos.cpp index 9957f2c4..12097cdf 100644 --- a/test/test_static_vector_kokkos.cpp +++ b/test/test_static_vector_kokkos.cpp @@ -1,3 +1,5 @@ +#ifdef PORTABILITY_STRATEGY_KOKKOS + #include "ports-of-call/portability.hpp" #include "ports-of-call/static_vector.hpp" @@ -111,3 +113,5 @@ TEST_CASE("static vector GPU", "[reaction_network]") { } // ================================================================================================ + +#endif //PORTABILITY_STRATEGY_KOKKOS