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
Assertions are only used in debug builds, and are discarded for release builds. This is a shame, because the information they contain can be worth their weight in gold if used by the compiler as hints to optimize the code. Even for non-PGO builds. (This shouldn't hurt PGO builds either, as this can e.g. better convey the range of integral variables that the developer intended rather than values that were found during benchmarks.)
Major compilers (GCC, Clang, and MSVC) contain built-in functions that allow one to implement this without a lot of indecipherable goo.
My proposal is to introduce a _Py_CONTRACT() macro that will expand to:
assert() on debug builds
Whatever compiler-specific goo is necessary to use the expression for optimization purposes
No-op on non-supported compilers
Note that this is different from a generic assert() macro because, in some situations (e.g. when you need to call a potentially expensive function to check an invariant and you're willing to pay the price in debug builds, or the expression would have a side-effect), you don't want to expand the macro on release builds.
This is not a new idea. The MSVC documentation for __assume() mentions this. This blog post has a few other examples that illustrates why this might be a good idea, although it cautions that this is a dangerous thing to use as it artificially introduces undefined behavior to trick the compiler into optimizing code.
Implementing more specialized contract macros might be another way of going forward with this. It makes it harder to misuse the generic assert-like macro, which could introduce nasty bugs or performance regressions we're not willing to pay for. For instance, a macro like _Py_CONTRACT_RANGE(variable, min_value, max_value) could be defined to check if a value is within a particular range; it's possible to check in some compilers that both min_value and max_value are integer constant expressions and expand to a no-op or #error if they're not. (Since this is an optimization hint, if it's not possible to check if something is an ICE, we can always fall back to a no-op.)
Thoughts?
I imagine this being written roughly like this:
#if defined(_MSC_VER)
#define__Py_CONTRACT(expr) (__assume(expr))
#elif defined(__clang__)
#define__Py_CONTRACT(expr) (__builtin_assume(expr))
#elif defined(__GNUC__)
#define__Py_CONTRACT(expr) do { if (!(expr)) __builtin_unreachable(); } while(0)
#else#define__Py_CONTRACT(expr) do { } while(0)
#endif#if defined(NDEBUG)
#define_Py_CONTRACT(expr) __Py_CONTRACT(expr)
#else#define_Py_CONTRACT(expr) assert(expr)
#endif
Some example code differences (not really impressive, but it's hard to find artificial examples that showcases this thing well):
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Assertions are only used in debug builds, and are discarded for release builds. This is a shame, because the information they contain can be worth their weight in gold if used by the compiler as hints to optimize the code. Even for non-PGO builds. (This shouldn't hurt PGO builds either, as this can e.g. better convey the range of integral variables that the developer intended rather than values that were found during benchmarks.)
Major compilers (GCC, Clang, and MSVC) contain built-in functions that allow one to implement this without a lot of indecipherable goo.
My proposal is to introduce a
_Py_CONTRACT()
macro that will expand to:assert()
on debug buildsNote that this is different from a generic
assert()
macro because, in some situations (e.g. when you need to call a potentially expensive function to check an invariant and you're willing to pay the price in debug builds, or the expression would have a side-effect), you don't want to expand the macro on release builds.This is not a new idea. The MSVC documentation for
__assume()
mentions this. This blog post has a few other examples that illustrates why this might be a good idea, although it cautions that this is a dangerous thing to use as it artificially introduces undefined behavior to trick the compiler into optimizing code.Implementing more specialized contract macros might be another way of going forward with this. It makes it harder to misuse the generic assert-like macro, which could introduce nasty bugs or performance regressions we're not willing to pay for. For instance, a macro like
_Py_CONTRACT_RANGE(variable, min_value, max_value)
could be defined to check if a value is within a particular range; it's possible to check in some compilers that bothmin_value
andmax_value
are integer constant expressions and expand to a no-op or#error
if they're not. (Since this is an optimization hint, if it's not possible to check if something is an ICE, we can always fall back to a no-op.)Thoughts?
I imagine this being written roughly like this:
Some example code differences (not really impressive, but it's hard to find artificial examples that showcases this thing well):
Gets generated by Clang and GCC when generating for x86-64 as:
Beta Was this translation helpful? Give feedback.
All reactions