diff --git a/include/alpaka/mem/MdSpan.hpp b/include/alpaka/mem/MdSpan.hpp index 4cae1629..8ea17658 100644 --- a/include/alpaka/mem/MdSpan.hpp +++ b/include/alpaka/mem/MdSpan.hpp @@ -112,11 +112,21 @@ namespace alpaka } constexpr auto begin() const + { + return MdForwardIter{this->getConstMdSpan()}; + } + + constexpr auto begin() { return MdForwardIter{*this}; } constexpr auto end() const + { + return MdForwardIterEnd{this->getConstMdSpan()}; + } + + constexpr auto end() { return MdForwardIterEnd{*this}; } diff --git a/include/alpaka/mem/MdSpanArray.hpp b/include/alpaka/mem/MdSpanArray.hpp index 75a418ff..f788cbca 100644 --- a/include/alpaka/mem/MdSpanArray.hpp +++ b/include/alpaka/mem/MdSpanArray.hpp @@ -108,11 +108,21 @@ namespace alpaka } constexpr auto begin() const + { + return MdForwardIter{this->getConstMdSpan()}; + } + + constexpr auto begin() { return MdForwardIter{*this}; } constexpr auto end() const + { + return MdForwardIterEnd{this->getConstMdSpan()}; + } + + constexpr auto end() { return MdForwardIterEnd{*this}; } diff --git a/test/unit/mem/mdIterator.cpp b/test/unit/mem/mdIterator.cpp index cdbc4da9..043fc725 100644 --- a/test/unit/mem/mdIterator.cpp +++ b/test/unit/mem/mdIterator.cpp @@ -6,29 +6,124 @@ #include +#include +#include +#include +#include using namespace alpaka; using namespace alpaka::onHost; -TEST_CASE("mdIterator", "") +TEST_CASE("mdIterator host coverage", "[mem][mdIterator][iterator]") { - constexpr auto numElements = CVec{}; - alpaka::concepts::IBuffer auto span = onHost::allocHost(numElements); + auto collectValues = [](T_Range&& range) + { + using Value = std::remove_cvref_t; + std::vector values; + for(auto&& value : range) + values.push_back(value); + return values; + }; + + SECTION("zero-extent MdSpan and View are empty") + { + // Zero extents should terminate immediately instead of yielding a bogus first element. + std::array storage{42u}; + auto const emptyMdSpan = alpaka::makeMdSpan(storage.data(), alpaka::Vec{0u, 3u}); + auto const emptyView = alpaka::makeView(api::host, storage.data(), alpaka::Vec{0u, 3u}); + + REQUIRE(emptyMdSpan.begin() == emptyMdSpan.end()); + REQUIRE(emptyView.begin() == emptyView.end()); + REQUIRE(collectValues(emptyMdSpan).empty()); + REQUIRE(collectValues(emptyView).empty()); + } + + SECTION("1D MdSpan iteration stays linear") + { + // A linear buffer should be visited in storage order on the host path. + std::array storage{3u, 6u, 9u, 12u, 15u}; + auto span = alpaka::makeMdSpan(storage.data(), alpaka::Vec{storage.size()}); - size_t counter = 0u; - for(uint32_t& v : span) - v = counter++; + REQUIRE(collectValues(span) == std::vector{3u, 6u, 9u, 12u, 15u}); + } - // validate by using the forward iterator - size_t refence = 0u; - for(uint32_t v : span) + SECTION("3D View iteration follows linearize order") { - CHECK(v == refence); - ++refence; + // The iterator must keep the last dimension fastest so traversal matches linearized indexing. + auto const extents = alpaka::Vec{2u, 3u, 4u}; + auto buffer = onHost::allocHost(extents); + auto view = buffer.getView(); + + meta::ndLoopIncIdx( + extents, + [&](alpaka::concepts::Vector auto idx) + { view[idx] = static_cast(linearize(extents, idx)); }); + + auto const visited = collectValues(view); + REQUIRE(visited.size() == extents.product()); + REQUIRE(visited.front() == 0u); + REQUIRE(visited.back() == extents.product() - 1u); + + for(uint32_t linearIdx = 0; linearIdx < visited.size(); ++linearIdx) + CHECK(visited[linearIdx] == linearIdx); } - // validate without using the forward iterator - meta::ndLoopIncIdx( - numElements, - [&](alpaka::concepts::Vector auto idx) { CHECK(span[idx] == linearize(numElements, idx)); }); + SECTION("mutable View iteration writes back into the underlying buffer") + { + // Writing through a non-const iterator must update the storage the view refers to. + auto const extents = alpaka::Vec{2u, 3u}; + auto buffer = onHost::allocHost(extents); + auto view = buffer.getView(); + + int nextValue = 10; + for(int& value : view) + { + value = nextValue; + nextValue += 5; + } + + REQUIRE(view[alpaka::Vec{0u, 0u}] == 10); + REQUIRE(view[alpaka::Vec{0u, 2u}] == 20); + REQUIRE(view[alpaka::Vec{1u, 0u}] == 25); + REQUIRE(view[alpaka::Vec{1u, 2u}] == 35); + CHECK(buffer[alpaka::Vec{1u, 1u}] == 30); + } + + SECTION("const View iteration is read-only and preserves order") + { + // Const iteration should expose const references while traversing the same order as mutable iteration. + std::array storage{2, 4, 6, 8, 10, 12}; + auto view = alpaka::makeView(api::host, storage.data(), alpaka::Vec{2u, 3u}); + auto const constView = view.getConstView(); + + static_assert(!std::is_const_v>); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + + REQUIRE(collectValues(view) == std::vector{2, 4, 6, 8, 10, 12}); + REQUIRE(collectValues(constView) == collectValues(view)); + } + + SECTION("pre-increment and post-increment advance one element at a time") + { + // Forward-iterator increments need to preserve the old value for post-increment and return self for pre-increment. + std::array storage{7, 11, 13, 17}; + auto span = alpaka::makeMdSpan(storage.data(), alpaka::Vec{storage.size()}); + + auto iter = span.begin(); + REQUIRE(*iter == 7); + + auto& preIncrement = ++iter; + REQUIRE(&preIncrement == &iter); + REQUIRE(*iter == 11); + + auto postIncrement = iter++; + REQUIRE(*postIncrement == 11); + REQUIRE(*iter == 13); + + ++iter; + REQUIRE(*iter == 17); + ++iter; + REQUIRE(iter == span.end()); + } }