How do I make an infinite empty loop that won't be optimized away?

The C11 standard appears to imply that iteration statements with constant controlling expressions should not be optimized out. I'm taking my advice from this answer, which specifically quotes section 6.8.5 from the draft standard:

An iteration statement whose controlling expression is not a constant expression ... may be assumed by the implementation to terminate.

In that answer it mentions that a loop like while(1) ; should not be subject to optimization.

So...why does Clang/LLVM optimize out the loop below (compiled with cc -O2 -std=c11 test.c -o test)?

#include <stdio.h>

static void die() {
    while(1)
        ;
}

int main() {
    printf("begin\n");
    die();
    printf("unreachable\n");
}

On my machine, this prints out begin, then crashes on an illegal instruction (a ud2 trap placed after die()). On godbolt, we can see that nothing is generated after the call to puts.

It's been a surprisingly difficult task to get Clang to output an infinite loop under -O2 - while I could repeatedly test a volatile variable, that involves a memory read that I don't want. And if I do something like this:

#include <stdio.h>

static void die() {
    while(1)
        ;
}

int main() {
    printf("begin\n");
    volatile int x = 1;
    if(x)
        die();
    printf("unreachable\n");
}

...Clang prints begin followed by unreachable as if the infinite loop never existed.

How do you get Clang to output a proper, no-memory-access infinite loop with optimizations turned on?


Solution 1:

The C11 standard says this, 6.8.5/6:

An iteration statement whose controlling expression is not a constant expression,156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157)

The two foot notes are not normative but provide useful information:

156) An omitted controlling expression is replaced by a nonzero constant, which is a constant expression.

157) This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven.

In your case, while(1) is a crystal clear constant expression, so it may not be assumed by the implementation to terminate. Such an implementation would be hopelessly broken, since "for-ever" loops is a common programming construct.

What happens to the "unreachable code" after the loop is however, as far as I know, not well-defined. However, clang does indeed behave very strange. Comparing the machine code with gcc (x86):

gcc 9.2 -O3 -std=c11 -pedantic-errors

.LC0:
        .string "begin"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
.L2:
        jmp     .L2

clang 9.0.0 -O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.Lstr:
        .asciz  "begin"

gcc generates the loop, clang just runs into the woods and exits with error 255.

I'm leaning towards this being non-compliant behavior of clang. Because I tried to expand your example further like this:

#include <stdio.h>
#include <setjmp.h>

static _Noreturn void die() {
    while(1)
        ;
}

int main(void) {
    jmp_buf buf;
    _Bool first = !setjmp(buf);

    printf("begin\n");
    if(first)
    {
      die();
      longjmp(buf, 1);
    }
    printf("unreachable\n");
}

I added C11 _Noreturn in an attempt to help the compiler further along. It should be clear that this function will hang up, from that keyword alone.

setjmp will return 0 upon first execution, so this program should just smash into the while(1) and stop there, only printing "begin" (assuming \n flushes stdout). This happens with gcc.

If the loop was simply removed, it should print "begin" 2 times then print "unreachable". On clang however (godbolt), it prints "begin" 1 time and then "unreachable" before returning exit code 0. That's just plain wrong no matter how you put it.

I can find no case for claiming undefined behavior here, so my take is that this is a bug in clang. At any rate, this behavior makes clang 100% useless for programs like embedded systems, where you simply must be able to rely on eternal loops hanging the program (while waiting for a watchdog etc).

Solution 2:

You need to insert an expression that may cause a side-effect.

The simplest solution:

static void die() {
    while(1)
       __asm("");
}

Godbolt link

Solution 3:

Other answers already covered ways to make Clang emit the infinite loop, with inline assembly language or other side effects. I just want to confirm that this was indeed a compiler bug. Specifically, it was a long-standing LLVM bug - it applied the C++ concept of "all loops without side-effects must terminate" to languages where it shouldn't, such as C. The bug was finally fixed in LLVM 12.

For example, the Rust programming language also allows infinite loops and uses LLVM as a backend, and it had this same issue.

LLVM 12 added a mustprogress attribute that frontends can omit to indicate when functions don't necessarily return, and clang 12 was updated to account for it. You can see that your example compiles correctly with clang 12.0.0 whereas it did not with clang 11.0.1