[PATCH v2] libstdc++: Prevent hanging of uniform_int_distribution for non-conforming engines [PR118665]
Jonathan Wakely
jwakely@redhat.com
Tue Sep 1 11:37:06 GMT 2026
On Fri, 28 Aug 2026 at 07:35, Tomasz Kamiński <tkaminsk@redhat.com> wrote:
>
> Since r11-3757-g98c37d3bacbb2f the uniform_int_distribution uses Lemire's
> algorithm for engines outputting power of two range. As this algorithm
> (in-contrast to other) rejects low outputs from incoming generator, this
> change lead to hangs (due infinite loop) when distribution was mixed
> with non-uniform generator, that returned 0 constantly. Such usage has
> undefined behavior according to standard, however didn't lead to hangs
> prior to GCC 11 and other standard libraries.
>
> This patch, addresses above by changing the (slower) rejection path of the
> algorithm, to alternate between rejecting front/back value of the __prod % 2^N
> range. This still produces an uniform output, as the range of preserved
> values remain continuous and have some size.
>
> To elaborate, we notice that the values of m (__product) are necessary
> an multiplies of range size s (__range), and the shift __product >> N
> distributes them along the bucket of size of 2^N. The produced distribution
> is uniform if each 2^N contains the same number of multiplies of s, i.e.
> we are equally likely to end inisde it.
>
> The shift itself is biased, for example given s == 5 and N == 3, we
> get following buckets:
> [0, 8) -> 0, 5
> [8, 16) -> 10, 15
> [16, 24) -> 20
> [24, 32) -> 25, 30
> [32, 40) -> 35
> In that case, the 16 and 32 bucket (and corresponding 2 and 4 outputs)
> are half less likely to occur.
>
> To mitigate above we observe that any continuous range of size n * s
> (multiply of s) contains exactly n multiplies of s, so by reducing the size
> of the range from 2^N to continuous range of size that is multiply of s, we
> will produce uniform distribution. This can be simply done by rejecting
> 2^N % s values from either or both size of the range. It does not matter
> from which side they are rejected.
> As illustration the table bellow shows rejected value for s == 5, N == 3,
> the columns corresponds to buckets and rows labels +X/-Y describe rejecting
> X values from front and Y values from back.
> 0 8 16 24 32
> +3/-0 0 10 - 25 -
> +2/-1 0 15 - 25 -
> +1/-2 0 15 - 30 -
> +0/-3 5 15 - 30 -
>
> In consequence, of above the algorithm finishes successfully for any generator
> that returns same value constantly. While, it is possible to create an
> generator that would still lead to infinite loop, by alternating between low
> and high values, I believe this is inherent property on any algorithm that
> rejects some of the engine's outputs.
>
> PR libstdc++/118665
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/uniform_int_dist.h
> (uniform_int_distribution::_S_nd): Alternate between rejecting
> from beginning/end of the range.
> * testsuite/26_numerics/random/uniform_int_distribution/operators/pr118665.cc:
> New test.
> ---
> v2:
> * expands the explanation in commit description on why the output
> remains uniform
> * updates comment in code
> * introduce __back_treshold variable
> * applyes - __min from previous patch
>
> OK for trunk? Backports?
OK for trunk now. OK for backports after some soak time on trunk.
>
> libstdc++-v3/include/bits/uniform_int_dist.h | 13 ++++++++-
> .../operators/pr118665.cc | 28 +++++++++++++++++++
> 2 files changed, 40 insertions(+), 1 deletion(-)
> create mode 100644 libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/pr118665.cc
>
> diff --git a/libstdc++-v3/include/bits/uniform_int_dist.h b/libstdc++-v3/include/bits/uniform_int_dist.h
> index 072c6351e1f..5ee9945b61c 100644
> --- a/libstdc++-v3/include/bits/uniform_int_dist.h
> +++ b/libstdc++-v3/include/bits/uniform_int_dist.h
> @@ -271,9 +271,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _Up __low = _Up(__product);
> if (__low < __range)
> {
> - _Up __threshold = -__range % __range;
> + const _Up __threshold = -__range % __range;
> while (__low < __threshold)
> {
> + __product = _Wp(__g() - __min) * _Wp(__range);
> + __low = _Up(__product);
> +
> + // The algorithm is modified to alternate between rejecting
> + // from the beginning and end of the range. This guarantees
> + // that we stop for non-uniform engines that always result
> + // in values below the __threshold.
> + const _Up __back_treshold = _Up_traits::__max - __threshold;
> + if (__low <= __back_treshold)
> + break;
> +
> __product = _Wp(__g() - __min) * _Wp(__range);
> __low = _Up(__product);
> }
> diff --git a/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/pr118665.cc b/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/pr118665.cc
> new file mode 100644
> index 00000000000..979d5fdfca9
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/26_numerics/random/uniform_int_distribution/operators/pr118665.cc
> @@ -0,0 +1,28 @@
> +// { dg-do run { target c++11 } }
> +// { dg-require-cstdint "" }
> +
> +#include <random>
> +#include <testsuite_hooks.h>
> +
> +class Constant {
> +public:
> + using result_type = unsigned;
> + static constexpr result_type min() { return 0u; }
> + static constexpr result_type max() { return ~0u; }
> +
> + // always return 0
> + result_type operator()() {
> + ++calls;
> + VERIFY( calls < 1000 );
> + return 0;
> + }
> +
> + unsigned calls = 0;
> +};
> +
> +int main()
> +{
> + Constant gen;
> + std::uniform_int_distribution<int> dist(0, 42);
> + dist(gen);
> +}
> --
> 2.55.0
>
More information about the Libstdc++
mailing list