Why is this optimized away by modern compilers for C++11 and higher

Viewed 89

I'm lost.. I wanted to play around with the compiler explorer to experiment with multithreaded C code, and started with a simple piece of code. The code is compiled with -O3.

static int hang = 0;

void set_hang(int x) {
    hang = x;
}

void wait() {
    while (hang != 0) {
        // wait...
    }
}

To my surprise, this was the compiler output:

set_hang(int):
        mov     dword ptr [rip + hang], edi
        ret
wait():
        ret

It took me a while before I noticed that I was compiling the code as C++ instead of C. Switching to C gave me something what I would have expected:

set_hang:
        mov     DWORD PTR hang[rip], edi
        ret
wait:
        mov     eax, DWORD PTR hang[rip]
        test    eax, eax
        je      .L3
.L5:
        jmp     .L5
.L3:
        ret

Thus, when compiled as C++, wait() always returns, no matter which value was passed before to set_hang(). I confirmed this by compiling and the code on my PC. This code immediately exists, while I would expect it to hang foreever:

int main(void) {
    set_hang(1);
    wait();
    return 0;
}   

And indeed, if I compile this with gcc instead of with g++, it hangs.

I experimented with different compilers (Clang and GCC), and this only happens with Clang 12.0.0 or higer or GCC 10.1 or higher. If I pass --std=c++98 also the code I would expect is emitted, so it seems to be something specific for C++11 and higher.

Removing the static keyword from the hang doesn't affect the emitted assembly.

What is happening here? It has been a few months since I wrote C++, so I might be missing some knowledge about the latest and greatest exotic C++ black magic, but this is really straightforward code. I'm clueless.


Edit: Even this program is optimized away completely:

// test.cpp
static int hang = 0;

static void set_hang(int x) {
    hang = x;
}

static void wait() {
    while (hang != 0) {
        // wait...
    }
}

int main(void) {
    set_hang(1);
    wait();
    return 0;
}

Compiler output:

main:
        xor     eax, eax
        ret

For GCC version 10.3.0 on Ubuntu:

This command will hang: g++ -O1 -o test test.cpp && ./test

And this command won't: g++ -O2 -o test test.cpp && ./test

1 Answers

It's because of following rule:

[intro.progress]

The implementation may assume that any thread will eventually do one of the following:

  • terminate,
  • make a call to a library I/O function,
  • perform an access through a volatile glvalue, or
  • perform a synchronization operation or an atomic operation.

The compiler was able to prove that a program that enters the loop will never do any of the listed things and thus it is allowed to assume that the loop will never be entered.

Related