blob: 4a68fa2bbe78d8cd90387f13c9bc923d833914a5 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#include "packetmath_test_shared.h"
#include <memory>
// SVE's 32-bit gathers form the lane offsets the same way and still overflow; see pgather<float, PacketXf>.
#if defined(__unix__) && (defined(__LP64__) || defined(_LP64)) && !defined(EIGEN_VECTORIZE_SVE)
#include <sys/mman.h>
#include <unistd.h>
#include <cstdint>
#define EIGEN_TEST_LARGE_STRIDE_GATHER
#endif
namespace Eigen {
namespace test {
#ifdef EIGEN_TEST_LARGE_STRIDE_GATHER
// Lane i at from[i * stride] for a stride near the limit of 32-bit lane offsets (lanes - 1) * stride. Only the pages
// holding the lanes set in `mapped` are mapped, so the reserved range costs address space but no memory.
template <typename Scalar>
struct large_stride_lanes {
Index stride;
std::size_t bytes = 0;
void* reserved = MAP_FAILED;
Scalar* from = nullptr;
large_stride_lanes(int lanes, Index stride_, std::uint32_t mapped = ~std::uint32_t(0)) : stride(stride_) {
const std::uintptr_t page = std::uintptr_t(sysconf(_SC_PAGESIZE));
const Index span = (lanes - 1) * numext::abs(stride);
bytes = std::size_t(span + 1) * sizeof(Scalar) + page;
reserved = mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
if (reserved == MAP_FAILED) {
std::cerr << "Skipping the gather test at stride " << stride << ": cannot reserve " << bytes << " bytes.\n";
return;
}
from = static_cast<Scalar*>(reserved) + (stride < 0 ? span : 0);
for (Index i = 0; i < lanes; ++i) {
if (((mapped >> i) & 1) == 0) continue;
void* lane_page = reinterpret_cast<void*>(reinterpret_cast<std::uintptr_t>(from + i * stride) & ~(page - 1));
VERIFY_IS_EQUAL(mprotect(lane_page, page, PROT_READ | PROT_WRITE), 0);
from[i * stride] = Scalar(i + 1);
}
}
~large_stride_lanes() {
if (reserved != MAP_FAILED) VERIFY_IS_EQUAL(munmap(reserved, bytes), 0);
}
};
// The largest stride whose lane offsets fit in an int, the next one, and its negation.
template <typename Packet>
std::vector<Index> large_strides() {
const Index fits = Index(NumTraits<int>::highest()) / (internal::unpacket_traits<Packet>::size - 1);
return {fits, fits + 1, -(fits + 1)};
}
template <typename Scalar, typename Packet>
void pgather_pscatter_large_stride() {
constexpr int PacketSize = internal::unpacket_traits<Packet>::size;
if (PacketSize < 2) return;
for (Index s : large_strides<Packet>()) {
// Opaque, as in production: clang 14-20 merge _mm_set_pd(p[0x7fffffff], p[0]) into one load at a constant stride.
volatile Index runtime_stride = s;
const Index stride = runtime_stride;
large_stride_lanes<Scalar> lanes(PacketSize, stride);
if (lanes.from == nullptr) continue;
Scalar actual[PacketSize], expected[PacketSize];
for (Index i = 0; i < PacketSize; ++i) expected[i] = Scalar(i + 1);
internal::pstoreu(actual, internal::pgather<Scalar, Packet>(lanes.from, stride));
VERIFY(areEqualBits(expected, actual, PacketSize, false));
for (Index i = 0; i < PacketSize; ++i) expected[i] = Scalar(2 * i + 3);
internal::pscatter<Scalar, Packet>(lanes.from, internal::ploadu<Packet>(expected), stride);
for (Index i = 0; i < PacketSize; ++i) actual[i] = lanes.from[i * stride];
VERIFY(areEqualBits(expected, actual, PacketSize, false));
}
}
#ifdef EIGEN_VECTORIZE_AVX512
// The masked forms, which the AVX-512 GEMM kernel uses on a strided result. The masked-off lanes stay unmapped, so
// touching one faults.
template <typename Scalar, typename Packet>
void masked_pgather_pscatter_large_stride() {
constexpr int PacketSize = internal::unpacket_traits<Packet>::size;
const auto mask = static_cast<typename internal::unpacket_traits<Packet>::mask_t>(0x5555);
for (Index s : large_strides<Packet>()) {
volatile Index runtime_stride = s; // see pgather_pscatter_large_stride
const Index stride = runtime_stride;
large_stride_lanes<Scalar> lanes(PacketSize, stride, mask);
if (lanes.from == nullptr) continue;
Scalar actual[PacketSize], expected[PacketSize];
for (Index i = 0; i < PacketSize; ++i) expected[i] = (i % 2 == 0) ? Scalar(i + 1) : Scalar(-1);
internal::pstoreu(actual, internal::pgather(internal::pset1<Packet>(Scalar(-1)), lanes.from, stride, mask));
VERIFY(areEqualBits(expected, actual, PacketSize, false));
internal::pscatter(lanes.from, internal::pset1<Packet>(Scalar(0)), stride, mask);
for (Index i = 0; i < PacketSize; i += 2) VERIFY_IS_EQUAL(lanes.from[i * stride], Scalar(0));
}
}
#endif
#endif
template <typename Scalar, typename Packet>
struct runall<Scalar, Packet, false, false> {
static void run() {
constexpr int PacketSize = internal::unpacket_traits<Packet>::size;
auto samples = special_values<Scalar>();
using Bits = typename numext::get_integer_by_size<sizeof(Scalar)>::unsigned_type;
const Bits nan_payload = numext::bit_cast<Bits>(NumTraits<Scalar>::quiet_NaN()) | Bits(0x123);
samples.push_back(numext::bit_cast<Scalar>(nan_payload));
Scalar actual[PacketSize], expected[PacketSize];
for (Index stride : {Index(-7), Index(-2), Index(-1), Index(0), Index(1), Index(2), Index(3), Index(7)}) {
const Index span = (PacketSize - 1) * numext::abs(stride) + 1;
for (Index offset = 0; offset < PacketSize; ++offset) {
// Exact-size new[] lets sanitizers detect reads past the last gathered coefficient.
std::unique_ptr<Scalar[]> storage(new Scalar[span + offset]);
Scalar* data = storage.get() + offset;
const Scalar* from = stride < 0 ? data + span - 1 : data;
for (Index rotation = 0; rotation < Index(samples.size()); ++rotation) {
for (Index i = 0; i < span; ++i) data[i] = samples[(i + rotation) % samples.size()];
for (Index i = 0; i < PacketSize; ++i) expected[i] = from[i * stride];
volatile Index runtime_stride = stride;
internal::pstoreu(actual, internal::pgather<Scalar, Packet>(from, runtime_stride));
VERIFY(areEqualBits(expected, actual, PacketSize, false));
if (stride == 2) {
internal::pstoreu(actual, internal::pgather<Scalar, Packet>(from, 2));
VERIFY(areEqualBits(expected, actual, PacketSize, false));
}
}
}
}
#ifdef EIGEN_TEST_LARGE_STRIDE_GATHER
pgather_pscatter_large_stride<Scalar, Packet>();
#endif
}
};
} // namespace test
} // namespace Eigen
EIGEN_DECLARE_TEST(packetmath_gather) {
CALL_SUBTEST_1(test::runner<float>::run());
CALL_SUBTEST_2(test::runner<double>::run());
#ifdef EIGEN_TEST_LARGE_STRIDE_GATHER
CALL_SUBTEST_1((test::pgather_pscatter_large_stride<int32_t, internal::packet_traits<int32_t>::type>()));
CALL_SUBTEST_2((test::pgather_pscatter_large_stride<int64_t, internal::packet_traits<int64_t>::type>()));
#ifdef EIGEN_VECTORIZE_AVX512
CALL_SUBTEST_1((test::masked_pgather_pscatter_large_stride<float, internal::Packet16f>()));
CALL_SUBTEST_2((test::masked_pgather_pscatter_large_stride<double, internal::Packet8d>()));
#endif
#endif
}