public inbox for libstdc++-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r11-7867] libstdc++: Add PRNG fallback to std::random_device
@ 2021-03-26 19:12 Jonathan Wakely
  0 siblings, 0 replies; only message in thread
From: Jonathan Wakely @ 2021-03-26 19:12 UTC (permalink / raw)
  To: gcc-cvs, libstdc++-cvs

https://gcc.gnu.org/g:5f070ba29803c99a5fe94ed7632d7b8c55593df3

commit r11-7867-g5f070ba29803c99a5fe94ed7632d7b8c55593df3
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Mar 26 18:39:49 2021 +0000

    libstdc++: Add PRNG fallback to std::random_device
    
    This makes std::random_device usable on VxWorks when running on older
    x86 hardware. Since the r10-728 fix for PR libstdc++/85494 the library
    will use the new code unconditionally on x86, but the cpuid checks for
    RDSEED and RDRAND can fail at runtime, depending on the hardware where
    the code is executing. If the OS does not provide /dev/urandom then this
    means the std::random_device constructor always fails. In previous
    releases if /dev/urandom is unavailable then std::mt19937 was used
    unconditionally.
    
    This patch adds a fallback for the case where the runtime cpuid checks
    for x86 hardware instructions fail, and no /dev/urandom is available.
    When this happens a std::linear_congruential_engine object will be used,
    with a seed based on hashing the engine's address and the current time.
    Distinct std::random_device objects will use different seeds, unless an
    object is created and destroyed and a new object created at the same
    memory location within the clock tick. This is not great, but is better
    than always throwing from the constructor, and better than always using
    std::mt19937 with the same seed (as GCC 9 and earlier do).
    
    libstdc++-v3/ChangeLog:
    
            * src/c++11/random.cc (USE_LCG): Define when a pseudo-random
            fallback is needed.
            [USE_LCG] (bad_seed, construct_lcg_at, destroy_lcg_at, __lcg):
            New helper functions and callback.
            (random_device::_M_init): Add 'prng' and 'all' enumerators.
            Replace switch with fallthrough with a series of 'if' statements.
            [USE_LCG]: Construct an lcg_type engine and use __lcg when cpuid
            checks fail.
            (random_device::_M_init_pretr1) [USE_MT19937]: Accept "prng"
            token.
            (random_device::_M_getval): Check for callback unconditionally
            and always pass _M_file pointer.
            * testsuite/26_numerics/random/random_device/85494.cc: Remove
            effective-target check. Use new random_device_available helper.
            * testsuite/26_numerics/random/random_device/94087.cc: Likewise.
            * testsuite/26_numerics/random/random_device/cons/default-cow.cc:
            Remove effective-target check.
            * testsuite/26_numerics/random/random_device/cons/default.cc:
            Likewise.
            * testsuite/26_numerics/random/random_device/cons/token.cc: Use
            new random_device_available helper. Test "prng" token.
            * testsuite/util/testsuite_random.h (random_device_available):
            New helper function.

Diff:
---
 libstdc++-v3/src/c++11/random.cc                   | 252 +++++++++++++--------
 .../26_numerics/random/random_device/85494.cc      |   9 +-
 .../26_numerics/random/random_device/94087.cc      |  15 +-
 .../random/random_device/cons/default-cow.cc       |   1 -
 .../random/random_device/cons/default.cc           |   1 -
 .../26_numerics/random/random_device/cons/token.cc |  27 +--
 libstdc++-v3/testsuite/util/testsuite_random.h     |  12 +
 7 files changed, 189 insertions(+), 128 deletions(-)

diff --git a/libstdc++-v3/src/c++11/random.cc b/libstdc++-v3/src/c++11/random.cc
index 1092299e56d..44b9f30e4a9 100644
--- a/libstdc++-v3/src/c++11/random.cc
+++ b/libstdc++-v3/src/c++11/random.cc
@@ -66,14 +66,23 @@
 # include <stdlib.h>
 #endif
 
-#if defined USE_RDRAND || defined USE_RDSEED \
-  || defined _GLIBCXX_USE_CRT_RAND_S || defined _GLIBCXX_USE_DEV_RANDOM
+#if defined _GLIBCXX_USE_CRT_RAND_S || defined _GLIBCXX_USE_DEV_RANDOM
+// The OS provides a source of randomness we can use.
 # pragma GCC poison _M_mt
+#elif defined USE_RDRAND || defined USE_RDSEED
+// 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
@@ -136,6 +145,53 @@ namespace std _GLIBCXX_VISIBILITY(default)
       return val;
     }
 #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
   }
 
   void
@@ -152,25 +208,16 @@ namespace std _GLIBCXX_VISIBILITY(default)
     _M_fd = -1;
 
     const char* fname [[gnu::unused]] = nullptr;
-    bool default_token [[gnu::unused]] = false;
 
