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 Py_ASSUME() macro for __builtin_unreachable() #82328
Comments
GCC (along with Clang and ICC) has __builtin_unreachable() and MSVC has __assume() builtins, that can be used to optimize out unreachable conditions. So we could add macro like this: #ifdef Py_DEBUG
# define Py_ASSUME(cond) (assert(cond))
#else
# if defined(_MSC_VER)
# define Py_ASSUME(cond) (__assume(cond))
# elif defined(__GNUC__)
# define Py_ASSUME(cond) (cond? (void)0: __builtin_unreachable())
# else
# define Py_ASSUME(cond) ((void)0);
# endif
#endif Here's a pair of really simple examples showing how it can optimize code: https://godbolt.org/z/g9LYXF. Real world example. _PyLong_Copy() [1] calls _PyLong_New() [2]. _PyLong_New() checks the size, so that overflow does not occur. This check is redundant when _PyLong_New() is called from _PyLong_Copy(). We could add a function that bypass that check, but in LTO build PyObject_MALLOC() is inlined into _PyLong_New() and it also checks the size. Adding Py_ASSUME((size_t)size <= MAX_LONG_DIGITS) allows to bypass both checks. [1] Lines 287 to 309 in 3a4f667
[2] Lines 264 to 283 in 3a4f667
|
This sounds like a bad usage of __builtin_unreachable(). _PyLong_New() must always check that size <= MAX_LONG_DIGITS, the check must not be optimized by the compiler. __builtin_unreachable() must only be used if the code really be reached by design. For example: if (...) { Py_FatalError("oops)"; __builtin_unreachable() } But it's a bad example, since Py_FatalError is decorated with the "noreturn" attribute, so the compiler should already know that Py_FatalError() never returns. |
If you care of _PyLong_Copy() performance, you should somehow manually inline _PyLong_New() inside _PyLong_Copy(). |
It doesn't solve this:
Here's example: sir-sigurd@c8699d0. I attach some disassembly from this example compiled with LTO, to demonstrate how the proposed macro affects generated code. |
"_Py_ASSUME((size_t)size <= MAX_LONG_DIGITS);" Typically, such code use assert() and is removed for release build. assert() is more for contract base programming: when the error "cannot" happen at runtime (it would be a programming error). For other cases, I prefer to always emit code to handle the error (the error can happen, for example, the function must check inputs), even in release build. |
See also bpo-38249. |
Closing as there is no consensus here, so this won't happen without a wider discussion on python-dev (following which this can be reopened). |
The C API has the Py_UNREACHABLE() macro which does basically implement requested feature:
Extract of the implementation: (...)
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
# define Py_UNREACHABLE() __builtin_unreachable()
#elif defined(__clang__) || defined(__INTEL_COMPILER)
# define Py_UNREACHABLE() __builtin_unreachable()
#elif defined(_MSC_VER)
# define Py_UNREACHABLE() __assume(0)
#else
# define Py_UNREACHABLE() \
Py_FatalError("Unreachable C code path reached")
#endif |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: