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
137 changes: 137 additions & 0 deletions test/unit/mem/constCorrectness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(extents);
alignas(64) std::array<int, 2 * 3 * 4> mutableStorage{};
alignas(64) std::array<int const, 2 * 3 * 4> 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<decltype(mutablePackedView.getApi()), alpaka::api::Host>);
static_assert(std::same_as<decltype(outerConstPackedView.getApi()), alpaka::api::Host>);
static_assert(std::same_as<decltype(innerConstPackedView.getApi()), alpaka::api::Host>);

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

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

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

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("View::getMdSpan keeps host mutability boundaries", "[mem][view][mdspan][correctness]")
{
alignas(32) std::array<int, 2 * 3> storage{};
Expand Down
87 changes: 87 additions & 0 deletions test/unit/mem/mdIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(extents);
auto const explicitPitches = alpaka::Vec{80u, 20u, 4u};
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 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<decltype(packedView.getAlignment()), alpaka::Alignment<64>>);

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<decltype(mdSpan.getAlignment()), alpaka::Alignment<64>>);

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<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("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