]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - libstdc++-v3/src/c++11/random.cc
Update copyright years.
[thirdparty/gcc.git] / libstdc++-v3 / src / c++11 / random.cc
index 1d102c755b2eb8fbeabb8d30bc0958a7715e4992..b4bf0a785fb98119f1dbb33fa7d1671602215e45 100644 (file)
@@ -1,6 +1,6 @@
 // random -*- C++ -*-
 
-// Copyright (C) 2012-2015 Free Software Foundation, Inc.
+// Copyright (C) 2012-2024 Free Software Foundation, Inc.
 //
 // This file is part of the GNU ISO C++ Library.  This library is free
 // software; you can redistribute it and/or modify it under the
 // <http://www.gnu.org/licenses/>.
 
 #define _GLIBCXX_USE_CXX11_ABI 1
-#include <random>
+#define _CRT_RAND_S // define this before including <stdlib.h> to get rand_s
 
-#ifdef  _GLIBCXX_USE_C99_STDINT_TR1
+#include <random>
+#include <system_error>
 
 #if defined __i386__ || defined __x86_64__
 # include <cpuid.h>
+# ifdef _GLIBCXX_X86_RDRAND
+#  define USE_RDRAND 1
+# endif
+# ifdef _GLIBCXX_X86_RDSEED
+#  define USE_RDSEED 1
+# endif
+#elif defined __powerpc64__ && defined __BUILTIN_CPU_SUPPORTS__
+# define USE_DARN 1
 #endif
 
+#include <cerrno>
 #include <cstdio>
+#include <cctype> // For std::isdigit.
+
+#if defined _GLIBCXX_HAVE_UNISTD_H && defined _GLIBCXX_HAVE_FCNTL_H
+# include <unistd.h>
+# include <fcntl.h>
+// Use POSIX open, close, read etc. instead of ISO fopen, fclose, fread
+# define USE_POSIX_FILE_IO
+#endif
+
+#ifdef _GLIBCXX_HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+
+#ifdef _GLIBCXX_HAVE_LINUX_TYPES_H
+# include <linux/types.h>
+#endif
+
+#ifdef _GLIBCXX_HAVE_LINUX_RANDOM_H
+# include <linux/random.h>
+#endif
+
+#ifdef _GLIBCXX_USE_CRT_RAND_S
+# include <stdlib.h>
+#endif
 
-#ifdef _GLIBCXX_HAVE_UNISTD_H
+#ifdef _GLIBCXX_HAVE_GETENTROPY
 # include <unistd.h>
 #endif
 
