You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
add Py_ASSUME() macro for __builtin_unreachable() #82328
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.
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.
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.
This check is redundant when _PyLong_New() is called from _PyLong_Copy().
If you care of _PyLong_Copy() performance, you should somehow manually inline _PyLong_New() inside _PyLong_Copy().
sir-sigurd commented on Sep 19, 2019
sir-sigurdmannequin
MannequinAuthor
More actions
If you care of _PyLong_Copy() performance, you should somehow manually inline _PyLong_New() inside _PyLong_Copy().
It doesn't solve this:
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.
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.
Activity
sir-sigurd commentedon Sep 12, 2019
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:
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]
cpython/Objects/longobject.c
Lines 287 to 309 in 3a4f667
[2]
cpython/Objects/longobject.c
Lines 264 to 283 in 3a4f667
vstinner commentedon Sep 19, 2019
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:
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.
vstinner commentedon Sep 19, 2019
If you care of _PyLong_Copy() performance, you should somehow manually inline _PyLong_New() inside _PyLong_Copy().
sir-sigurd commentedon Sep 19, 2019
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.
vstinner commentedon Sep 19, 2019
"_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.
[-]add macro for __builtin_unreachable[/-][+]add Py_ASSUME() macro for __builtin_unreachable()[/+]vstinner commentedon Sep 23, 2019
See also bpo-38249.
iritkatriel commentedon Nov 27, 2023
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).
vstinner commentedon Nov 28, 2023
The C API has the Py_UNREACHABLE() macro which does basically implement requested feature:
Extract of the implementation: