You can't assume anything about how GCC implemented the a++
operation, or whether it even did the computation before your inline asm, or before a function call.
You could make a
an (unused) input to your inline asm, but gcc could still have chosen to use lea
to copy-and-add instead of inc
or add
, or constant-propagation after inlining could have turned it into a mov
-immediate.
And of course gcc could have done some other computation that writes FLAGS right before your inline asm.
There is no way to make a++; asm(...)
safe for this
Stop now, you're on the wrong track. If you insist on using asm, you need to do the add
or inc
inside the asm so you can read the flags output. If you only care about the overflow flag, use SETCC, specifically seto %0
, to create an 8-bit output value. Or better, use GCC6 flag-output syntax to tell the compiler that a boolean output result is in the OF condition in FLAGS at the end of your inline asm.
Also, signed overflow in C is undefined behaviour, so actually causing overflow in a++
is already a bug. It usually won't manifest itself if you somehow detect it after the fact, but if you use a
as an array index or something gcc may have widened it to 64-bit to avoid redoing sign-extension.
GCC has builtins for add with overflow detection, since gcc5
There are builtins for signed/unsigned add, sub, and mul, see the GCC manual, that avoid signed-overflow UB and tell you if there was overflow.
bool __builtin_add_overflow (type1 a, type2 b, type3 *res)
is the generic version
bool __builtin_sadd_overflow (int a, int b, int *res)
is the signed int
version
bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res)
is the signed 64-bit long long
version.
The compiler will attempt to use hardware instructions to implement these built-in functions where possible, like conditional jump on overflow after addition, conditional jump on carry etc.
There's a saddl
version in case you want the operation for whatever size long
is on the target platform. (For x86-64 gcc, int
is always 32-bit, long long
is always 64-bit, but long
depends on Windows vs. non-Windows. For platforms like AVR, int
would be 16-bit, and only long
would be 32-bit.)
int checked_add_int(int a, int b, bool *of) {
int result;
*of = __builtin_sadd_overflow(a, b, &result);
return result;
}
compiles with gcc -O3
for x86-64 System V to this asm, on Godbolt
checked_add_int:
mov eax, edi
add eax, esi # can't use the normal lea eax, [rdi+rsi]
seto BYTE PTR [rdx]
and BYTE PTR [rdx], 1 # silly compiler, it's already 0/1
ret
ICC19 uses setcc
into an integer register and then stores that, same difference as far as uops, but worse code-size.
After inlining to a caller that did if(of) {}
it should just jo
or jno
instead of actually using setcc
to create an integer 0/1; in general this should inline efficiently.
Also, since gcc7, there's a builtin to ask if an addition (after promotion to a given type) would overflow, without returning the value.
#include <stdbool.h>
int overflows(int a, int b) {
bool of = __builtin_add_overflow_p(a, b, (int)0);
return of;
}
compiles with gcc -O3
for x86-64 System V to this asm, also on Godbolt
overflows:
xor eax, eax
add edi, esi
seto al
ret
See also Detecting signed overflow in C/C++