-
Notifications
You must be signed in to change notification settings - Fork 69
Add USE_FLOAT_EXCEPTIONS to enable floating point exceptions
#1451
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
Conversation
…vars to enable floating point exceptions
|
I have not tested the Apple and MSVC code. |
f05b27f to
4993a7f
Compare
USE_DEBUG_FPE to enable floating point exceptionsUSE_FLOAT_EXCEPTIONS to enable floating point exceptions
4993a7f to
8687854
Compare
|
I renamed the cmake option to |
b878d3a to
7c60e69
Compare
src/engine/framework/System.cpp
Outdated
| static void SetFloatingPointExceptions() | ||
| { | ||
| // Must be done after Sys::Init() to read cvars from command line. | ||
| #if defined(DAEMON_USE_FLOAT_EXCEPTIONS_AVAILABLE) |
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.
Our code style is to have these on the left side and never indent them based on indentation of non-preprocessor code.
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.
It's bad practice within a block, really.
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.
What? This style is used very consistently in our code and is common in other code bases too. Preprocessor and non-preprocessor lines do not syntactically nest with each other, so it makes a sort of sense that neither one affects the other's indentation.
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.
When the ifdef is just a precompiled replacement for a test that would be doable at run time, it's better to keep them readable the same way, really.
Not indenting some ifdef is only a good solution when something can't be indented properly, like in a single operation, function call with optional parameters, or things like that…
We better use nested ifdef when it's possible, it makes things much more readable.
I modified the code to not indent the first level of ifdef, but keeping the other indentations makes it it much more readable.
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.
This has been done the same way throughout the history of the codebase so we should keep doing it that way instead of worsening the mixture of styles, regardless of some arguments that a different one would be better if starting from scratch.
src/engine/framework/System.cpp
Outdated
| unsigned int exceptions = 0; | ||
| #endif | ||
|
|
||
| // Operations with NaN. |
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.
Not very accurate, cos(1.001) also does this for example
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.
Are you sure?
Warn: Computing √-1…
Warn: Result of √-1: -nan
Warn: Computing cos(1.001)…
Warn: Result of cos(1.001): 0.539
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.
And the invalid exception isn't caught with cos(1.001), but is caught with sqrt(-1).
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.
Typo, I meant acos (inverse cosine)
cmake/DaemonFlags.cmake
Outdated
| endif() | ||
|
|
||
| # Compiler options | ||
| option(USE_FLOAT_EXCEPTIONS "Use floating point exceptions" OFF) |
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.
The use description of this is not quite accurate. It doesn't turn on exceptions; it alters compiler options in a way that makes the exceptions more likely to be useful. Maybe you could call it USE_FLOAT_DEBUG_MODE or something
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.
Actually I think the name is fine but it would be good to mention the cvars. Enable floating point exceptions with common.floatException.* cvars
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.
Done.
7c60e69 to
a65cdc7
Compare
|
Hmm, my mac doesn't support more than Catalina, so no more than macOS 10.15.7 with Clang 12.0.0, and I get these errors: So I cannot test macOS right now. |
10070cb to
4086988
Compare
|
I face a weird cmake bug, if I do that: if (USE_FLOAT_EXCEPTIONS)
message(STATUS "test true")
# Floating point exceptions requires trapping math
# to avoid false positives on architectures with SSE.
set_c_cxx_flag("-ffp-model=strict")
endif()The “test true” message is printed, but the But if I do: set_c_cxx_flag("-ffp-model=strict")
if (USE_FLOAT_EXCEPTIONS)
message(STATUS "test true")
# Floating point exceptions requires trapping math
# to avoid false positives on architectures with SSE.
endif()The flag is set. So to sum it up:
but the whole combination doesn't work… |
|
If I do instead: if (USE_FLOAT_EXCEPTIONS)
message(STATUS "test true")
try_c_cxx_flag(FFP_MODEL_STRICT "-ffp-model=strict")
# Floating point exceptions requires trapping math
# to avoid false positives on architectures with SSE.
endif()I get this printed: But the flag is not added to the compiler command line. On the contrary if I do that: try_c_cxx_flag(FFP_MODEL_STRICT "-ffp-model=strict")
if (USE_FLOAT_EXCEPTIONS)
message(STATUS "test true")
# Floating point exceptions requires trapping math
# to avoid false positives on architectures with SSE.
endif()I get this printed: And the flag is added to the compiler command line. |
8417ef8 to
9fa7434
Compare
9e5dd3a to
da7a0e5
Compare
|
I only tested on Linux. I can't test on macOS:
I don't use MSVC. |
|
Note: people says on the Internet that on macOS this code will only work on amd64, not on arm64, I haven't implemented the macOS arm64 workaround: https://stackoverflow.com/a/71792418 |
src/engine/framework/System.cpp
Outdated
| _controlfp_s(¤t, exceptions, _MCW_EM); | ||
| #endif | ||
|
|
||
| if (common_floatExceptions_test.Get()) |
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.
The injectFault command can already do this. If more types of exception are needed they should be added there
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.
The purpose of this test is not only to test if the exception is caught when raised, but also to make sure that when doing the mistake for real the exception is raised.
As reported on chat, I noticed that doesn't raise an exception:
float f = std::numeric_limits<float>::max();
Log::Warn("Result of 2×%.0f: %.0f", f, 2*f);But doing that raises an exception:
volatile float f = std::numeric_limits<float>::max();
Log::Warn("Result of 2×%.0f: %.0f", static_cast<float>(f), 2*f);I want to test that.
We can also do some injectFault calls, but to me that should only be done after those tests are done (basically to test that the exception catching works even if the error did not happened, so we can investigate while the error did not happened).
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.
An injectFault fault flavor need not be guaranteed to crash in all circumstances. I don't see any reason why this stuff wouldn't fit well there.
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.
The injectFault floatdiv doesn't work, likely because a volatile doesn't guarantee dead code to be removed.
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.
Not, it's because 0 / 0 is invalid error, not divByZero error. That is 1 / 0 that is divByZero error.
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.
I fixed and extended injectFault.
| vec_t length = DotProduct( v, v ); | ||
|
|
||
| VectorScale( v, ilength, v ); | ||
| #if DAEMON_USE_FLOAT_EXCEPTIONS |
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.
The program should not change its behavior like that when the debugging thing is used. That nullifies the whole point of it.
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.
Not changing this behavior nullifies the whole point of it.
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.
The whole point of it is:
- Making possible to debug everything that is not
Q_rsqrt_fast()as called byVectorNormalizeFast().
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.
The whole point of it is:
* Making possible to debug everything that is not `Q_rsqrt_fast()` as called by `VectorNormalizeFast()`.
Why? There should be some explanation for this.
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.
What I mean is that it can be fine to purposely ignore one specific hack, instead of making impossible to debug anything else because of one hack.
Anyway, this code raises other questions:
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.
So, answers were:
I am not a Quake 3 developer but I will give my best guess.
- Were Quake 3 developers correct to consider it was acceptable to get garbage when the length is zero?
Yes for VectorNormalizeFast, no for VectorNormalize.
- If they were right to consider it acceptable to get that garbage, is it still correct to get
NaNinstead of that garbage?Yes
So if we consider it OK to get NaN there when length is 0, we better make the NaN detector purposely ignore this one.
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.
I just want to see some code comment about why this happens.
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.
I tried to see for myself and apparently some models trigger it in R_TBNtoQtangents.
da7a0e5 to
0c354c4
Compare
|
Using MacOS Sequoia… the symbols were still missing, and I discovered the symbols I was missing were arm64-only. 🤦♀️️ Now I have a code that works for amd64 on macOS. It probably already works for arm64 on macOS as I actually ported an arm64 code to also support amd64. |
|
See first post for implementation and test status. |
0c354c4 to
62ff53c
Compare
62ff53c to
0c6e857
Compare
0c6e857 to
644d8a8
Compare
…d, also fix “floatdiv”
e09077b to
60aac27
Compare
|
So, this looks ready to me. I cannot test macOS arm64, neither MSVC, but not only that code is not build by default, but when built, the features are not enabled by default, so that should not prevent the merge: people can test and improve later. |
|
The Windows code works when built with MinGW and run on Wine, and the code is exactly the same for building with MSVC. |
9cbb988 to
08b22a0
Compare
| vec_t length = DotProduct( v, v ); | ||
|
|
||
| VectorScale( v, ilength, v ); | ||
| #if DAEMON_USE_FLOAT_EXCEPTIONS |
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.
I just want to see some code comment about why this happens.
src/engine/framework/System.cpp
Outdated
| #endif | ||
|
|
||
| #if defined(DAEMON_USE_ARCH_INTRINSICS_i686_sse) | ||
| sse_exceptions |= _MM_MASK_INVALID; |
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.
amd64 Mac did not work (though arm64 did). I didn't get any effect from the 3 fault injectors.
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.
On my end Mac amd64 still works, you suggested on IRC that maybe Rosetta isn't emulating it.
86b6a78 to
1a41a31
Compare
| vec_t length = DotProduct( v, v ); | ||
|
|
||
| VectorScale( v, ilength, v ); | ||
| #if DAEMON_USE_FLOAT_EXCEPTIONS |
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.
I tried to see for myself and apparently some models trigger it in R_TBNtoQtangents.
051b3a1 to
6b0062c
Compare
|
LGTM |
USE_FLOAT_EXCEPTIONSto enable floating point exceptions (disabled by default)USE_FAST_MATHto enable or disable fast math (enabled by default)When
USE_FLOAT_EXCEPTIONSis used, nothing is done unless some of those cvars are enabled:common.floatExceptions.invalidcommon.floatExceptions.divByZerocommon.floatExceptions.overflowThe
USE_FLOAT_EXCEPTIONSoption is required to specialize the build to make it possible to enable them.The
common.floatExceptions.testcvar enables some code doing bad floating point operations on purpose to test how they are handled.Status: