diff --git a/test/unit/mem/constCorrectness.cpp b/test/unit/mem/constCorrectness.cpp index c5a02587b..27b6ce544 100644 --- a/test/unit/mem/constCorrectness.cpp +++ b/test/unit/mem/constCorrectness.cpp @@ -835,6 +835,140 @@ TEST_CASE( } } +TEST_CASE( + "alpaka::makeMdSpan(any) preserves host shape alignment and inner constness", + "[mem][mdspan][correctness][makeMdSpan]") +{ + auto mutableBuffer = alpaka::onHost::allocHost(alpaka::Vec{2u, 3u}); + auto mutableView = mutableBuffer.getView(); + auto const& outerConstBuffer = mutableBuffer; + auto const& outerConstView = mutableView; + + alignas(32) std::array innerConstStorage{0, 1, 2, 3, 4, 5}; + auto innerConstView + = alpaka::makeView(alpaka::api::host, innerConstStorage.data(), alpaka::Vec{2u, 3u}, alpaka::Alignment<32>{}); + + auto mutableBufferMdSpan = alpaka::makeMdSpan(mutableBuffer); + auto mutableViewMdSpan = alpaka::makeMdSpan(mutableView); + auto outerConstBufferMdSpan = alpaka::makeMdSpan(outerConstBuffer); + auto outerConstViewMdSpan = alpaka::makeMdSpan(outerConstView); + auto innerConstViewMdSpan = alpaka::makeMdSpan(innerConstView); + + SECTION("compile-time rebuilding keeps host layout metadata and inner constness") + { + // `makeMdSpan(any)` should rebuild host buffers and views without changing shape, pitches, alignment, or the + // pointed-to constness encoded by the source object. + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert( + std::same_as); + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as); + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert( + std::same_as); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert( + std::same_as); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert( + std::same_as); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + } +} + +TEST_CASE( + "alpaka::makeMdSpan(pointer, extents, pitches, alignment) preserves explicit host metadata and inner constness", + "[mem][mdspan][correctness][makeMdSpan]") +{ + auto const extents = alpaka::Vec{2u, 3u, 4u}; + auto const explicitPitches = alpaka::Vec{80u, 20u, 4u}; + alignas(64) std::array mutableStorage{}; + alignas(64) std::array innerConstStorage{}; + + auto mutableExplicitPitchMdSpan + = alpaka::makeMdSpan(mutableStorage.data(), extents, explicitPitches, alpaka::Alignment<64>{}); + auto const outerConstExplicitPitchMdSpan + = alpaka::makeMdSpan(mutableStorage.data(), extents, explicitPitches, alpaka::Alignment<64>{}); + auto innerConstExplicitPitchMdSpan + = alpaka::makeMdSpan(innerConstStorage.data(), extents, explicitPitches, alpaka::Alignment<64>{}); + + SECTION("compile-time explicit pitches stay part of the rebuilt mdspan contract") + { + // The raw explicit-pitch overload should keep the caller-supplied host layout, alignment, and pointed-to + // constness instead of normalizing the span back to a packed representation. + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as>); + static_assert(!std::is_const_v>); + static_assert( + !std::is_const_v>); + + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as>); + static_assert(std::is_const_v>); + static_assert(std::is_const_v< + std::remove_reference_t>); + + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as< + std::remove_cvref_t, + std::remove_cvref_t>); + static_assert(std::same_as>); + static_assert(std::is_const_v>); + static_assert(std::is_const_v< + std::remove_reference_t>); + } +} + TEST_CASE("View::getMdSpan keeps host mutability boundaries", "[mem][view][mdspan][correctness]") { alignas(32) std::array storage{}; diff --git a/test/unit/mem/mdIterator.cpp b/test/unit/mem/mdIterator.cpp index 1b7864b43..02cd33836 100644 --- a/test/unit/mem/mdIterator.cpp +++ b/test/unit/mem/mdIterator.cpp @@ -175,6 +175,102 @@ TEST_CASE("mdIterator host coverage", "[mem][mdIterator][iterator]") REQUIRE(&mdSpan[sampleIdx] == &packedView[sampleIdx]); } + SECTION("makeMdSpan(any) rebuilds host buffers and views without breaking aliasing") + { + // Rebuilding an MdSpan from an existing host buffer or view should preserve the layout contract and keep + // direct access bound to the original storage. + auto const extents = alpaka::Vec{2u, 3u, 4u}; + auto buffer = onHost::allocHost(extents); + auto bufferMdSpan = alpaka::makeMdSpan(buffer); + + meta::ndLoopIncIdx( + extents, + [&](alpaka::concepts::Vector auto idx) + { buffer[idx] = static_cast(100u + linearize(extents, idx)); }); + + alignas(64) std::array storage{}; + for(std::size_t i = 0; i < storage.size(); ++i) + storage[i] = static_cast(i); + + auto view = alpaka::makeView(api::host, storage.data(), extents, alpaka::Alignment<64>{}); + auto mdSpan = alpaka::makeMdSpan(view); + auto const constMdSpan = alpaka::makeMdSpan(std::as_const(view)); + + REQUIRE(bufferMdSpan.getExtents() == buffer.getExtents()); + REQUIRE(bufferMdSpan.getPitches() == buffer.getPitches()); + REQUIRE(bufferMdSpan.data() == buffer.data()); + STATIC_REQUIRE(std::is_same_v); + + REQUIRE(mdSpan.getExtents() == view.getExtents()); + REQUIRE(mdSpan.getPitches() == view.getPitches()); + REQUIRE(mdSpan.data() == view.data()); + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(std::is_same_v>); + static_assert(!std::is_const_v>); + static_assert(std::is_const_v>); + + auto const sampleIdx = alpaka::Vec{1u, 2u, 3u}; + mdSpan[sampleIdx] = 777; + REQUIRE(view[sampleIdx] == 777); + REQUIRE(storage[storage.size() - 1u] == 777); + REQUIRE(&mdSpan[sampleIdx] == &view[sampleIdx]); + REQUIRE(&constMdSpan[sampleIdx] == &view[sampleIdx]); + + auto const visited = collectValues(mdSpan); + REQUIRE(visited.size() == storage.size()); + REQUIRE(visited.front() == 0); + REQUIRE(visited.back() == 777); + REQUIRE(collectValues(constMdSpan) == visited); + } + + SECTION("explicit-pitch makeMdSpan keeps padded host layout and aliases the same storage") + { + // The raw explicit-pitch overload must keep caller-provided padded pitches for indexing instead of deriving a + // packed layout from the extents. + auto const extents = alpaka::Vec{2u, 3u, 4u}; + auto const explicitPitches = alpaka::Vec{80u, 20u, 4u}; + auto const packedPitches = alpaka::calculatePitchesFromExtents(extents); + alignas(64) std::array mutableStorage{}; + mutableStorage.fill(-1); + alignas(64) std::array innerConstStorage{}; + + auto mdSpan = alpaka::makeMdSpan(mutableStorage.data(), extents, explicitPitches, alpaka::Alignment<64>{}); + auto const outerConstMdSpan + = alpaka::makeMdSpan(mutableStorage.data(), extents, explicitPitches, alpaka::Alignment<64>{}); + auto innerConstMdSpan + = alpaka::makeMdSpan(innerConstStorage.data(), extents, explicitPitches, alpaka::Alignment<64>{}); + + REQUIRE(mdSpan.getExtents() == extents); + REQUIRE(mdSpan.getPitches() == explicitPitches); + REQUIRE(mdSpan.getPitches() != packedPitches); + REQUIRE(mdSpan.data() == mutableStorage.data()); + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(std::is_same_v>); + static_assert(!std::is_const_v>); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + + mutableStorage[5] = -50; + mutableStorage[4] = -40; + REQUIRE(mdSpan[alpaka::Vec{0u, 1u, 0u}] == -50); + + mutableStorage[20] = 220; + mutableStorage[12] = 120; + REQUIRE(mdSpan[alpaka::Vec{1u, 0u, 0u}] == 220); + + auto const sampleIdx = alpaka::Vec{1u, 2u, 3u}; + mdSpan[sampleIdx] = 1323; + REQUIRE(mutableStorage[33] == 1323); + REQUIRE(mutableStorage[23] == -1); + REQUIRE(&mdSpan[sampleIdx] == &mutableStorage[33]); + REQUIRE(&outerConstMdSpan[sampleIdx] == &mutableStorage[33]); + + auto iter = mdSpan.begin(); + ++iter; + REQUIRE(&*iter == &mutableStorage[1]); + } + 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