-    enum { rand_s, rdseed, rdrand, device_file } which;
+    enum {
+	rand_s = 1, rdseed = 2, rdrand = 4, device_file = 8, prng = 16,
+	any = 0xffff
+    } which;
 
     if (token == "default")
       {
-	default_token = true;
+	which = any;
 	fname = "/dev/urandom";
-#if defined _GLIBCXX_USE_CRT_RAND_S
-	which = rand_s;
-#elif defined USE_RDSEED
-	which = rdseed;
-#elif defined USE_RDRAND
-	which = rdrand;
-#elif defined _GLIBCXX_USE_DEV_RANDOM
-	which = device_file;
-#else
-# error "either define USE_MT19937 above or set the default device here"
-#endif
       }
 #ifdef USE_RDSEED
     else if (token == "rdseed")
@@ -191,99 +238,104 @@ namespace std _GLIBCXX_VISIBILITY(default)
 	which = device_file;
       }
 #endif // _GLIBCXX_USE_DEV_RANDOM
+#ifdef USE_LCG
+    else if (token == "prng")
+      which = prng;
+#endif
     else
       std::__throw_runtime_error(
 	  __N("random_device::random_device(const std::string&):"
 	      " unsupported token"));
 
-    switch (which)
-    {
 #ifdef _GLIBCXX_USE_CRT_RAND_S
-      case rand_s:
-      {
-	_M_func = &__winxp_rand_s;
-	return;
-      }
+    if (which & rand_s)
+    {
+      _M_func = &__winxp_rand_s;
+      return;
+    }
 #endif // _GLIBCXX_USE_CRT_RAND_S
+
 #ifdef USE_RDSEED
-      case 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)
-	      {
+    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;
-		  }
+	      // 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;
-	      }
-	  }
-	// If rdseed was explicitly requested then we're done here.
-	if (!default_token)
-	  break;
-	// Otherwise fall through to try the next available option.
-	[[gnu::fallthrough]];
-      }
+	      _M_func = &__x86_rdseed;
+	      return;
+	    }
+	}
+    }
 #endif // USE_RDSEED
+
 #ifdef USE_RDRAND
-      case 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;
-	      }
-	  }
-	// If rdrand was explicitly requested then we're done here.
-	if (!default_token)
-	  break;
-	// Otherwise fall through to try the next available option.
-	[[gnu::fallthrough]];
-      }
+    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;
+	    }
+	}
+    }
 #endif // USE_RDRAND
+
 #ifdef _GLIBCXX_USE_DEV_RANDOM
-      case device_file:
-      {
+    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)
+      _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
-	[[gnu::fallthrough]];
-      }
+    }
 #endif // _GLIBCXX_USE_DEV_RANDOM
-      default:
-      { }
+
+#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
+
     std::__throw_runtime_error(
 	__N("random_device::random_device(const std::string&):"
 	    " device not available"));
@@ -297,7 +349,7 @@ namespace std _GLIBCXX_VISIBILITY(default)
   {
 #ifdef USE_MT19937
     unsigned long seed = 5489UL;
-    if (token != "default" && token != "mt19937")
+    if (token != "default" && token != "mt19937" && token != "prng")
       {
 	const char* nptr = token.c_str();
 	char* endptr;
@@ -335,6 +387,14 @@ namespace std _GLIBCXX_VISIBILITY(default)
     if (!_M_file)
       return;
 
+#if USE_LCG
+    if (_M_func == &__lcg)
+      {
+	destroy_lcg_at(_M_file);
+	return;
+      }
+#endif
+
 #ifdef USE_POSIX_FILE_IO
     ::close(_M_fd);
     _M_fd = -1;
@@ -351,10 +411,8 @@ namespace std _GLIBCXX_VISIBILITY(default)
     return _M_mt();
 #else
 
-#if defined USE_RDRAND || defined USE_RDSEED || defined _GLIBCXX_USE_CRT_RAND_S
     if (_M_func)
-      return _M_func(nullptr);
-#endif
+      return _M_func(_M_file);
 
     result_type ret;
     void* p = &ret;
@@ -430,5 +488,11 @@ namespace std _GLIBCXX_VISIBILITY(default)
     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
 }
 #endif // _GLIBCXX_USE_C99_STDINT_TR1
diff --git a/libstdc++-v3/testsuite/26_numerics/random/random_device/85494.cc b/libstdc++-v3/testsuite/26_numerics/random/random_device/85494.cc
index 8bfb1fa71bf..80cb912b587 100644
--- a/libstdc++-v3/testsuite/26_numerics/random/random_device/85494.cc
+++ b/libstdc++-v3/testsuite/26_numerics/random/random_device/85494.cc
@@ -16,14 +16,21 @@
 // <http://www.gnu.org/licenses/>.
 
 // { dg-do run { target c++11 } }
-// { dg-require-effective-target random_device }
 
 #include <random>
 #include <testsuite_hooks.h>
+#include <testsuite_random.h>
 
 void
 test01()
 {
+  if (__gnu_test::random_device_available("mt19937"))
+  {
+    // std::random_device uses a Mersenne Twister with default seed,
+    // and the test below will fail.  No point trying to test it.
+    return;
+  }
+
   unsigned v1[3], v2[3];
   std::random_device d1, d2;
   for (auto& v : v1)
diff --git a/libstdc++-v3/testsuite/26_numerics/random/random_device/94087.cc b/libstdc++-v3/testsuite/26_numerics/random/random_device/94087.cc
index c77917fb680..7ff672e7024 100644
--- a/libstdc++-v3/testsuite/26_numerics/random/random_device/94087.cc
+++ b/libstdc++-v3/testsuite/26_numerics/random/random_device/94087.cc
@@ -25,18 +25,9 @@
 #include <memory>
 #include <thread>
 #include <cstdio>
+#include <testsuite_random.h>
 
-bool
-random_device_available(const char* token) noexcept
-{
-  try {
-    std::random_device dev(token);
-    return true;
-  } catch (...) {
-    std::printf("random_device(\"%s\") not available\n", token);
-    return false;
-  }
-}
+using __gnu_test::random_device_available;
 
 void read_random_device(const char* token, int iterations)
 {
@@ -59,5 +50,7 @@ int main() {
       for (auto& w : workers)
 	w.join();
     }
+    else
+      std::printf("random_device(\"%s\") not available\n", dev);
   }
 }
diff --git a/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default-cow.cc b/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default-cow.cc
index ef35773e009..489a0a0965f 100644
--- a/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default-cow.cc
+++ b/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default-cow.cc
@@ -1,6 +1,5 @@
 // { dg-options "-D_GLIBCXX_USE_CXX11_ABI=0" }
 // { dg-do run { target c++11 } }
-// { dg-require-effective-target random_device }
 // { dg-require-cstdint "" }
 //
 // Copyright (C) 2019-2021 Free Software Foundation, Inc.
diff --git a/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default.cc b/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default.cc
index 72756e245d5..79e044ece42 100644
--- a/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default.cc
+++ b/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/default.cc
@@ -1,5 +1,4 @@
 // { dg-do run { target c++11 } }
-// { dg-require-effective-target random_device }
 // { dg-require-cstdint "" }
 //
 // 2008-11-24  Edward M. Smith-Rowland <3dw4rd@verizon.net>
diff --git a/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/token.cc b/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/token.cc
index defb8d58c58..aeb7403e830 100644
--- a/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/token.cc
+++ b/libstdc++-v3/testsuite/26_numerics/random/random_device/cons/token.cc
@@ -25,6 +25,7 @@
 #include <random>
 #include <stdexcept>
 #include <testsuite_hooks.h>
+#include <testsuite_random.h>
 
 void
 test01()
@@ -50,19 +51,14 @@ test03()
 {
   // At least one of these tokens should be valid.
   const std::string tokens[] = {
-    "rdseed", "rdrand", "rand_s", "/dev/urandom", "/dev/random", "mt19937"
+    "rdseed", "rdrand", "rand_s", "/dev/urandom", "/dev/random", "mt19937",
+    "prng"
   };
   int count = 0;
   for (const std::string& token : tokens)
   {
-    try
-    {
-      std::random_device x(token);
+    if (__gnu_test::random_device_available(token))
       ++count;
-    }
-    catch (const std::runtime_error&)
-    {
-    }
   }
   VERIFY( count != 0 );
 }
@@ -70,21 +66,12 @@ test03()
 void
 test04()
 {
-  bool can_use_mt19937 = true;
-  std::random_device::result_type xval;
-  try
+  if (__gnu_test::random_device_available("mt19937"))
   {
     std::random_device x("mt19937");
-    xval = x();
-  }
-  catch (const std::runtime_error&)
-  {
-    can_use_mt19937 = false;
-  }
+    std::random_device::result_type xval = x();
 
-  // If "mt19937" is a valid token then numeric seeds should be too.
-  if (can_use_mt19937)
-  {
+    // If "mt19937" is a valid token then numeric seeds should be too.
     std::random_device x1("0");
     std::random_device x2("1234");
     std::random_device x3("0xc0fefe");
diff --git a/libstdc++-v3/testsuite/util/testsuite_random.h b/libstdc++-v3/testsuite/util/testsuite_random.h
index 0b670bfb771..8fba2688bc0 100644
--- a/libstdc++-v3/testsuite/util/testsuite_random.h
+++ b/libstdc++-v3/testsuite/util/testsuite_random.h
@@ -197,6 +197,18 @@ namespace __gnu_test
   }
 #endif
 
+  // Check whether TOKEN can construct a std::random_device successfully.
+  inline bool
+  random_device_available(const std::string& token) noexcept
+  {
+    try {
+      std::random_device dev(token);
+      return true;
+    } catch (...) {
+      return false;
+    }
+  }
+
 } // namespace __gnu_test
 
 #endif // #ifndef _GLIBCXX_TESTSUITE_RANDOM_H


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2021-03-26 19:12 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-26 19:12 [gcc r11-7867] libstdc++: Add PRNG fallback to std::random_device Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).