I have a macro used all over my code that in debug mode does:
#define contract(condition) \
if (!(condition)) \
throw exception("a contract has been violated");
... but in release mode:
#define contract(condition) \
if (!(condition)) \
__builtin_unreachable();
What this does over an assert()
is that, in release builds, the compiler can heavily optimize the code thanks to UB propagation.
For example, testing with the following code:
int foo(int i) {
contract(i == 1);
return i;
}
// ...
foo(0);
... throws an exception in debug mode, but produces assembly for an unconditional return 1;
in release mode:
foo(int):
mov eax, 1
ret
The condition, and everything that depended on it, has been optimized out.
My issue arises with more complex conditions. When compiler cannot prove that the condition has no side effect, it does not optimize it out, which is a runtme penalty compared to not using the contract.
Is there a way to express that the condition in the contract has no side effect, so that it is always optimized out?
Not likely.
It's known that you cannot take a big collection of assertions, turn them into assumptions (via
__builtin_unreachable
) and expect good results (e.g. Assertions Are Pessimistic, Assumptions Are Optimistic by John Regehr).Some clues:
CLANG, while already having the
__builtin_unreachable
intrinsic, introduced __builtin_assume exactly for this purpose.N4425 - Generalized Dynamic Assumptions(*) notes that:
The Guidelines Support Library (GSL, hosted by Microsoft, but in no way Microsoft specific) has "merely" this code:
and notes that:
*) Paper rejected: EWG's guidance was to provide the functionality within the proposed contract facilities.
So, not an answer, but some interesting results which might lead somewhere.
I ended up with the following toy code:
You can follow along at home, too ;)
noSideEffect
is the function which we know has no side effects, but the compiler doesn't.It went like this:
GCC has
__attribute__((pure))
to mark a function as having no side effect.Qualifying
noSideEffect
with thepure
attribute removes the function call completely. Nice!But we cannot modify the declaration of
noSideEffect
. So how about wrapping its call inside a function that is itselfpure
? And since we are trying to make a self-contained macro, a lambda sounds good.Surprisingly, that doesn't work... unless we add
noinline
to the lambda! I suppose that the optimizer inlines the lambda first, losing thepure
attribute on the way, before considering optimizing out the call tonoSideEffect
. Withnoinline
, in a somewhat counter-intuitive way, the optimizer is able to wipe everything out. Great!However, there are two issues now: with the
noinline
attribute, the compiler generates a body for each lambda, even if they are never used. Meh -- the linker will probably be able to throw them away anyway.But more importantly... We actually lost the optimizations that
__builtin_unreachable()
had enabled :(To sum it up, you can remove or put back
noinline
in the snippet above, and end up with one of these results:With
noinline
Without
noinline
There is no way to force optimize out code as-if it was dead-code, because GCC has to always be complaint with the standard.
On the other hand the expression can be checked to not have any side effects by using the attribute
error
which will show an error whenever a function's call could not be optimized out.An example of a macro that checks whatever is optimized out and does UB propagation:
The error attribute does not work without optimization (so this macro can only be used for release\optimization mode compilation). Note that the error that indicates whatever the contract has side effects is shown during linkage.
A test that shows an error with unoptimizable contract.
A test that optimizes out a contract but, does UB propagation with it.