//===-- Pseudo-random number generation utilities ---------------*- 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_LIBC_BENCHMARKS_GPU_RANDOM_H #define LLVM_LIBC_BENCHMARKS_GPU_RANDOM_H #include "hdr/stdint_proxy.h" #include "src/__support/CPP/algorithm.h" #include "src/__support/CPP/optional.h" #include "src/__support/CPP/type_traits.h" #include "src/__support/FPUtil/FPBits.h" #include "src/__support/macros/attributes.h" #include "src/__support/macros/config.h" #include "src/__support/macros/properties/types.h" #include "src/__support/sign.h" namespace LIBC_NAMESPACE_DECL { namespace benchmarks { // Pseudo-random number generator (PRNG) that produces unsigned 64-bit, 32-bit, // and 16-bit integers. The implementation is based on the xorshift* generator, // seeded using SplitMix64 for robust initialization. For more details, see: // https://en.wikipedia.org/wiki/Xorshift class RandomGenerator { uint64_t state; static LIBC_INLINE uint64_t splitmix64(uint64_t x) noexcept { x += 0x9E3779B97F4A7C15ULL; x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ULL; x = (x ^ (x >> 27)) * 0x94D049BB133111EBULL; x = (x ^ (x >> 31)); return x ? x : 0x9E3779B97F4A7C15ULL; } public: explicit LIBC_INLINE RandomGenerator(uint64_t seed) noexcept : state(splitmix64(seed)) {} LIBC_INLINE uint64_t next64() noexcept { uint64_t x = state; x ^= x >> 12; x ^= x << 25; x ^= x >> 27; state = x; return x * 0x2545F4914F6CDD1DULL; } LIBC_INLINE uint32_t next32() noexcept { return static_cast(next64() >> 32); } LIBC_INLINE uint16_t next16() noexcept { return static_cast(next64() >> 48); } }; // Generates random floating-point numbers where the unbiased binary exponent // is sampled uniformly in `[min_exp, max_exp]`. The significand bits are // always randomized, while the sign is randomized by default but can be fixed. // Evenly covers orders of magnitude; never yields Inf/NaN. template class UniformExponent { static_assert(cpp::is_same_v || cpp::is_same_v || cpp::is_same_v, "UniformExponent supports float16, float, and double"); using FPBits = LIBC_NAMESPACE::fputil::FPBits; using Storage = typename FPBits::StorageType; public: explicit UniformExponent(int min_exp = -FPBits::EXP_BIAS, int max_exp = FPBits::EXP_BIAS, cpp::optional forced_sign = cpp::nullopt) : min_exp(clamp_exponent(cpp::min(min_exp, max_exp))), max_exp(clamp_exponent(cpp::max(min_exp, max_exp))), forced_sign(forced_sign) {} LIBC_INLINE T operator()(RandomGenerator &rng) const noexcept { // Sample unbiased exponent e uniformly in [min_exp, max_exp] without modulo // bias, using rejection sampling auto sample_in_range = [&](uint64_t r) -> int32_t { const uint64_t range = static_cast( static_cast(max_exp) - static_cast(min_exp) + 1); const uint64_t threshold = (-range) % range; while (r < threshold) r = rng.next64(); return static_cast(min_exp + static_cast(r % range)); }; const int32_t e = sample_in_range(rng.next64()); // Start from random bits to get random sign and mantissa FPBits xbits([&] { if constexpr (cpp::is_same_v) return FPBits(rng.next64()); else if constexpr (cpp::is_same_v) return FPBits(rng.next32()); else return FPBits(rng.next16()); }()); if (e == -FPBits::EXP_BIAS) { // Subnormal: biased exponent must be 0; ensure mantissa != 0 to avoid 0 xbits.set_biased_exponent(Storage(0)); if (xbits.get_mantissa() == Storage(0)) xbits.set_mantissa(Storage(1)); } else { // Normal: biased exponent in [1, 2 * FPBits::EXP_BIAS] const int32_t biased = e + FPBits::EXP_BIAS; xbits.set_biased_exponent(static_cast(biased)); } if (forced_sign) xbits.set_sign(*forced_sign); return xbits.get_val(); } private: static LIBC_INLINE int clamp_exponent(int val) noexcept { if (val < -FPBits::EXP_BIAS) return -FPBits::EXP_BIAS; if (val > FPBits::EXP_BIAS) return FPBits::EXP_BIAS; return val; } const int min_exp; const int max_exp; const cpp::optional forced_sign; }; // Generates random floating-point numbers that are uniformly distributed on // a linear scale. Values are sampled from `[min_val, max_val)`. template class UniformLinear { static_assert(cpp::is_same_v || cpp::is_same_v || cpp::is_same_v, "UniformLinear supports float16, float, and double"); using FPBits = LIBC_NAMESPACE::fputil::FPBits; using Storage = typename FPBits::StorageType; static constexpr T MAX_NORMAL = FPBits::max_normal().get_val(); public: explicit UniformLinear(T min_val = -MAX_NORMAL, T max_val = MAX_NORMAL) : min_val(clamp_val(cpp::min(min_val, max_val))), max_val(clamp_val(cpp::max(min_val, max_val))) {} LIBC_INLINE T operator()(RandomGenerator &rng) const noexcept { double u = standard_uniform(rng.next64()); double a = static_cast(min_val); double b = static_cast(max_val); double y = a + (b - a) * u; return static_cast(y); } private: static LIBC_INLINE T clamp_val(T val) noexcept { if (val < -MAX_NORMAL) return -MAX_NORMAL; if (val > MAX_NORMAL) return MAX_NORMAL; return val; } static LIBC_INLINE double standard_uniform(uint64_t x) noexcept { constexpr int PREC_BITS = LIBC_NAMESPACE::fputil::FPBits::SIG_LEN + 1; constexpr int SHIFT_BITS = LIBC_NAMESPACE::fputil::FPBits::EXP_LEN; constexpr double INV = 1.0 / static_cast(1ULL << PREC_BITS); return static_cast(x >> SHIFT_BITS) * INV; } const T min_val; const T max_val; }; } // namespace benchmarks } // namespace LIBC_NAMESPACE_DECL #endif