+#if defined _GLIBCXX_USE_CRT_RAND_S || defined _GLIBCXX_USE_DEV_RANDOM \
+  || _GLIBCXX_HAVE_GETENTROPY
+// The OS provides a source of randomness we can use.
+# pragma GCC poison _M_mt
+#elif defined USE_RDRAND || defined USE_RDSEED || defined USE_DARN
+// Hardware instructions might be available, but use cpuid checks at runtime.
+# pragma GCC poison _M_mt
+// If the runtime cpuid checks fail we'll use a linear congruential engine.
+# define USE_LCG 1
+#else
+// Use the mt19937 member of the union, as in previous GCC releases.
+# define USE_MT19937 1
+#endif
+
+#ifdef USE_LCG
+# include <chrono>
+#endif
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
   namespace
   {
-    static unsigned long
-    _M_strtoul(const std::string& __str)
-    {
-      unsigned long __ret = 5489UL;
-      if (__str != "mt19937")
-       {
-         const char* __nptr = __str.c_str();
-         char* __endptr;
-         __ret = std::strtoul(__nptr, &__endptr, 0);
-         if (*__nptr == '\0' || *__endptr != '\0')
-           std::__throw_runtime_error(__N("random_device::_M_strtoul"
-                                          "(const std::string&)"));
-       }
-      return __ret;
-    }
+    [[noreturn]]
+    inline void
+    __throw_syserr([[maybe_unused]] int e, [[maybe_unused]] const char* msg)
+    { _GLIBCXX_THROW_OR_ABORT(system_error(e, std::generic_category(), msg)); }
 
-#if (defined __i386__ || defined __x86_64__) && defined _GLIBCXX_X86_RDRAND
+#if USE_RDRAND
     unsigned int
     __attribute__ ((target("rdrnd")))
-    __x86_rdrand(void)
+    __x86_rdrand(void*)
     {
       unsigned int retries = 100;
       unsigned int val;
 
-      while (__builtin_ia32_rdrand32_step(&val) == 0)
+      while (__builtin_ia32_rdrand32_step(&val) == 0) [[__unlikely__]]
        if (--retries == 0)
-         std::__throw_runtime_error(__N("random_device::__x86_rdrand(void)"));
+         std::__throw_runtime_error(__N("random_device: rdrand failed"));
 
       return val;
     }
 #endif
+
+#if USE_RDSEED
+    unsigned int
+    __attribute__ ((target("rdseed")))
+    __x86_rdseed(void* fallback)
+    {
+      unsigned int retries = 100;
+      unsigned int val;
+
+      while (__builtin_ia32_rdseed_si_step(&val) == 0) [[__unlikely__]]
+       {
+         if (--retries == 0)
+           {
+             if (auto f = reinterpret_cast<unsigned int(*)(void*)>(fallback))
+               return f(nullptr);
+             std::__throw_runtime_error(__N("random_device: rdseed failed"));
+           }
+         __builtin_ia32_pause();
+       }
+
+      return val;
+    }
+
+#if USE_RDRAND
+    unsigned int
+    __attribute__ ((target("rdseed,rdrnd")))
+    __x86_rdseed_rdrand(void*)
+    {
+      return __x86_rdseed(reinterpret_cast<void*>(&__x86_rdrand));
+    }
+#endif
+#endif
+
+#ifdef USE_DARN
+    unsigned int
+    __attribute__((target("cpu=power9")))
+    __ppc_darn(void*)
+    {
+      const uint64_t failed = -1;
+      unsigned int retries = 10;
+      uint64_t val = __builtin_darn();
+      while (val == failed) [[__unlikely__]]
+       {
+         if (--retries == 0)
+           std::__throw_runtime_error(__N("random_device: darn failed"));
+         val = __builtin_darn();
+       }
+      return (uint32_t)val;
+    }
+#endif
+
+#ifdef _GLIBCXX_USE_CRT_RAND_S
+    unsigned int
+    __winxp_rand_s(void*)
+    {
+      unsigned int val;
+      if (::rand_s(&val) != 0)
+       std::__throw_runtime_error(__N("random_device: rand_s failed"));
+      return val;
+    }
+#endif
+
+#ifdef _GLIBCXX_HAVE_GETENTROPY
+    unsigned int
+    __libc_getentropy(void*)
+    {
+      unsigned int val;
+      if (::getentropy(&val, sizeof(val)) != 0)
+       std::__throw_runtime_error(__N("random_device: getentropy failed"));
+      return val;
+    }
+#endif
+
+#ifdef _GLIBCXX_HAVE_ARC4RANDOM
+    unsigned int
+    __libc_arc4random(void*)
+    {
+      return ::arc4random();
+    }
+#endif
+
+#ifdef USE_LCG
+    // TODO: use this to seed std::mt19937 engine too.
+    unsigned
+    bad_seed(void* p) noexcept
+    {
+      // Poor quality seed based on hash of the current time and the address
+      // of the object being seeded. Better than using the same default seed
+      // for every object though.
+      const uint64_t bits[] = {
+       (uint64_t) chrono::system_clock::now().time_since_epoch().count(),
+       (uint64_t) reinterpret_cast<uintptr_t>(p)
+      };
+      auto bytes = reinterpret_cast<const unsigned char*>(bits);
+      // 32-bit FNV-1a hash
+      uint32_t h = 2166136261u;
+      for (unsigned i = 0; i < sizeof(bits); ++i)
+       {
+         h ^= *bytes++;
+         h *= 16777619u;
+       }
+      return h;
+    }
+
+    // Same as std::minstd_rand0 but using unsigned not uint_fast32_t.
+    using lcg_type
+      = linear_congruential_engine<unsigned, 16807UL, 0UL, 2147483647UL>;
+
+    inline lcg_type*
+    construct_lcg_at(void* addr) noexcept
+    {
+      return ::new(addr) lcg_type(bad_seed(addr));
+    }
+
+    inline void
+    destroy_lcg_at(void* addr) noexcept
+    {
+      static_cast<lcg_type*>(addr)->~lcg_type();
+    }
+
+    unsigned int
+    __lcg(void* ptr) noexcept
+    {
+      auto& lcg = *static_cast<lcg_type*>(ptr);
+      return lcg();
+    }
+#endif
+
+    enum Which : unsigned {
+      device_file = 1, prng = 2, rand_s = 4, getentropy = 8, arc4random = 16,
+      rdseed = 64, rdrand = 128, darn = 256,
+      any = 0xffff
+    };
+
+    constexpr Which
+    operator|(Which l, Which r) noexcept
+    { return Which(unsigned(l) | unsigned(r)); }
+
+    inline Which
+    which_source(random_device::result_type (*func [[maybe_unused]])(void*),
+                void* file [[maybe_unused]])
+    {
+#ifdef _GLIBCXX_USE_CRT_RAND_S
+      if (func == &__winxp_rand_s)
+       return rand_s;
+#endif
+
+#ifdef USE_RDSEED
+#ifdef USE_RDRAND
+      if (func == &__x86_rdseed_rdrand)
+       return rdseed;
+#endif
+      if (func == &__x86_rdseed)
+       return rdseed;
+#endif
+
+#ifdef USE_RDRAND
+      if (func == &__x86_rdrand)
+       return rdrand;
+#endif
+
+#ifdef USE_DARN
+      if (func == &__ppc_darn)
+       return darn;
+#endif
+
+#ifdef _GLIBCXX_USE_DEV_RANDOM
+      if (file != nullptr)
+       return device_file;
+#endif
+
+#ifdef _GLIBCXX_HAVE_ARC4RANDOM
+      if (func == __libc_arc4random)
+       return arc4random;
+#endif
+
+#ifdef _GLIBCXX_HAVE_GETENTROPY
+      if (func == __libc_getentropy)
+       return getentropy;
+#endif
+
+#ifdef USE_LCG
+      if (func == &__lcg)
+       return prng;
+#endif
+
+#ifdef USE_MT19937
+      return prng;
+#endif
+
+      return any; // should be unreachable
+    }
   }
 
   void
   random_device::_M_init(const std::string& token)
   {
-    const char *fname = token.c_str();
+#ifdef USE_MT19937
+    // If no real random device is supported then use the mt19937 engine.
+    _M_init_pretr1(token);
+    return;
+#else
+
+    _M_file = nullptr;
+    _M_func = nullptr;
+    _M_fd = -1;
+
+    const char* fname [[gnu::unused]] = nullptr;
+
+    Which which;
 
     if (token == "default")
       {
-#if (defined __i386__ || defined __x86_64__) && defined _GLIBCXX_X86_RDRAND
-       unsigned int eax, ebx, ecx, edx;
-       // Check availability of cpuid and, for now at least, also the
-       // CPU signature for Intel's
-       if (__get_cpuid_max(0, &ebx) > 0 && ebx == signature_INTEL_ebx)
+       which = any;
+       fname = "/dev/urandom";
+      }
+#ifdef USE_RDSEED
+    else if (token == "rdseed")
+      which = rdseed;
+#endif // USE_RDSEED
+#ifdef USE_RDRAND
+    else if (token == "rdrand" || token == "rdrnd")
+      which = rdrand;
+#endif // USE_RDRAND
+#ifdef USE_DARN
+    else if (token == "darn")
+      which = darn;
+#endif
+#if defined USE_RDRAND || defined USE_RDSEED || defined USE_DARN
+    else if (token == "hw" || token == "hardware")
+      which = rdrand | rdseed | darn;
+#endif
+#ifdef _GLIBCXX_USE_CRT_RAND_S
+    else if (token == "rand_s")
+      which = rand_s;
+#endif // _GLIBCXX_USE_CRT_RAND_S
+#ifdef _GLIBCXX_HAVE_GETENTROPY
+    else if (token == "getentropy")
+      which = getentropy;
+#endif // _GLIBCXX_HAVE_GETENTROPY
+#ifdef _GLIBCXX_HAVE_ARC4RANDOM
+    else if (token == "arc4random")
+      which = arc4random;
+#endif // _GLIBCXX_HAVE_ARC4RANDOM
+#ifdef _GLIBCXX_USE_DEV_RANDOM
+    else if (token == "/dev/urandom" || token == "/dev/random")
+      {
+       fname = token.c_str();
+       which = device_file;
+      }
+#endif // _GLIBCXX_USE_DEV_RANDOM
+#ifdef USE_LCG
+    else if (token == "prng")
+      which = prng;
+#endif
+    else
+      std::__throw_syserr(EINVAL, __N("random_device::random_device"
+                                     "(const std::string&):"
+                                     " unsupported token"));
+
+#if defined ENOSYS
+    [[maybe_unused]] const int unsupported = ENOSYS;
+#elif defined ENOTSUP
+    [[maybe_unused]] const int unsupported = ENOTSUP;
+#else
+    [[maybe_unused]] const int unsupported = 0;
+#endif
+    int err = 0;
+
+#ifdef _GLIBCXX_USE_CRT_RAND_S
+    if (which & rand_s)
+    {
+      _M_func = &__winxp_rand_s;
+      return;
+    }
+#endif // _GLIBCXX_USE_CRT_RAND_S
+
+#ifdef USE_RDSEED
+    if (which & rdseed)
+    {
+      unsigned int eax, ebx, ecx, edx;
+      // Check availability of cpuid and, for now at least, also the
+      // CPU signature for Intel and AMD.
+      if (__get_cpuid_max(0, &ebx) > 0
+         && (ebx == signature_INTEL_ebx || ebx == signature_AMD_ebx))
+       {
+         // CPUID.(EAX=07H, ECX=0H):EBX.RDSEED[bit 18]
+         __cpuid_count(7, 0, eax, ebx, ecx, edx);
+         if (ebx & bit_RDSEED)
+           {
+#ifdef USE_RDRAND
+             // CPUID.01H:ECX.RDRAND[bit 30]
+             __cpuid(1, eax, ebx, ecx, edx);
+             if (ecx & bit_RDRND)
+               {
+                 _M_func = &__x86_rdseed_rdrand;
+                 return;
+               }
+#endif
+             _M_func = &__x86_rdseed;
+             return;
+           }
+       }
+      err = unsupported;
+    }
+#endif // USE_RDSEED
+
+#ifdef USE_RDRAND
+    if (which & rdrand)
+    {
+      unsigned int eax, ebx, ecx, edx;
+      // Check availability of cpuid and, for now at least, also the
+      // CPU signature for Intel and AMD.
+      if (__get_cpuid_max(0, &ebx) > 0
+         && (ebx == signature_INTEL_ebx || ebx == signature_AMD_ebx))
+       {
+         // CPUID.01H:ECX.RDRAND[bit 30]
+         __cpuid(1, eax, ebx, ecx, edx);
+         if (ecx & bit_RDRND)
+           {
+             _M_func = &__x86_rdrand;
+             return;
+           }
+       }
+      err = unsupported;
+    }
+#endif // USE_RDRAND
+
+#ifdef USE_DARN
+    if (which & darn)
+      {
+       if (__builtin_cpu_supports("darn"))
          {
-           __cpuid(1, eax, ebx, ecx, edx);
-           if (ecx & bit_RDRND)
-             {
-               _M_file = nullptr;
-               return;
-             }
+           _M_func = &__ppc_darn;
+           return;
          }
-#endif
+       err = unsupported;
+      }
+#endif // USE_DARN
 
-       fname = "/dev/urandom";
+#ifdef _GLIBCXX_HAVE_ARC4RANDOM
+    if (which & arc4random)
+      {
+       _M_func = &__libc_arc4random;
+       return;
       }
-    else if (token != "/dev/urandom" && token != "/dev/random")
-    fail:
-      std::__throw_runtime_error(__N("random_device::"
-                                    "random_device(const std::string&)"));
+#endif // _GLIBCXX_HAVE_ARC4RANDOM
 
-    _M_file = static_cast<void*>(std::fopen(fname, "rb"));
-    if (!_M_file)
-      goto fail;
+#ifdef _GLIBCXX_HAVE_GETENTROPY
+    if (which & getentropy)
+      {
+       unsigned int i;
+       if (::getentropy(&i, sizeof(i)) == 0) // On linux the syscall can fail.
+         {
+           _M_func = &__libc_getentropy;
+           return;
+         }
+       err = unsupported;
+      }
+#endif // _GLIBCXX_HAVE_GETENTROPY
+
+#ifdef _GLIBCXX_USE_DEV_RANDOM
+    if (which & device_file)
+    {
+#ifdef USE_POSIX_FILE_IO
+      _M_fd = ::open(fname, O_RDONLY);
+      if (_M_fd != -1)
+       {
+         // Set _M_file to non-null so that _M_fini() will do clean up.
+         _M_file = &_M_fd;
+         return;
+       }
+#else // USE_POSIX_FILE_IO
+      _M_file = static_cast<void*>(std::fopen(fname, "rb"));
+      if (_M_file)
+       return;
+#endif // USE_POSIX_FILE_IO
+      err = errno;
+    }
+#endif // _GLIBCXX_USE_DEV_RANDOM
+
+#ifdef USE_LCG
+    // Either "prng" was requested explicitly, or "default" was requested
+    // but nothing above worked, use a PRNG.
+    if (which & prng)
+    {
+      static_assert(sizeof(lcg_type) <= sizeof(_M_fd), "");
+      static_assert(alignof(lcg_type) <= alignof(_M_fd), "");
+      _M_file = construct_lcg_at(&_M_fd);
+      _M_func = &__lcg;
+      return;
+    }
+#endif
+
+    auto msg = __N("random_device::random_device(const std::string&):"
+                  " device not available");
+    if (err)
+      std::__throw_syserr(err, msg);
+    else
+      std::__throw_runtime_error(msg);
+#endif // USE_MT19937
   }
 
