16

While investigating a dubious claim, I wrote this little test program noway.c

int proveit()
{
    unsigned int n = 0;
    while (1) n++;
    return 0;
}

int main()
{
    proveit();
    return 0;
}

Testing this, I get:

$ clang -O noway.c
$ ./a.out
zsh: illegal hardware instruction  ./a.out

Wat.

If I compile without optimizations it hangs as expected. I looked at the assembly, and without all the bells and whistles the main function looks like this:

_main:                                  ## @main
    pushq   %rbp
    movq    %rsp, %rbp
    ud2

Where ud2 is apparently is an instruction specifically for undefined behavior. The aforementioned dubious claim, "A function that never returns is UB", is reinforced. I still find it hard to believe though. Really!? You can't safely write a spin loop?

So I guess my questions are:

  1. Is this a correct reading of what is going on?
  2. If so, can someone point me to some official resource that verifies it?
  3. What is a situation in which you would want this type of optimization to occur?

Relevant info

$ clang --version
Apple clang version 11.0.0 (clang-1100.0.20.17)
Target: x86_64-apple-darwin18.6.0
Thread model: posix
InstalledDir: /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
luqui
  • 59,485
  • 12
  • 145
  • 204
  • `int proveit() __attribute__((noreturn))` is what you're looking for for spin locks. – Jazzwave06 Nov 28 '19 at 00:17
  • 3
    IIRC, signed int overflow is UB. – wildplasser Nov 28 '19 at 00:20
  • 1
    ^^^^ i.e. `int n = 0` ===> `unsigned int n = 0;` or better still.. `while (1);` – WhozCraig Nov 28 '19 at 00:21
  • 2
    Hmmm... Compiler Explorer clang gets a self-target jump instruction with -O https://gcc.godbolt.org/z/NTCQYT and a line-by-line translation without it. Seems consistent across many versions. But I also recall (though won't look it up) that the C standard says a non-termination is ub _if it's side-effect free_. Here's Hans Boehm explaining that this allows certain otherwise-impossible compiler optimizations: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm – Gene Nov 28 '19 at 00:35
  • I can't reproduce this on any version of clang on godbolt, but every version from 3.9.0 generates an empty `main` without any code whatsoever, even a RET instruction. – Ross Ridge Nov 28 '19 at 00:38
  • 1
    The undefined behaviour is signed integer overflow, not the infinite loop . You could fix it by using `unsigned int` – M.M Nov 28 '19 at 00:40
  • 1
    @M.M. that is not so. I'll edit the question since that's the second time someone has suggested this explanation. – luqui Nov 28 '19 at 00:40
  • 2
    @Gene I don't think it says that. Sideffect-free loops with non-const controlling expressions may be assumed to terminate (http://port70.net/~nsz/c/c11/n1570.html#6.8.5p6) but busy looping should be fine. The UB is in the integer overflow, though I can't reproduce the example with the UD instruction. – Petr Skocik Nov 28 '19 at 00:42
  • 1
    also 9.0.0 is the latest clang release, maybe you have an experimental build installed – M.M Nov 28 '19 at 00:52
  • 1
    @M.M: I have a mainline Apple Clang installed, and it says it is 11.0.0 too. Perhaps Apple uses different version numbering? – Eric Postpischil Nov 28 '19 at 00:56
  • Related: https://stackoverflow.com/questions/16436237/is-while1-undefined-behavior-in-c – Ross Ridge Nov 28 '19 at 00:58

1 Answers1

4

If you get the ud2 for the code that is now in the question, then the compiler is not a conforming C compiler . You could report a compiler bug.

Note that in C++ this code would actually be UB. When threads were added (C11 and C++11 respectively), there was put in place a forward progress guarantee for any thread, including the main execution thread of a program that's not multi-threaded.

In C++ all threads must eventually progress, with no exceptions. However in C, a loop whose controlling expression is a constant expression is not required to progress. My understanding is that C added this exception because it was already common practice in embedded coding to use a while(1) {} to hang the thread.

Similar question with more detailed answers

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Yes I do get the `ud2` from the C code, but thank you for including the info about C++ forward progress guarantee (a term it seems I will be able to research) which is what I was really asking about but got, uh, optimized away as I was preparing the question. – luqui Nov 28 '19 at 01:01
  • A better phrasing of what I think the Standards Committee was trying to say is that if deferring everything within a loop past a certain operation would not observably affect program behavior, deferring the execution of the loop as a whole past that operation would not be considered an observable change of behavior. – supercat Nov 30 '19 at 22:58