diff --git a/test/unit/mem/constCorrectness.cpp b/test/unit/mem/constCorrectness.cpp index d1828eef3..0143d6a61 100644 --- a/test/unit/mem/constCorrectness.cpp +++ b/test/unit/mem/constCorrectness.cpp @@ -777,6 +777,143 @@ TEST_CASE( } } +TEST_CASE( + "alpaka::makeView(api, pointer, extents, alignment) preserves packed host metadata and inner constness", + "[mem][view][correctness][makeView]") +{ + auto const extents = alpaka::Vec{2u, 3u, 4u}; + auto const packedPitches = alpaka::calculatePitchesFromExtents(extents); + alignas(64) std::array mutableStorage{}; + alignas(64) std::array innerConstStorage{}; + + auto mutablePackedView + = alpaka::makeView(alpaka::api::host, mutableStorage.data(), extents, alpaka::Alignment<64>{}); + auto const outerConstPackedView + = alpaka::makeView(alpaka::api::host, mutableStorage.data(), extents, alpaka::Alignment<64>{}); + auto innerConstPackedView + = alpaka::makeView(alpaka::api::host, innerConstStorage.data(), extents, alpaka::Alignment<64>{}); + + SECTION("compile-time packed pitches stay part of the returned host view contract") + { + // The packed overload should derive the host pitch type from the extents while preserving alignment and + // pointed-to constness. + static_assert(std::same_as); + static_assert(std::same_as); + static_assert(std::same_as); + + 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(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("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 78ac2564f..ad5febce6 100644 --- a/test/unit/mem/mdIterator.cpp +++ b/test/unit/mem/mdIterator.cpp @@ -136,6 +136,93 @@ TEST_CASE("mdIterator host coverage", "[mem][mdIterator][iterator]") REQUIRE(collectValues(constMdSpan) == visited); } + SECTION("packed makeView derives packed pitches and getMdSpan keeps that layout") + { + // The packed raw-pointer overload must derive pitches from extents instead of accepting a padded layout, and + // the immediate MdSpan view should expose the same packed contract. + auto const extents = alpaka::Vec{2u, 3u, 4u}; + auto const packedPitches = alpaka::calculatePitchesFromExtents(extents); + auto const explicitPitches = alpaka::Vec{80u, 20u, 4u}; + alignas(64) std::array storage{}; + for(std::size_t i = 0; i < storage.size(); ++i) + storage[i] = static_cast(i); + + auto packedView = alpaka::makeView(api::host, storage.data(), extents, alpaka::Alignment<64>{}); + auto mdSpan = packedView.getMdSpan(); + + REQUIRE(packedView.getApi() == api::host); + REQUIRE(packedView.getExtents() == extents); + REQUIRE(packedView.getPitches() == packedPitches); + REQUIRE(packedView.getPitches() != explicitPitches); + REQUIRE(packedView.data() == storage.data()); + STATIC_REQUIRE(std::is_same_v>); + + storage[15] = 1500; + storage[23] = 2300; + auto const sampleIdx = alpaka::Vec{1u, 0u, 3u}; + REQUIRE(packedView[sampleIdx] == 1500); + REQUIRE(&packedView[sampleIdx] == &storage[15]); + + REQUIRE(mdSpan.getExtents() == packedView.getExtents()); + REQUIRE(mdSpan.getPitches() == packedView.getPitches()); + REQUIRE(mdSpan.data() == packedView.data()); + STATIC_REQUIRE(std::is_same_v>); + + mdSpan[sampleIdx] = 777; + REQUIRE(packedView[sampleIdx] == 777); + REQUIRE(storage[15] == 777); + REQUIRE(storage[23] == 2300); + 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("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