Skip to content
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

Add USE_FLOAT_EXCEPTIONS to enable floating point exceptions #1451

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

illwieckz
Copy link
Member

@illwieckz illwieckz commented Dec 2, 2024

  • Add USE_FLOAT_EXCEPTIONS to enable floating point exceptions (disabled by default)
  • Add USE_FAST_MATH to enable or disable fast math (enabled by default)

When USE_FLOAT_EXCEPTIONS is used, nothing is done unless some of those cvars are enabled:

  • common.floatExceptions.invalid
  • common.floatExceptions.divByZero
  • common.floatExceptions.overflow

The USE_FLOAT_EXCEPTIONS option is required to specialize the build to make it possible to enable them.

The common.floatExceptions.test cvar enables some code doing bad floating point operations on purpose to test how they are handled.

@illwieckz
Copy link
Member Author

I have not tested the Apple and MSVC code.

@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch 5 times, most recently from f05b27f to 4993a7f Compare December 3, 2024 09:00
src/engine/framework/System.cpp Outdated Show resolved Hide resolved
src/engine/framework/System.cpp Outdated Show resolved Hide resolved
src/engine/framework/System.cpp Outdated Show resolved Hide resolved
src/engine/framework/System.cpp Outdated Show resolved Hide resolved
@illwieckz illwieckz changed the title Add USE_DEBUG_FPE to enable floating point exceptions Add USE_FLOAT_EXCEPTIONS to enable floating point exceptions Dec 4, 2024
@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch from 4993a7f to 8687854 Compare December 4, 2024 08:31
@illwieckz
Copy link
Member Author

I renamed the cmake option to USE_FLOAT_EXCEPTIONS.

@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch 2 times, most recently from b878d3a to 7c60e69 Compare December 4, 2024 08:35
src/engine/framework/System.cpp Outdated Show resolved Hide resolved
static void SetFloatingPointExceptions()
{
// Must be done after Sys::Init() to read cvars from command line.
#if defined(DAEMON_USE_FLOAT_EXCEPTIONS_AVAILABLE)
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

unsigned int exceptions = 0;
#endif

// Operations with NaN.
Copy link
Member

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

Copy link
Member Author

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 

Copy link
Member Author

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).

Copy link
Member

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 Show resolved Hide resolved
@@ -148,13 +148,20 @@ else()
set(WARNMODE "no-")
endif()

# Compiler options
option(USE_FLOAT_EXCEPTIONS "Use floating point exceptions" OFF)
Copy link
Member

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

Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch from 7c60e69 to a65cdc7 Compare December 5, 2024 07:56
@illwieckz
Copy link
Member Author

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:

src/engine/framework/System.cpp:319:19: error: use of
      undeclared identifier '__fpcr_trap_invalid'
                                exceptions |= __fpcr_trap_invalid;
                                              ^
src/engine/framework/System.cpp:331:19: error: use of
      undeclared identifier '__fpcr_trap_divbyzero'
                                exceptions |= __fpcr_trap_divbyzero;
                                              ^
src/engine/framework/System.cpp:343:19: error: use of
      undeclared identifier '__fpcr_trap_overflow'
                                exceptions |= __fpcr_trap_overflow;
                                              ^
src/engine/framework/System.cpp:356:8: error: no member named
      '__fpcr' in 'fenv_t'
                        env.__fpcr = env.__fpcr | exceptions;
                        ~~~ ^
src/engine/framework/System.cpp:356:21: error: no member named
      '__fpcr' in 'fenv_t'
                        env.__fpcr = env.__fpcr | exceptions;

So I cannot test macOS right now.

@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch 3 times, most recently from 10070cb to 4086988 Compare December 5, 2024 10:12
@illwieckz
Copy link
Member Author

illwieckz commented Dec 6, 2024

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 -ffp-model=strict flag isn't set.

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:

  • I know the test is true,
  • I know the block is executed,
  • I know the function works,

but the whole combination doesn't work…

@illwieckz
Copy link
Member Author

illwieckz commented Dec 6, 2024

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:

-- test true
-- Performing Test FLAG_FFP_MODEL_STRICT
-- Performing Test FLAG_FFP_MODEL_STRICT - Success

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:

-- Performing Test FLAG_FFP_MODEL_STRICT
-- Performing Test FLAG_FFP_MODEL_STRICT - Success
-- test true

And the flag is added to the compiler command line.

@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch 3 times, most recently from 8417ef8 to 9fa7434 Compare December 20, 2024 04:39
@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch 3 times, most recently from 278fa37 to 7c8ac23 Compare January 3, 2025 18:02
@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch 2 times, most recently from 9e5dd3a to da7a0e5 Compare January 3, 2025 18:10
@illwieckz
Copy link
Member Author

illwieckz commented Jan 3, 2025

That looks ready to me. I've added a cvar named common.floatExceptions.test that enables code expected to trigger some floating point exceptions on purpose to test the exception code is working.

@illwieckz
Copy link
Member Author

I only tested on Linux.

I can't test on macOS:

  • my macOS virtual machine is too old for shipping the symbols and I don't want to update it.
  • my old mac is not allowed by Apple to get a newer macOS so I run into the same problems.

I don't use MSVC.

@illwieckz
Copy link
Member Author

illwieckz commented Jan 3, 2025

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

_controlfp_s(&current, exceptions, _MCW_EM);
#endif

if (common_floatExceptions_test.Get())
Copy link
Member

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

Copy link
Member Author

@illwieckz illwieckz Jan 4, 2025

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).

Copy link
Member

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.


VectorScale( v, ilength, v );
#if DAEMON_USE_FLOAT_EXCEPTIONS
Copy link
Member

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.

Copy link
Member Author

@illwieckz illwieckz Jan 3, 2025

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.

Copy link
Member Author

@illwieckz illwieckz Jan 3, 2025

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().

Copy link
Member

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.

@illwieckz illwieckz force-pushed the illwieckz/catch-0div branch from da7a0e5 to 0c354c4 Compare January 4, 2025 23:46
@illwieckz
Copy link
Member Author

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.

@illwieckz
Copy link
Member Author

Platform Implemented Tested
Linux GCC/Clang ✅️ ✅️
MacOS Clang amd64 ✅️ ✅️
MacOS Clang arm64 ✅️
Windows MSVC ✅️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants