Skip to content

Conversation

@AlexGuteniev
Copy link
Contributor

@AlexGuteniev AlexGuteniev commented Oct 28, 2025

ARM64 coverage being added in #5815 drawn my attention to that numeric_limit has traps, and it is apparently defined incorrectly for some architectures.

🪤 What are traps?

See https://en.cppreference.com/w/cpp/types/numeric_limits/traps.html

traps should be true if there's an operation on a value of that type that traps.

For integers, it is zero division, it usually traps, but still does not trap for certain compiler/platform combinations. Sometimes it also happens with signed overflow on division. See below.

Hypothetically, I imagine that other signed overflows, like incrementing int containing INT_MAX, may trap, but I'm not aware if any hardware behaves like this, at least, not our target platforms.

We all know that bools are integers, but they are surprisingly exempt from zero division trap: when you divide by false, you actually divide by integer zero, so it is not bool value that traps 😕. Oh, and bool has non-value representations, but they do not trap for us either.

Floats normally don't trap. You need to both compile with /fp:except and enable these exceptions to make them trap. The trait reflects the state after program starts, so enabling by setting control word does not count.

Overall that part of the traits doesn't seem to be terribly useful feature. For platform-agnostic code, the information "it is trap" is not useful, it does not tell where is the trap and what is the trap. And for platform-specific code, you can know the behavior of exact platforms specifically without querying the trait.

➗ Integer division traps

On x64 and x86,, integer zero division triggers some interrupt, ultimately resulting in STATUS_INTEGER_DIVIDE_BY_ZERO (0xC0000094) structured exception.

On Arm64 zero integer division results in having zero at destination, and no trap.

However, MSVC inserts check for zero and brk instruction to get into the same trap 🐶.

clang-cl does not implement this behavior, and does not trap on Arm64.

INT_MIN / -1 division would also trap on x64 and x86. The exception code is STATUS_INTEGER_OVERFLOW (0xC0000095).

This integer division overflow does not trap on Arm64: MSVC does not insert any checks for that.

⚙️ Product code fix

We change the value of traps for all integers except bools on all supported architectures.

Except for Clang on Arm64, where we keep it false.

✔️ Coverage

The runtime test approach was rejected, as it needs UB to perform the actual behavior test.
So the test checks for the assumed behavior on current hardware and compiler.
It errors out on an unknown hardware.

This libc++ test still needs some fixes.

I have not checked ARM64 EC, but according to the 👮‍♂️ two policemen theorem 👮‍♀️, if it traps on x64, and traps on Arm64, then on Arm64 EC that is between both of them it also traps. And I expect from Clang to behave naturally and don't trap there.

🎃 Horror

This might be a mess when we link Clang and MSVC objects together, we define the same variable differently. On the other hand, we also mix behaviors in this case. The variable is constexpr and hence very inline, so the definition should not matter, it is expected to inline to query sites even in /Od.

@AlexGuteniev AlexGuteniev requested a review from a team as a code owner October 28, 2025 16:43
@github-project-automation github-project-automation bot moved this to Initial Review in STL Code Reviews Oct 28, 2025
@StephanTLavavej StephanTLavavej added the bug Something isn't working label Oct 28, 2025
@frederick-vs-ja
Copy link
Contributor

We all know that bools are integers, but they are surprisingly exempt from zero division trap: when you divide by false, you actually divide by integer zero, so it is not bool value that traps 😕. Oh, and bool has non-value representations, but they do not trap for us either.

Oh, however, this is arguably true for all non-promoted integer types. Due to usual arithmetic conversions, no arithmetic operation on char, short, etc. is actually performed because the operands are converted to int first. I guess an LWG issue is wanted.

@AlexGuteniev
Copy link
Contributor Author

Oh, however, this is arguably true for all non-promoted integer types.

Yes, @Alcaro mentioned that in Discord, and captured that in llvm/llvm-project#166053

You can argue that division by false promotes to int, therefore it's not an operation on bool that traps, but if so, char is wrong.


I guess an LWG issue is wanted.

You can try.
What I'd actually want here after thinking about this all is this trait deprecation and removal.

@frederick-vs-ja
Copy link
Contributor

Ah, I found that there's already LWG-554 which is closed as NAD. So perhaps we should make numeric_limits<I>::traps false for all non-promoted integer types.

@AlexGuteniev
Copy link
Contributor Author

Thanks, I'll mention LWG-554 in the test.

So perhaps we should make numeric_limits<I>::traps false for all non-promoted integer types.