+  // This function is called by _M_init for targets that use mt19937 for
+  // randomness, and by code compiled against old releases of libstdc++.
   void
   random_device::_M_init_pretr1(const std::string& token)
   {
-    _M_mt.seed(_M_strtoul(token));
+#ifdef USE_MT19937
+    unsigned long seed = 5489UL;
+    if (token != "default" && token != "mt19937" && token != "prng")
+      {
+       const char* nptr = token.c_str();
+       char* endptr;
+       seed = std::strtoul(nptr, &endptr, 0);
+       if (*nptr == '\0' || *endptr != '\0')
+         std::__throw_syserr(EINVAL, __N("random_device::_M_init_pretr1"
+                                         "(const std::string&)"));
+      }
+    _M_mt.seed(seed);
+#else
+    // Convert old default token "mt19937" or numeric seed tokens to "default".
+    if (token == "mt19937" || std::isdigit((unsigned char)token[0]))
+      _M_init("default");
+    else
+      _M_init(token);
+#endif
   }
 
+  // Called by old ABI version of random_device::_M_init(const std::string&).
   void
-  random_device::_M_fini()
+  random_device::_M_init(const char* s, size_t len)
   {
-    if (_M_file)
-      std::fclose(static_cast<FILE*>(_M_file));
+    const std::string token(s, len);
+#ifdef USE_MT19937
+    _M_init_pretr1(token);
+#else
+    _M_init(token);
+#endif
   }
 
