Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Divide by zero with loop optimization VC++ 2015

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:

  1. Is it a bug or not? The C++ standard was heavily changed since VC 2008 so may it be affected like this?
  2. The main question, is there any workaround to fix the issue thru compiler options, except -Ob?
like image 612
Ilya Ivanov Avatar asked Nov 08 '22 14:11

Ilya Ivanov


1 Answers

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.

like image 137
Di Mo Avatar answered Jan 01 '23 21:01

Di Mo