commit e18187c00b0a65f3b5ab0dc340e78516d5a4f44f Author: Jonathan Wakely Date: Wed Mar 24 13:53:22 2021 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/random then this means the std::random_device constructor always fails. In previous releases if /dev/random 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/random is available. When this happens a std::linear_congruential_engine object will be used, with a (not very good) seed. This at least libstdc++-v3/ChangeLog: * src/c++11/random.cc (USE_LCG): Define when a pseudo-random fallback is needed. [USE_LCG] (lcg_type, __lcg): New typedef 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 --git a/libstdc++-v3/src/c++11/random.cc b/libstdc++-v3/src/c++11/random.cc index 1092299e56d..6b9b40c684a 100644 --- a/libstdc++-v3/src/c++11/random.cc +++ b/libstdc++-v3/src/c++11/random.cc @@ -42,6 +42,7 @@ #include #include #include // For std::isdigit. +#include // For std::time. #if defined _GLIBCXX_HAVE_UNISTD_H && defined _GLIBCXX_HAVE_FCNTL_H # include @@ -66,9 +67,14 @@ # include #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 @@ -136,6 +142,19 @@ namespace std _GLIBCXX_VISIBILITY(default) return val; } #endif + +#if USE_LCG + // Same as std::minstd_rand0 but using unsigned not uint_fast32_t. + using lcg_type + = std::linear_congruential_engine; + + unsigned int + __lcg(void* ptr) + { + auto& lcg = *static_cast(ptr); + return lcg(); + } +#endif } void @@ -152,25 +171,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,22 +201,25 @@ 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: + if (which | rand_s) { _M_func = &__winxp_rand_s; return; } #endif // _GLIBCXX_USE_CRT_RAND_S + #ifdef USE_RDSEED - case rdseed: + if (which | rdseed) { unsigned int eax, ebx, ecx, edx; // Check availability of cpuid and, for now at least, also the @@ -231,15 +244,11 @@ namespace std _GLIBCXX_VISIBILITY(default) 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]]; } #endif // USE_RDSEED + #ifdef USE_RDRAND - case rdrand: + if (which | rdrand) { unsigned int eax, ebx, ecx, edx; // Check availability of cpuid and, for now at least, also the @@ -255,15 +264,11 @@ namespace std _GLIBCXX_VISIBILITY(default) 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]]; } #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); @@ -278,12 +283,25 @@ namespace std _GLIBCXX_VISIBILITY(default) 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 PRNG with lame seed. This should be + // the last option, so we try everything else first for "default". + if (which | prng) + { + static_assert(sizeof(lcg_type) <= sizeof(_M_fd), ""); + static_assert(alignof(lcg_type) <= alignof(_M_fd), ""); + unsigned seed = reinterpret_cast(this) >> 2; + seed ^= static_cast(std::time(nullptr)); + _M_file = ::new(&_M_fd) lcg_type(seed); + _M_func = &__lcg; + return; } +#endif + std::__throw_runtime_error( __N("random_device::random_device(const std::string&):" " device not available")); @@ -297,7 +315,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; @@ -351,10 +369,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; 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 @@ // . // { dg-do run { target c++11 } } -// { dg-require-effective-target random_device } #include #include +#include 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..77bb7fa2d54 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 #include #include +#include -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) { 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 #include #include +#include void test01() @@ -50,41 +51,27 @@ 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 ); } 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) - { 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..c8323078492 100644 --- a/libstdc++-v3/testsuite/util/testsuite_random.h +++ b/libstdc++-v3/testsuite/util/testsuite_random.h @@ -197,6 +197,19 @@ 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 (...) { + std::printf("random_device(\"%s\") not available\n", token); + return false; + } + } + } // namespace __gnu_test #endif // #ifndef _GLIBCXX_TESTSUITE_RANDOM_H