Efficient Integer Overflow Checking in LLVM
blog.regehr.org
blog.regehr.org
Unfortunately almost all languages use overflow by default. I only know that C language has unspecified behavior for overflowing signed integers, so it's legal to crash here. That's a sad state of things.
The VAX architecture (to go back some thirty-five years) can trap or not---there's a bit in the flags register that controls overflow traps.
On the x86 line, you can trap on overflow, but the check has to happen after each instruction. The obvious method (using INTO) is slow (http://boston.conman.org/2015/09/05.2) while using a conditional jump (JO) doesn't incur much overhead (http://boston.conman.org/2015/09/07.1) but it does lead to overall slower code because you have to use instructions that set the overflow flag. LEA (can be used to add and multiply) never sets the overflow flag, so instead of using a single instruction to, say, multiply by 16 and add 7, you have to use multiple instructions.
But it already is. As a comment the article links to describes, add and then jo is fused by the processor, so the jo causes no extra uops and we can assume it's the same performance as a real add_and_jo instruction would be. The article also says the that the impact on the icache is insignificant so the fact that it's two instructions rather than one until the fusing also doesn't matter.
So what else did you want?
(Edit: spc476 points out that addressing mode arithmetic can't be used with jo, so there is that)
I once rebuilt the UNIX tools on a VAX with the integer overflow detection bit set. About half of them still worked.
[1] http://odl.sysworks.biz/disk$axpdocmar981/opsys/vmsos71/ovms...
a = b + c + 47;
can be implemented as: LEA a, 47[b+c]
(a, b and c are assigned to registers)This does not generate any trap or flag when it overflows, it just wraps. It's an important optimization, too.
I disagree. It greatly adds to the complexity, verification, cost, and power for the HW to do this, which is especially onerous in that almost nobody cares or runs code that exercises it!
Instead, the real performance hit comes from the compiler being forced to serialize otherwise independent instruction streams. So why waste all that HW effort when you can pay it all in the SW when and only when you want to use it? The user probably won't even notice the difference between SW and HW overflow support on real workloads.
The next optimization, which the parent article doesn't cover, is hoisting checks out of loops. This is a huge win for subscript checks. Most newer compilers optimize FOR loops, at least.
Having detected an error, what do you do? An exception would be nice, but few languages other than Ada handle machine level exceptions well. Aborting the whole program is drastic.
There's a subset of of arithmetic and logic that's completely decidable. It includes addition, subtraction, multiplication by constants, "if", the theory of arrays, a similar theory of structures, and the Boolean operators (on bools, not integers.) This is enough for most subscript and overflow checks. Complete solvers exist for this subset. If you try to prove all constraints that need run-time checks with this approach, you can eliminate maybe 80-90% of run time checks as unnecessary.
Some checks cannot be eliminated, but can be hoisted out of loops. This means making one check before entering the loop, rather than one on each iteration. FOR statements usually can be optimized in this way. That's almost a no-brainer in modern compilers. The "Go" compiler does this. Heavier machinery is needed for more complex loops.
[1] http://www.animats.com/papers/verifier/verifiermanual.pdf
Really? I was under the impression the Go compiler did only simplistic optimisations, to get their wonderfully fast compile times. Do you have a source (e.g. the source)?
And, my impression is that bounds checks are only avoided in Go with range-based for loops (which are very similar to C++ or Rust iterators, which avoid bounds checks in probably exactly the same way), not a loop over integers with indexing.
bool SafeAddInts(int a, int b, int* out) {
if (willOverFlowAdd(a,b)) {
return false;
}
*out = a + b;
return true;
}
Or better bool SafeAddInts(int a, int b, int* out) {
int temp = a + b;
if (magicCheckProcessorFlagsForOverflow()) {
return false;
}
*out = temp;
return true;
}
Or something along those lines.It's all great the compiler can maybe generate code that crashes my program there's overflow but what's the efficient case for handling it at a user level given that overflow is undefined in the general case given the result is undefined?
int a = get_number_a(), b = get_number_b(), c;
if (__builtin_add_overflow(a, b, &c)) // Note: Unlike your example, returns true iff overflow occurred
uh_oh();
else
everything_is_ok();
These are quite efficient on many common architectures.MSVC has SafeInt[3] which is more awkward but gets the job done.
1. https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins...
2. http://clang.llvm.org/docs/LanguageExtensions.html#checked-a...
(Note that your second example can't work in C/C++, since the check happens after the operation.)
I think the poster already knew that, hence the `magic`.
With SPEC it's really easy to use a wrong compiler flag or make a change to an expected int-width and end up with garbage outputs and be none the wiser.