blob: df137d3cb110f867b93ecbb03d95b89632f8fb3b [file] [log] [blame]
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include "libdivide.h"
#if __cplusplus < 201703L
#error "Sorry, needs C++17 or later."
#endif
// How many bytes of data to use for numerators at most.
// Must be larger than the largest simd size.
constexpr const std::size_t NbytesOfInput = 512 / 8;
// how much data to consume for the divisor (at most).
constexpr const std::size_t NbytesForDivisor = sizeof(std::int64_t);
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
// pick what types to operate on from the fuzz data
if (Size < 2) return 0;
const auto type_selector = Data[0];
const auto branchfree_selector = Data[1];
Data += 2;
Size -= 2;
// exit early to avoid reading outside bounds later
if (Size < NbytesOfInput + NbytesForDivisor) {
return 0;
}
// This generic lambda does the work for us, by conveniently deducing the type
// It will return an int, just so the switch case looks prettier at the
// call site.
auto outer = [&](const auto integerdummy) -> int {
auto inner = [&](const auto branchingtypedummy) -> int {
using Integer = std::remove_const_t<decltype(integerdummy)>;
static_assert(std::is_integral_v<Integer>, "input should be an integer");
// pick the divisor from the fuzz data
const Integer divisor = [&]() {
static_assert(sizeof(Integer) <= NbytesForDivisor, "make NbytesForDivisor larger");
Integer tmp;
std::memcpy(&tmp, Data, sizeof(Integer));
Data += NbytesForDivisor;
Size -= NbytesForDivisor;
return tmp;
}();
// don't let the universe explode
if (divisor == 0) {
return 0;
}
// respect the branchfree variant prohibiting 1
if (branchingtypedummy.value == libdivide::BRANCHFREE && divisor == 1) {
return 0;
}
// This array type is used for both input and output. It may be overly
// large for the smaller simd types.
using ArrayOfIntegers = std::array<Integer, NbytesOfInput / sizeof(Integer)>;
// The numbers to later divide by divisor
ArrayOfIntegers numerators;
numerators.fill(0);
if (Size < NbytesOfInput) return 0;
std::memcpy(numerators.data(), Data, NbytesOfInput);
// avoid problems with integer overflow from INT_MIN/-1
if (std::is_signed_v<Integer> && divisor == -1) {
for (auto& e : numerators) {
if (e == std::numeric_limits<Integer>::min()) {
e = 0;
}
}
}
// get data into a simd register
#if defined(LIBDIVIDE_AVX512)
// not tested!
using Vector = __m512i;
const Vector num_as_simdvector = _mm512_loadu_si512((const Vector*)numerators.data());
#endif
#if defined(LIBDIVIDE_AVX2)
using Vector = __m256i;
const Vector num_as_simdvector = _mm256_loadu_si256((const Vector*)numerators.data());
#endif
#if defined(LIBDIVIDE_SSE2)
using Vector = __m128i;
const Vector num_as_simdvector = _mm_loadu_si128((const Vector*)numerators.data());
#endif
// carry out the division
libdivide::divider<Integer, branchingtypedummy.value> divider(divisor);
const Vector res = num_as_simdvector / divider;
// this will eventually contain the result
ArrayOfIntegers simdresult;
simdresult.fill(0);
// copy the results from the simd register
#if defined(LIBDIVIDE_AVX512)
_mm512_storeu_si512((Vector*)simdresult.data(), res);
#endif
#if defined(LIBDIVIDE_AVX2)
_mm256_storeu_si256((Vector*)simdresult.data(), res);
#endif
#if defined(LIBDIVIDE_SSE2)
_mm_storeu_si128((Vector*)simdresult.data(), res);
#endif
// how many elements will be assigned?
constexpr const std::size_t Nelements = sizeof(Vector) / sizeof(Integer);
// validate the result
for (std::size_t i = 0; i < Nelements; ++i) {
const Integer expected = numerators.at(i) / divisor;
const Integer actual = simdresult.at(i);
if (expected != actual) abort();
}
return 0;
};
if (branchfree_selector == libdivide::BRANCHFULL) {
return inner(std::integral_constant<int, libdivide::BRANCHFULL>{});
} else if (branchfree_selector == libdivide::BRANCHFREE) {
return inner(std::integral_constant<int, libdivide::BRANCHFREE>{});
}
return 0;
};
switch (type_selector) {
case 0:
return outer(std::uint32_t{});
case 1:
return outer(std::int32_t{});
case 2:
return outer(std::uint64_t{});
case 3:
return outer(std::int64_t{});
default:
return 0;
}
return 0;
}