-  random_device::result_type
-  random_device::_M_getval()
+  void
+  random_device::_M_fini()
   {
-#if (defined __i386__ || defined __x86_64__) && defined _GLIBCXX_X86_RDRAND
+    // _M_file == nullptr means no resources to free.
     if (!_M_file)
-      return __x86_rdrand();
-#endif
+      return;
 
-    result_type __ret;
+#if USE_LCG
+    if (_M_func == &__lcg)
+      {
+       destroy_lcg_at(_M_file);
+       return;
+      }
+#endif
 
-#ifdef _GLIBCXX_HAVE_UNISTD_H
-    auto e = read(fileno(static_cast<FILE*>(_M_file)),
-                 static_cast<void*>(&__ret), sizeof(result_type));
+#ifdef _GLIBCXX_USE_DEV_RANDOM
+#ifdef USE_POSIX_FILE_IO
+    ::close(_M_fd);
+    _M_fd = -1;
 #else
-    auto e = std::fread(static_cast<void*>(&__ret), sizeof(result_type),
-                       1, static_cast<FILE*>(_M_file));
+    std::fclose(static_cast<FILE*>(_M_file));
 #endif
-    if (e != sizeof(result_type))
-      __throw_runtime_error(__N("random_device could not read enough bytes"));
+    _M_file = nullptr;
+#endif
+  }
+
+  random_device::result_type
+  random_device::_M_getval()
+  {
+#ifdef USE_MT19937
+    return _M_mt();
+#else
 
-    return __ret;
+    if (_M_func)
+      return _M_func(_M_file);
+
+    result_type ret;
+    void* p = &ret;
+    size_t n = sizeof(result_type);
+#ifdef USE_POSIX_FILE_IO
+    do
+      {
+       const int e = ::read(_M_fd, p, n);
+       if (e > 0)
+         {
+           n -= e;
+           p = static_cast<char*>(p) + e;
+         }
+       else if (e != -1 || errno != EINTR)
+         __throw_syserr(errno, __N("random_device could not be read"));
+      }
+    while (n > 0);
+#else // USE_POSIX_FILE_IO
+    const size_t e = std::fread(p, n, 1, static_cast<FILE*>(_M_file));
+    if (e != 1)
+      __throw_runtime_error(__N("random_device could not be read"));
+#endif // USE_POSIX_FILE_IO
+
+    return ret;
+#endif // USE_MT19937
   }
 