I'm not sure. On one hand, the promotion happens. On the other, one can say that zero value does not change when it can be represented, whereas false is converted to zero.

Comment on lines +30 to +31
static_assert(traps_<char> == traps_<int> && traps_<signed char> == traps_<int> && traps_<unsigned char> == traps_<int>
&& traps_<short> == traps_<int> && traps_<unsigned short> == traps_<int>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frederick-vs-ja suggests these should not trap either, according to LWG-554 resolution interpretation.
I'm not sure.

@Alcaro
Copy link
Contributor

Alcaro commented Nov 6, 2025

  • This stuff varies depending on not only the hardware and OS (like sizeof(long)), but also the compiler (and it wouldn't surprise me if Clang adds a -ftrap-zero-div flag that emulates arm64 msvc behavior, making trapness depend on compiler arguments too)
  • traps=true says nothing about which operations trap, therefore effectively says nothing
  • traps=false doesn't say anything certain either; it considers only the program's initial state, but _controlfp can change it
  • Every trapping operation is UB, so we're reasoning about the behavior of undefined behavior
  • A typical user's intuition says that property tells whether (T)val1 some_operator (T)val2 can trap, but LWG-554 explicitly says otherwise
  • While 554's definition is technically true, it's misleading. And by that logic, std::numeric_limits<unsigned short>::is_modulo should be false, because operations on that type don't exist and therefore can't go outside the range min()..=max().
  • We can argue for three different behaviors for that property, all with straight faces

To me, those are strong arguments that the property is unimplementable and useless, and that the only correct answer is deprecation.

edit: split off 'vacuous truth' clause from previous entry, added a note about is_modulo

@frederick-vs-ja
Copy link
Contributor

  • This stuff varies depending on not only the hardware and OS (like sizeof(long)), but also the compiler (and it wouldn't surprise me if Clang adds a -ftrap-zero-div flag that emulates arm64 msvc behavior, making trapness depend on compiler arguments too)

To me the intent is to reflect such a compiler option (if any), which doesn't seem wrong. numeric_limits needs to touch implementation-specific details anyway.

  • And by that logic, std::numeric_limits::is_modulo should be false, because operations on that type don't exist and therefore can't go outside the range min()..=max().

I think by that logic std::numeric_limits<NonPromoted>::is_modulo should be vacuously true - which conflicts with the requirement that std::numeric_limits<bool>::is_modulo is false. I'm to submit an LWG issue for this.

To me, those are strong arguments that the property is unimplementable and useless, and that the only correct answer is deprecation.

To me that's implementable and useless, as traps == false doesn't make UB more well-defined. I'd like to deprecate it as it's totally unreliable to me, but I want to see the clarified rationale first...

@Alcaro
Copy link
Contributor

Alcaro commented Nov 7, 2025

To me the intent is to reflect such a compiler option (if any), which doesn't seem wrong.

Compiler flags that change numeric_limits sound scary to me. Especially with Modules enabled, since those can't #ifdef it.

But it wouldn't be the first one, so I'll concede this point.

(-fwrapv should set int's is_modulo to true, but neither libstdc++ nor libc++ do. I'll file bugs once the exact expected behavior of those traits is known, unless someone beats me to it.)

I think by that logic std::numeric_limits<NonPromoted>::is_modulo should be vacuously true

static constexpr bool is_modulo;
true if the type is modulo. A type is modulo if, for any operation involving +, -, or * on values of that type whose result would fall outside the range [min(), max()], the value returned differs from the true value by an integer multiple of max() - min() + 1.
[Example 1: is_modulo is false for signed integer types ([basic.fundamental]) unless an implementation, as an extension to this document, defines signed integer overflow to wrap. — end example]
Meaningful for all specializations.

That logic depends on whether we read 'any' as 'at least one' or 'for every possible option'.

If we can read such a simple word differently, using that word is a spec bug already; I agree with filing an LWG issue. Perhaps it should be simply changed to 'all'; vacuous all is true.

(Even without vacuous statements, 'any' is wrong word - on a hypothetical processor where multiplication overflow returns zero, (int)65536 * (int)65536 will differ from the true value by an integer multiple of max() - min() + 1.)

@seishun
Copy link

seishun commented Nov 7, 2025

[Example 1: is_modulo is false for signed integer types ([basic.fundamental]) unless an implementation, as an extension to this document, defines signed integer overflow to wrap. — end example]

If numeric_limits<int>::is_modulo is false because signed integer overflow is UB, then numeric_limits<int>::traps should be false by the same logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: Initial Review

Development

Successfully merging this pull request may close these issues.

5 participants