Prehistory:
We have just switched our developing environment to VC++ 2015 from VC++ 2008. Right after that we have found the issue: program raised division by 0 despite testing divider in C++ code.
Test code:
#include <cstdlib>
int test(int n, int test_for_zero)
{
int result = 0;
for (int i = 0; i < n; ++i) {
if (test_for_zero)
result += rand() >> ((8 % test_for_zero) * test_for_zero);
}
return result;
}
int main()
{
return test(rand(), rand() & 0x80000000);
}
Compiled by VC++ 2015 Update 3 or VC++ 2017 with default Release options, on run it raises division by zero. Compiled by VC 2008 runs just fine.
Analysis:
; 6 : for (int i = 0; i < n; ++i) {
test edi, edi
jle SHORT $LN15@main
; 7 :
; 8 : if (test_for_zero)
; 9 : result += rand() >> ((8 % test_for_zero) * test_for_zero);
mov ecx, DWORD PTR _test_for_zero$1$[ebp]
mov eax, 8
cdq
idiv ecx ; <== IT'S HERE, IDIV BEFORE CHECKING FOR ZERO
mov eax, edx
imul eax, ecx
mov DWORD PTR tv147[ebp], eax
test ecx, ecx
je SHORT $LN15@main
$LL11@main:
call ebx
mov ecx, DWORD PTR tv147[ebp]
sar eax, cl
add esi, eax
sub edi, 1
jne SHORT $LL11@main
The compiler takes constant part ((8 % test_for_zero) * test_for_zero)
out of loop body and just forget for testing test_for_zero
before division. Obviously it can be fixed easily at place just by doing the compiler job but correctly.
I've played with several compiler options, like -d2SSAOptimizer-
and -Oxx
, but the only option to fix this is -Od
.
Questions:
Thanks for the report and the small repro (especially feedback from within Visual Studio!), they were very helpful in isolating the problem.
This is indeed a bug in the compiler itself, specifically a safety check in an "Invariant Code Motion" optimization pass. I've made the fix to this, which should appear in VS 15.7.
For now, the easiest workaround is to disable optimizations for functions which have this code pattern via https://learn.microsoft.com/en-us/cpp/preprocessor/optimize:
#pragma optimize( "", off )
<Function containing code with this loop>
#pragma optimize( "", on )
Unfortunately this optimization pass is tied directly to the optimizer, so there's no compiler option outside of completely disabling the optimizer (-Od) to work around this as you've discovered.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With