Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions test/unit/mem/constCorrectness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(alpaka::Vec{2u, 3u});
auto mutableView = mutableBuffer.getView();
auto const& outerConstBuffer = mutableBuffer;
auto const& outerConstView = mutableView;

alignas(32) std::array<int const, 2 * 3> 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<decltype(mutableBufferMdSpan.getExtents())>,
std::remove_cvref_t<decltype(mutableBuffer.getExtents())>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(mutableBufferMdSpan.getPitches())>,
std::remove_cvref_t<decltype(mutableBuffer.getPitches())>>);
static_assert(
std::same_as<decltype(mutableBufferMdSpan.getAlignment()), decltype(mutableBuffer.getAlignment())>);
static_assert(!std::is_const_v<std::remove_pointer_t<decltype(mutableBufferMdSpan.data())>>);
static_assert(!std::is_const_v<std::remove_reference_t<decltype(mutableBufferMdSpan[alpaka::Vec{0u, 0u}])>>);

static_assert(std::same_as<
std::remove_cvref_t<decltype(mutableViewMdSpan.getExtents())>,
std::remove_cvref_t<decltype(mutableView.getExtents())>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(mutableViewMdSpan.getPitches())>,
std::remove_cvref_t<decltype(mutableView.getPitches())>>);
static_assert(std::same_as<decltype(mutableViewMdSpan.getAlignment()), decltype(mutableView.getAlignment())>);
static_assert(!std::is_const_v<std::remove_pointer_t<decltype(mutableViewMdSpan.data())>>);
static_assert(!std::is_const_v<std::remove_reference_t<decltype(mutableViewMdSpan[alpaka::Vec{0u, 0u}])>>);

static_assert(std::same_as<
std::remove_cvref_t<decltype(outerConstBufferMdSpan.getExtents())>,
std::remove_cvref_t<decltype(outerConstBuffer.getExtents())>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(outerConstBufferMdSpan.getPitches())>,
std::remove_cvref_t<decltype(outerConstBuffer.getPitches())>>);
static_assert(
std::same_as<decltype(outerConstBufferMdSpan.getAlignment()), decltype(outerConstBuffer.getAlignment())>);
static_assert(std::is_const_v<std::remove_pointer_t<decltype(outerConstBufferMdSpan.data())>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(outerConstBufferMdSpan[alpaka::Vec{0u, 0u}])>>);

static_assert(std::same_as<
std::remove_cvref_t<decltype(outerConstViewMdSpan.getExtents())>,
std::remove_cvref_t<decltype(outerConstView.getExtents())>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(outerConstViewMdSpan.getPitches())>,
std::remove_cvref_t<decltype(outerConstView.getPitches())>>);
static_assert(
std::same_as<decltype(outerConstViewMdSpan.getAlignment()), decltype(outerConstView.getAlignment())>);
static_assert(std::is_const_v<std::remove_pointer_t<decltype(outerConstViewMdSpan.data())>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(outerConstViewMdSpan[alpaka::Vec{0u, 0u}])>>);

static_assert(std::same_as<
std::remove_cvref_t<decltype(innerConstViewMdSpan.getExtents())>,
std::remove_cvref_t<decltype(innerConstView.getExtents())>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(innerConstViewMdSpan.getPitches())>,
std::remove_cvref_t<decltype(innerConstView.getPitches())>>);
static_assert(
std::same_as<decltype(innerConstViewMdSpan.getAlignment()), decltype(innerConstView.getAlignment())>);
static_assert(std::is_const_v<std::remove_pointer_t<decltype(innerConstViewMdSpan.data())>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(innerConstViewMdSpan[alpaka::Vec{0u, 0u}])>>);
}
}

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<int, 2 * 3 * 5 * 2> mutableStorage{};
alignas(64) std::array<int const, 2 * 3 * 5 * 2> 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<decltype(mutableExplicitPitchMdSpan.getExtents())>,
std::remove_cvref_t<decltype(extents)>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(mutableExplicitPitchMdSpan.getPitches())>,
std::remove_cvref_t<decltype(explicitPitches)>>);
static_assert(std::same_as<decltype(mutableExplicitPitchMdSpan.getAlignment()), alpaka::Alignment<64>>);
static_assert(!std::is_const_v<std::remove_pointer_t<decltype(mutableExplicitPitchMdSpan.data())>>);
static_assert(
!std::is_const_v<std::remove_reference_t<decltype(mutableExplicitPitchMdSpan[alpaka::Vec{0u, 0u, 0u}])>>);

static_assert(std::same_as<
std::remove_cvref_t<decltype(outerConstExplicitPitchMdSpan.getExtents())>,
std::remove_cvref_t<decltype(extents)>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(outerConstExplicitPitchMdSpan.getPitches())>,
std::remove_cvref_t<decltype(explicitPitches)>>);
static_assert(std::same_as<decltype(outerConstExplicitPitchMdSpan.getAlignment()), alpaka::Alignment<64>>);
static_assert(std::is_const_v<std::remove_pointer_t<decltype(outerConstExplicitPitchMdSpan.data())>>);
static_assert(std::is_const_v<
std::remove_reference_t<decltype(outerConstExplicitPitchMdSpan[alpaka::Vec{0u, 0u, 0u}])>>);

static_assert(std::same_as<
std::remove_cvref_t<decltype(innerConstExplicitPitchMdSpan.getExtents())>,
std::remove_cvref_t<decltype(extents)>>);
static_assert(std::same_as<
std::remove_cvref_t<decltype(innerConstExplicitPitchMdSpan.getPitches())>,
std::remove_cvref_t<decltype(explicitPitches)>>);
static_assert(std::same_as<decltype(innerConstExplicitPitchMdSpan.getAlignment()), alpaka::Alignment<64>>);
static_assert(std::is_const_v<std::remove_pointer_t<decltype(innerConstExplicitPitchMdSpan.data())>>);
static_assert(std::is_const_v<
std::remove_reference_t<decltype(innerConstExplicitPitchMdSpan[alpaka::Vec{0u, 0u, 0u}])>>);
}
}

TEST_CASE("View::getMdSpan keeps host mutability boundaries", "[mem][view][mdspan][correctness]")
{
alignas(32) std::array<int, 2 * 3> storage{};
Expand Down
96 changes: 96 additions & 0 deletions test/unit/mem/mdIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(extents);
auto bufferMdSpan = alpaka::makeMdSpan(buffer);

meta::ndLoopIncIdx(
extents,
[&](alpaka::concepts::Vector<uint32_t, 3> auto idx)
{ buffer[idx] = static_cast<int>(100u + linearize(extents, idx)); });

alignas(64) std::array<int, 2 * 3 * 4> storage{};
for(std::size_t i = 0; i < storage.size(); ++i)
storage[i] = static_cast<int>(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<decltype(bufferMdSpan.getAlignment()), decltype(buffer.getAlignment())>);

REQUIRE(mdSpan.getExtents() == view.getExtents());
REQUIRE(mdSpan.getPitches() == view.getPitches());
REQUIRE(mdSpan.data() == view.data());
STATIC_REQUIRE(std::is_same_v<decltype(mdSpan.getAlignment()), alpaka::Alignment<64>>);
STATIC_REQUIRE(std::is_same_v<decltype(constMdSpan.getAlignment()), alpaka::Alignment<64>>);
static_assert(!std::is_const_v<std::remove_reference_t<decltype(mdSpan[alpaka::Vec{0u, 0u, 0u}])>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(constMdSpan[alpaka::Vec{0u, 0u, 0u}])>>);

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<int>(extents);
alignas(64) std::array<int, 40> mutableStorage{};
mutableStorage.fill(-1);
alignas(64) std::array<int const, 40> 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<decltype(mdSpan.getAlignment()), alpaka::Alignment<64>>);
STATIC_REQUIRE(std::is_same_v<decltype(outerConstMdSpan.getAlignment()), alpaka::Alignment<64>>);
STATIC_REQUIRE(std::is_same_v<decltype(innerConstMdSpan.getAlignment()), alpaka::Alignment<64>>);
static_assert(!std::is_const_v<std::remove_reference_t<decltype(mdSpan[alpaka::Vec{0u, 0u, 0u}])>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(outerConstMdSpan[alpaka::Vec{0u, 0u, 0u}])>>);
static_assert(std::is_const_v<std::remove_reference_t<decltype(innerConstMdSpan[alpaka::Vec{0u, 0u, 0u}])>>);

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
Expand Down