-
Notifications
You must be signed in to change notification settings - Fork 12k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
linear_congruential_engine: Fixes for __lce_alg_picker #81080
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write If you have received no comments on your PR for a week, you can request a review If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-libcxx Author: None (LRFLEW) ChangesThis fixes two major mistakes in the implementation of The first mistake is in the definition of The second mistake is in the static assert. The original static assert erroneously included an unnecessary This should fix all the current instances where Full diff: https://github.com/llvm/llvm-project/pull/81080.diff 1 Files Affected:
diff --git a/libcxx/include/__random/linear_congruential_engine.h b/libcxx/include/__random/linear_congruential_engine.h
index 51f6b248d8f974..5f2f845c98b141 100644
--- a/libcxx/include/__random/linear_congruential_engine.h
+++ b/libcxx/include/__random/linear_congruential_engine.h
@@ -31,10 +31,10 @@ template <unsigned long long __a,
unsigned long long __m,
unsigned long long _Mp,
bool _MightOverflow = (__a != 0 && __m != 0 && __m - 1 > (_Mp - __c) / __a),
- bool _OverflowOK = ((__m | (__m - 1)) > __m), // m = 2^n
- bool _SchrageOK = (__a != 0 && __m != 0 && __m % __a <= __m / __a)> // r <= q
+ bool _OverflowOK = ((__m & (__m - 1)) == 0ull), // m = 2^n
+ bool _SchrageOK = (__a != 0 && __m != 0 && __m % __a <= __m / __a)> // r <= q
struct __lce_alg_picker {
- static_assert(__a != 0 || __m != 0 || !_MightOverflow || _OverflowOK || _SchrageOK,
+ static_assert(!_MightOverflow || _OverflowOK || _SchrageOK,
"The current values of a, c, and m cannot generate a number "
"within bounds of linear_congruential_engine.");
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
I don't know why the formatter wants the comments to be unaligned (seems like a bizarre choice to me), but if that's what the project wants, I'll make the change. |
Looks like the failed tests are because the tests in |
I've pushed some updated regression tests to account for the build failures. I reorganized some of the tests in I also went ahead and added a specific regression test for the |
a8eb596
to
7edc51a
Compare
libcxx/test/std/numerics/rand/rand.eng/rand.eng.lcong/alg.pass.cpp
Outdated
Show resolved
Hide resolved
libcxx/test/std/numerics/rand/rand.eng/rand.eng.lcong/alg.pass.cpp
Outdated
Show resolved
Hide resolved
172e790
to
87e3c8d
Compare
Fixes _OverflowOK and the static_assert to actually check for what they're intended to check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fix, I think this LGTM with a green CI -- I don't have much to add.
Let's add a release note explaining that we changed the value returned by this utility to be Standards conforming, but that it's technically an ABI break to do that. I think this falls within the realm of what is reasonable to change, especially since the status quo is that we disagree with other implementations.
I looked into the CI failures, and they all seem to say "The runner has received a shutdown signal." I'm guessing they timed out from the CI as a whole taking too long. I don't see anything I need to address so far, so those tasks may just need to be restarted. I don't think I have the permissions needed to restart them, so I don't think I can do anything about it myself right now. |
Thanks for working on this! I'll restart the failed jobs. |
@LRFLEW Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested Please check whether problems have been caused by your change specifically, as How to do this, and the rest of the post-merge process, is covered in detail here. If your change does cause a problem, it may be reverted, or you can revert it yourself. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! |
…low (#81583) This PR is a followup to #81080. This PR makes two major changes to how the LCG operation is computed: The first is that I added an additional case where `ax + c` might overflow the intermediate variable, but `ax` by itself won't. In this case, it's much better to use `(ax mod m) + c mod m` than the previous behavior of falling back to Schrage's algorithm. The addition modulo is done in the same way as when using Schrage's algorithm (i.e. `x += c - (x >= m - c)*m`), but the multiplication modulo is calculated directly, which is faster. The second is that I added handling for the case where the `ax` intermediate might overflow, but Schrage's algorithm doesn't apply (i.e. r > q). In this case, the only real option is to increase the precision of the intermediate values. The good news is that - for `x`, `a`, and `c` being n-bit values - `ax + c` will never overflow a 2n-bit intermediary, meaning this promotion can only happen once, and will always be able to use the simplest implementation. This is already the case for 16-bit LCGs, as libcxx chooses to compute them with 32-bit intermediate values. For 32-bit LCGs, I simply added code similar to the 16-bit case to use the existing 64-bit implementations. Lastly, for 64-bit LCGs, I wrote a case that calculates it using `unsigned __int128` if it is available to use. While this implementation covers a *lot* of the missing cases from #81080, this still won't compile **every** possible `linear_congruential_engine`. Specifically, if `a`, `c`, and `m` are chosen such that it needs 128-bit integers, but the platform doesn't support `__int128` (eg. 32-bit x86), then it will fail to compile. However, this is a fairly rare case to see actually used, and libcxx would be in good company with this, as [libstdc++ also fails to compile under these circumstances](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87744). Fixing **this** gap would require even **more** work of further complexity, so that would probably be best handled by a different PR (I'll put more details on what that PR would entail in a comment).
…low (llvm#81583) This PR is a followup to llvm#81080. This PR makes two major changes to how the LCG operation is computed: The first is that I added an additional case where `ax + c` might overflow the intermediate variable, but `ax` by itself won't. In this case, it's much better to use `(ax mod m) + c mod m` than the previous behavior of falling back to Schrage's algorithm. The addition modulo is done in the same way as when using Schrage's algorithm (i.e. `x += c - (x >= m - c)*m`), but the multiplication modulo is calculated directly, which is faster. The second is that I added handling for the case where the `ax` intermediate might overflow, but Schrage's algorithm doesn't apply (i.e. r > q). In this case, the only real option is to increase the precision of the intermediate values. The good news is that - for `x`, `a`, and `c` being n-bit values - `ax + c` will never overflow a 2n-bit intermediary, meaning this promotion can only happen once, and will always be able to use the simplest implementation. This is already the case for 16-bit LCGs, as libcxx chooses to compute them with 32-bit intermediate values. For 32-bit LCGs, I simply added code similar to the 16-bit case to use the existing 64-bit implementations. Lastly, for 64-bit LCGs, I wrote a case that calculates it using `unsigned __int128` if it is available to use. While this implementation covers a *lot* of the missing cases from llvm#81080, this still won't compile **every** possible `linear_congruential_engine`. Specifically, if `a`, `c`, and `m` are chosen such that it needs 128-bit integers, but the platform doesn't support `__int128` (eg. 32-bit x86), then it will fail to compile. However, this is a fairly rare case to see actually used, and libcxx would be in good company with this, as [libstdc++ also fails to compile under these circumstances](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87744). Fixing **this** gap would require even **more** work of further complexity, so that would probably be best handled by a different PR (I'll put more details on what that PR would entail in a comment).
This fixes two major mistakes in the implementation of
linear_congruential_engine
that allowed it to produce incorrect output. Specifically, these mistakes are in__lce_alg_picker
, which is used to determine whether Schrage's algorithm is valid and needed.The first mistake is in the definition of
_OverflowOK
. The code comment and the description of D65041 both indicate that it's supposed to be true iffm
is a power of two. However, the definition used does not work out to that, and instead is true wheneverm
is even. This could result inlinear_congruential_engine
using an invalid implementation, as it would incorrectly assume that any integer overflow can't change the result. I changed the implementation to one that accurately checks ifm
is a power of two. Technically, this implementation has an edge case where it considers0
to be a power of two, but in this case this is actually accurate behavior, asm = 0
indicates a modulus of 2^w where w is the size ofresult_type
in bits, which is a power of two.The second mistake is in the static assert. The original static assert erroneously included an unnecessary
a != 0 || m != 0
. Combined with the|| !_MightOverflow
, this actually resulted in the static assert being impossible to fail. Applying De Morgan's law and expanding_MightOverflow
gives that the only way this static assert can be triggered is ifa == 0 && m == 0 && a != 0 && m != 0 && ...
, which clearly cannot be true. I simply removed the explicit checks againsta
andm
, as the intended checks are already included in_MightOverflow
and_SchrageOK
, and their inclusion doesn't provide any obvious semantic benefit.This should fix all the current instances where
linear_congruential_engine
uses an invalid implementation. This technically isn't a complete implementation, though, since the static assert will cause some instantiations oflinear_congruential_engine
not disallowed by the standard from compiling. However, this should still be an improvement, as all compiling instantiations oflinear_congruential_engine
should use a valid implementation. Fixing the cases where the static assert triggers will require adding additional implementations, some of which will be fairly non-trivial, so I'd rather leave those for another PR so they don't hold up these more important fixes.Fixes #33554