+  // Only called by code compiled against old releases of libstdc++.
+  // Forward the call to _M_getval() and let it decide what to do.
   random_device::result_type
   random_device::_M_getval_pretr1()
+  { return _M_getval(); }
+
+  double
+  random_device::_M_getentropy() const noexcept
   {
-    return _M_mt();
+    const int max = sizeof(result_type) * __CHAR_BIT__;
+
+    switch(which_source(_M_func, _M_file))
+    {
+    case rdrand:
+    case rdseed:
+    case darn:
+      return (double) max;
+    case arc4random:
+    case getentropy:
+      return (double) max;
+    case rand_s:
+    case prng:
+      return 0.0;
+    case device_file:
+      // handled below
+      break;
+    default:
+      return 0.0;
+    }
+
+#if defined _GLIBCXX_USE_DEV_RANDOM \
+    && defined _GLIBCXX_HAVE_SYS_IOCTL_H && defined RNDGETENTCNT
+
+#ifdef USE_POSIX_FILE_IO
+    const int fd = _M_fd;
+#else
+    const int fd = ::fileno(static_cast<FILE*>(_M_file));
+#endif
+    if (fd < 0)
+      return 0.0;
+
+    int ent;
+    if (::ioctl(fd, RNDGETENTCNT, &ent) < 0)
+      return 0.0;
+
+    if (ent < 0)
+      return 0.0;
+
+    if (ent > max)
+      ent = max;
+
+    return static_cast<double>(ent);
+#else
+    return 0.0;
+#endif // _GLIBCXX_USE_DEV_RANDOM && _GLIBCXX_HAVE_SYS_IOCTL_H && RNDGETENTCNT
   }
 
+#ifdef USE_MT19937
   template class mersenne_twister_engine<
     uint_fast32_t,
     32, 624, 397, 31,
@@ -157,5 +681,11 @@ namespace std _GLIBCXX_VISIBILITY(default)
     0xffffffffUL, 7,
     0x9d2c5680UL, 15,
     0xefc60000UL, 18, 1812433253UL>;
-}
+#endif // USE_MT19937
+
+#ifdef USE_LCG
+  template class
+    linear_congruential_engine<unsigned, 16807UL, 0UL, 2147483647UL>;
+  template struct __detail::_Mod<unsigned, 2147483647UL, 16807UL, 0UL>;
 #endif
+}