Leandro Lacerda 8d7b50e572
[Offload][Conformance] Add RandomGenerator for large input spaces (#154252)
This patch implements the `RandomGenerator`, a new input generator that
enables conformance testing for functions with large input spaces (e.g.,
double-precision math functions).

**Architectural Refactoring**

To support different generation strategies in a clean and extensible
way, the existing `ExhaustiveGenerator` was refactored into a new class
hierarchy:
* A new abstract base class, `RangeBasedGenerator`, was introduced using
the Curiously Recurring Template Pattern (CRTP). It contains the common
logic for generators that operate on a sequence of ranges.
* `ExhaustiveGenerator` now inherits from this base class, simplifying
its implementation.

**New Components**
* The new `RandomGenerator` class also inherits from
`RangeBasedGenerator`. It implements a strategy that randomly samples a
specified number of points from the total input space.
* Random number generation is handled by a new, self-contained
`RandomState` class (a `xorshift64*` PRNG seeded with `splitmix64`) to
ensure deterministic and reproducible random streams for testing.

**Example Usage**

As a first use case and demonstration of this new capability, this patch
also adds the first double-precision conformance test for the `log`
function. This test uses the new `RandomGenerator` to validate the
implementations from the `llvm-libm`, `cuda-math`, and `hip-math`
providers.
2025-08-20 13:37:01 -05:00

120 lines
3.7 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains the definition of the ExhaustiveGenerator class, a
/// concrete range-based generator that exhaustively creates inputs from a
/// given sequence of ranges.
///
//===----------------------------------------------------------------------===//
#ifndef MATHTEST_EXHAUSTIVEGENERATOR_HPP
#define MATHTEST_EXHAUSTIVEGENERATOR_HPP
#include "mathtest/IndexedRange.hpp"
#include "mathtest/RangeBasedGenerator.hpp"
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <tuple>
namespace mathtest {
template <typename... InTypes>
class [[nodiscard]] ExhaustiveGenerator final
: public RangeBasedGenerator<ExhaustiveGenerator<InTypes...>, InTypes...> {
friend class RangeBasedGenerator<ExhaustiveGenerator<InTypes...>, InTypes...>;
using Base = RangeBasedGenerator<ExhaustiveGenerator<InTypes...>, InTypes...>;
using IndexArrayType = std::array<uint64_t, Base::NumInputs>;
using Base::RangesTuple;
using Base::Size;
public:
explicit constexpr ExhaustiveGenerator(
const IndexedRange<InTypes> &...Ranges) noexcept
: Base(Ranges...) {
const auto MaybeSize = getInputSpaceSize(Ranges...);
assert(MaybeSize.has_value() && "The size is too large");
Size = *MaybeSize;
assert((Size > 0) && "The size must be at least 1");
IndexArrayType DimSizes = {};
std::size_t DimIndex = 0;
((DimSizes[DimIndex++] = Ranges.getSize()), ...);
Strides[Base::NumInputs - 1] = 1;
if constexpr (Base::NumInputs > 1)
for (int Index = static_cast<int>(Base::NumInputs) - 2; Index >= 0;
--Index)
Strides[Index] = Strides[Index + 1] * DimSizes[Index + 1];
}
private:
[[nodiscard]] constexpr IndexArrayType
getNDIndex(uint64_t FlatIndex) const noexcept {
IndexArrayType NDIndex;
for (std::size_t Index = 0; Index < Base::NumInputs; ++Index) {
NDIndex[Index] = FlatIndex / Strides[Index];
FlatIndex -= NDIndex[Index] * Strides[Index];
}
return NDIndex;
}
template <typename BufferPtrsTupleType>
void writeInputs(uint64_t CurrentFlatIndex, uint64_t Offset,
BufferPtrsTupleType BufferPtrsTuple) const noexcept {
auto NDIndex = getNDIndex(CurrentFlatIndex + Offset);
writeInputsImpl<0>(NDIndex, Offset, BufferPtrsTuple);
}
template <std::size_t Index, typename BufferPtrsTupleType>
void writeInputsImpl(IndexArrayType NDIndex, uint64_t Offset,
BufferPtrsTupleType BufferPtrsTuple) const noexcept {
if constexpr (Index < Base::NumInputs) {
const auto &Range = std::get<Index>(RangesTuple);
std::get<Index>(BufferPtrsTuple)[Offset] = Range[NDIndex[Index]];
writeInputsImpl<Index + 1>(NDIndex, Offset, BufferPtrsTuple);
}
}
[[nodiscard]] static constexpr std::optional<uint64_t>
getInputSpaceSize(const IndexedRange<InTypes> &...Ranges) noexcept {
uint64_t InputSpaceSize = 1;
bool Overflowed = false;
auto Multiplier = [&](const uint64_t RangeSize) {
if (!Overflowed)
Overflowed =
__builtin_mul_overflow(InputSpaceSize, RangeSize, &InputSpaceSize);
};
(Multiplier(Ranges.getSize()), ...);
if (Overflowed)
return std::nullopt;
return InputSpaceSize;
}
IndexArrayType Strides = {};
};
} // namespace mathtest
#endif // MATHTEST_EXHAUSTIVEGENERATOR_HPP