150

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 Compiler Explorer (AKA 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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 3
    Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/206751/discussion-on-question-by-nneonneo-how-do-i-make-an-infinite-loop-that-wont-be). – Bhargav Rao Jan 28 '20 at 00:09
  • 3
    There's no portable solution that doesn't involve a side effect. If you don't want a memory access, your best hope would be register volatile unsigned char; but register goes away in C++17. – Scott M Jan 28 '20 at 01:17
  • 26
    Maybe this isn't in the scope of the question, but I'm curious why you want to do this. Surely there's some other way to accomplish your real task. Or is this just academic in nature? – Cruncher Jan 28 '20 at 14:39
  • 4
    @Cruncher: The effects of any particular attempt to run a program may be useful, essentially useless, or substantially worse than useless. An execution that results in a program getting stuck in an endless loop may be useless, but still be preferable to other behaviors a compiler might substitute. – supercat Jan 28 '20 at 23:56
  • @supercat But we're not talking about a case where the OP accidentally had an infinite loop where the compiler did something funky. The context of this question is the OP attempting to generate assembly which just spin loops with no side effects. My question is simply *why?*. – Cruncher Jan 29 '20 at 17:45
  • 12
    @Cruncher: Because the code might be running in a freestanding context where there is no concept of `exit()`, and because code may have discovered a situation where it cannot guarantee that the effects of continued execution would not be *worse than useless*. A jump-to-self loop is a pretty lousy way of handling such situations, but it may nonetheless be the best way of handling a bad situation. – supercat Jan 29 '20 at 17:52
  • 2
    @Cruncher: As an example, suppose an embedded function to free a memory block discovers, halfway through consolidating its list of free memory areas, that the list has somehow become corrupted. At that point, there would be a very high likelihood that while writing to what it thought were parts of the free list, it has already stomped over something important owned by other code. Ideally, the library would provide a way of configuring a "KILL THE CURRENT PROGRAM EXECUTION NOW!" function, but a typical default would be an endless loop. – supercat Jan 29 '20 at 19:41
  • Seems like ICC has the same issue: https://godbolt.org/z/r5-rKD – Alex Lop. Jan 29 '20 at 20:46
  • 1
    @AlexLop.: Gaaaahhh! I'm starting to think that computing's real "Billion Dollar Mistake" was the "Conformance" chapter of the C89 standard. Most of the controversies surrounding the Standard stem from its failure to make clear that the Standard is intended as a **baseline** of features and guarantees that all implementations should be expected to support, **and is not intended to specify everything necessary to make an implementation be suitable for any particular purpose**. – supercat Jan 29 '20 at 21:10
  • @AlexLop.: If an implementation happens to be used in a context (such as a VM) where even a maliciously-crafted program would be unable to behave in a way that was significantly worse than useless, having a compiler omit empty loops may be useful. In many other contexts, such behavior may be disastrous. The authors of the Standard expected that compiler writers would be better able than the Committee to identify customer needs, and that they would seek to fulfill such needs without regard for whether the Standard would compel them to do so. – supercat Jan 29 '20 at 21:14
  • Not a complete answer, but relevant to the question and answers so far. GCC actually has a section about this in its [documentation](https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Non-bugs.html#Non-bugs). The relevant part here is _"Historically, GCC has not deleted “empty” loops under the assumption that the most likely reason you would put one in a program is to have a delay, so deleting them will not make real programs run any faster."_ – pipe Jan 30 '20 at 20:15
  • This is astonishing. The first rule of optimization ought to be that it doesn't change the behavior of the program, other than in the intended way. As for whether or not it is useful to die in an infinite loop, why would anyone here assume they know better than the programmer what the best behavior will be??? Maybe there is a coprocessor that will restart execution, and spinning in cache is the least consumptive of battery. This all seemed impossible until I read the wiki page that refers to optimization battles against gcc. (The marketing department did it!) – Mike Layton Feb 19 '20 at 21:30
  • You can't, which is correct. Optimising the loop away is perfectly fine. The behaviour of a program which hits an infinite do-nothing loop is undefined, therefore the compiler can do anything it likes. The logic is that previous operations on the control path can be suspended: they must only occur before termination, and in the correct order. Thus, if you hit an infinite loop, the program need not have any behaviour at all, and so the result is undefined. – Yttrill Feb 20 '20 at 00:11
  • @Yttrill: The crux of this question is whether or not an infinite do-nothing loop is actually UB, *per the C standard*. In C++, it's clearly UB, by the forward-progress rule. But in C, it's not clear that this is UB - in fact, it appears to be fairly well-defined by the C standard. If you believe this is UB in C, and have the standard to back you up, please do post an answer. – nneonneo Feb 20 '20 at 04:05
  • @nneonneo: no, i don't care two hoots about what the Standard says in this case. Undefined behaviour is that which is NOT defined by the Standard. In any case it makes no difference in this case, and it has nothing to do with Standards at all. The semantics of a PL specify ordered observable events but except in special cases, not the actual timing. What you can deduce is that if E2 must occur after E1, and you observe E2, then if you do not observe E1, the system is not conforming. This is the only deduction possible. Termination is observable. [Next comment out of space] – Yttrill Feb 20 '20 at 12:07
  • Therefore, if you observe "unreachable" what can you deduce? Remember the rule: the ONLY deduction allowed is that if you observe an event and the rules allow you to deduce that another event should have occurred before it, and you do not observe that even, THEN you can deduce the translator is not conforming. In this case, I do not believe YOU can cite any such rule. The onus is on you to prove that if I observe unreachable, then some prior event which should have been observed has not been. In that case, the translator is not conforming. – Yttrill Feb 20 '20 at 12:11
  • 1
    It sounds like you’re implying that optimizing an infinite loop into a crash is kosher behavior for any language regardless of standards? Then what’s the point of a standard? Java, for instance, clearly defines semantics for non-terminating programs and contains a sizeable discussion of observability in JLS 17.4.9. In there, they assert that *hang* is an observable behaviour that can be rigorously specified as the lack of future observable behaviours. In the casual sense, a hang is definitely observable as a distinct behaviour from a crash. – nneonneo Feb 20 '20 at 17:45
  • You are arguing from the claim that a standard has to mandate something useful. It doesn't. Also that what it thinks is useful must be what you think is useful. It needn't. Also: "observable behaviour" is what the standard says it is, not what common sense might suggest. – philipxy Feb 22 '20 at 01:01
  • @Yttrill: Many useful optimizations can be facilitated by saying that a code block has a single statically reachable exit, execution of the block as a whole need only be treated as sequenced before an operation that follows the exit if some *individual operation* within the block would be likewise sequenced. Proving a lack of data dependencies and static reachability is a lot easier than proving that sequential program execution would ever reach the loop exit. Unfortunately, the way clang processes endless loops allows them to arbitrarily disrupt the behavior of surrounding code. – supercat Feb 01 '22 at 22:54
  • 1
    Another version, another bug https://bugs.llvm.org/show_bug.cgi?id=965 is closed, welcome https://github.com/llvm/llvm-project/issues/60622 :) – kerzol Feb 09 '23 at 21:20
  • @kerzol note that that issue is about C++, not C; the C++ standard contains a forward-progress guarantee that C does not have. The forward-progress guarantee may mean that infinite loops can be legally considered UB in C++, but this is not the case for C. – nneonneo Feb 10 '23 at 00:52

11 Answers11

89

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).

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 17
    I disagree on *"this a crystal clear constant expression, so it may not be assumed by the implementation to terminate"*. This really gets into nit-picky language lawyering, but `6.8.5/6` is in the form of *if (these) then you may assume (this)*. That does not mean *if not (these) you may not assume (this)*. It's a specification only for when the conditions are met, not when they are unmet where you can do whatever you want withing the standards. And if there are no observables... – kabanus Jan 27 '20 at 10:54
  • 8
    @kabanus The quoted part is a special case. If not (the special case), evaluate and sequence the code as you normally would. If you continue reading the same chapter, the controlling expression is evaluated as specified for each iteration statement ("as specified by the semantics") with the exception of the quoted special case. It follows the same rules as evaluation of any value computation, which is sequenced and well-defined. – Lundin Jan 27 '20 at 11:52
  • 2
    I agree, but you would not be surpised that in `int z=3; int y=2; int x=1; printf("%d %d\n", x, z);` there is no `2` in the assembly, so in the empty useless sense `x` was not assigned after `y` but after `z` due to optimization. So going from your last sentence, we follow the regular rules, assume the while halted (because we were not constrained any better), and left in the final, "unreachable" print. Now, we optimize out that useless statement (because we don't know any better). – kabanus Jan 27 '20 at 12:28
  • @kabanus The difference with that example is that `y` is not used. Rewrite it as `int z=3; int y=2; while(y); int x=1` and the compiler can't just optimize away `y`. – Lundin Jan 27 '20 at 13:04
  • 1
    @kabanus: The C semantics are dictated by the source code. The optimizer may have removed the actual write of the value 2 to the memory address of `y` but that does not remove the logical statement from the source code. – MSalters Jan 28 '20 at 11:41
  • 2
    @MSalters One of my comments was deleted, but thanks for the input - and I agree. What my comment said is I think this is the heart of the debate - is a `while(1);` the same as a `int y = 2;` statement in terms of what semantics are we allowed to optimize out, even if their logic remains in the source. From n1528 I was under the impression that they may be the same, but since people way more experienced than me are arguing the other way, and it is an official bug apparently, then beyond a philosophical debate on whether the wording in the standard is explicit, the argument is rendered moot. – kabanus Jan 28 '20 at 12:23
  • As further evidence that this is unintended, at least one version of clang generates a program that segfaults from the code in question: https://coliru.stacked-crooked.com/a/ff2bbee16e9ee250 – Vaelus Jan 29 '20 at 02:03
  • 3
    “Such an implementation would be hopelessly broken, since 'for-ever' loops is a common programming construct.” — I understand the sentiment but the argument is flawed because it could be applied identically to C++, yet a C++ compiler that optimised this loop away would not be broken but conformant. – Konrad Rudolph Jan 29 '20 at 17:52
  • 1
    @KonradRudolph: The concepts of a C implementation being "conforming" and "broken" are not disjoint, especially given the way the C Standard defines conformance. – supercat Jan 29 '20 at 20:01
  • @Lundin : Where in C11 6.8.5/6 is it established that `while(1);` has the semantics of consuming positive time on the abstract machine? What prevents the abstract machine completing this loop in finite time? – Eric Towers Jan 30 '20 at 03:14
  • @EricTowers What do you mean by "completing this loop"? What is the event that happens to make that construct pass from an "incomplete" state to a "complete" state? – pipe Jan 30 '20 at 20:01
  • @pipe : The event is completing the infinitely many repetitions that are required by the constant controlling expression. Nothing prevents the abstract machine completing that infinite process in finite time. The abstract machine is not defined incapable of performing [supertasks or hypertasks](https://en.wikipedia.org/wiki/Supertask). – Eric Towers Jan 30 '20 at 20:17
  • @EricTowers: Rather than thinking in terms of "completing the loop", I think it would make more sense to say that if all statically reachable paths from a loop pass through a certain point, the execution of code after that point which has no data dependencies on anything before it may be arbitrarily interleaved with code before it. Under such a model, an infinite loop would never actually "complete", but merely behave as though it continued to run forever, even after the program finished all its other tasks. In clang, an infinite loop may cause surrounding code to behave nonsensically,... – supercat Feb 01 '22 at 22:31
  • ...but allowing such behavior means that any programmer who can't be certain a loop would terminate, but who would be willing to have a program hang until externally killed if it fails to do so, would need to add directives to block all of the useful optimizations that would be facilitated by a parallel execution model. Do you see any upside to the clang/LLVM view of infinite loops? – supercat Feb 01 '22 at 22:35
  • @supercat : I am always cautious about reordering externally visible side-effects, which seems to be possible with the model you describe. (If the abstract machine behaves as-if it can optimize away no-external-visibles infinite loops, then "the loop completes and subsequent code executes" is perfectly reasonable behaviour.) However, if reording external visibles is guaranteed not to occur, I have no objection to the "parallel execution model" you describe. – Eric Towers Feb 02 '22 at 23:06
  • @EricTowers: Take a look at https://godbolt.org/z/dEG1Y7GjP and let me know when you can lift your jaw off the floor. If the purpose of the loop of the loop was to compute the value of b for any code that happens to need it, and getting stuck in a loop even when the value is ignored was a tolerable but not particularly desired side effect, then optimal code would eliminate the loop but keep the "if". It would also be acceptable for a compiler to treat the `if` as `if (a==x && x < 66000)` and replace that with `if (a==x)`, but *evaluation of that condition would be sequenced after the loop*. – supercat Feb 03 '22 at 16:30
  • @EricTowers: If one wants to ensure that the loop doesn't get skipped, one could write `if (a!=x) { do {} while(1); }` afterward. If reliance upon endless looping of non-static endless loops were deprecated, but compilers had compatibility mode to support with programs that rely upon such semantics without specifying them as above, that would make generation of optimal machine code meeting requirements possible in far more situations than will be possible if the only way to keep compilers from jumping the rails is to add dummy side effects. – supercat Feb 03 '22 at 16:40
  • @EricTowers: Incidentally, adding `if (a!=x) do {} while(1);` doesn't affect clang's behavior. Also, btw, if I were writing rules I'd explicitly allow one other compiler behavior: an implementation may at its leisure raise an implementation-defined signal (which might be defined as forcing program termination in an implementation-defined manner) if it detects that execution has fallen into an endless loop, regardless of whether the exit is statically reachable. Such traps may be useful, but only if their behavior is specified. – supercat Feb 03 '22 at 16:45
  • @supercat : Is my jaw on the floor because the predicate `x < 66000` is incorrectly evaluated (as can be seen by commenting out the infinite loop, which is what `-O1 -Rpass` reports has been done to that loop)? Especially stupid is that correct behaviour is recovered by "`x < 65535`" but not when the constant literal in that comparison is larger. (The target is x86-64, so expectation is data model LP64 or LLP64, so the expectation is `unsigned x` is 32 bits, not 16, so there is no justification for the code as given to ever evaluate the line with the ternary operator.) – Eric Towers Feb 04 '22 at 15:33
  • @EricTowers: If permission to assume the loop will terminate implies that any code execution where it doesn't invokes Undefined Behavior, and if one assumes that there is nothing outside the Standard that would regard some behaviors as unacceptable even though the Standard imposes no requirements, then the optimization would be correct. The problem is that the maintainers of clang and gcc seem to adhere to that second assumption as axiomatic, even though it is only really true of specialized implementations intended for processing only trusted data. – supercat Feb 04 '22 at 16:36
  • @EricTowers: What's ironic and sad is that in the common cases where optimizations allowed by the Standard should have value,--those where either hanging or skipping the loop would be equally acceptable behaviors, but unbounded UB would not be--the only way to guard against UB is for the programmer to write the program in such a way that the loop could not be skipped, thus negating any benefit the optimization could have offered. – supercat Feb 04 '22 at 16:41
  • @EricTowers: I wish examples like this were more widely published, to facilitate dialogue regarding what optimizations are appropriate in what kinds of implementations, and I think the "parallel execution" model would be a good one to advocate for general-purpose compilers. I think the underlying principle "If no individual within a loop is observably sequenced before some later (statically reachable) action, the loop as a whole isn't sequenced either" should be easy to understand intuitively, and fits well with what most programmers actually need to accomplish. – supercat Feb 04 '22 at 17:08
66

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

Arnavion
  • 3,627
  • 1
  • 24
  • 31
  • 10
    Nothing like the smell of a bug that's older than a decade... with multiple proposed fixes and patches... yet still hasn't been fixed. – Ian Kemp Jan 29 '20 at 10:41
  • 4
    @IanKemp: For them to fix the bug now would require acknowledging that they've taken ten years to fix the bug. Better to hold out hope that the Standard will change to justify their behavior. Of course, even if the standard did change, that still wouldn't justify their behavior except in the eyes of people who would regard the change to the Standard as an indication that the Standard's earlier behavioral mandate was a defect that should be corrected retroactively. – supercat Jan 29 '20 at 19:57
  • 5
    It has been "fixed" in the sense that LLVM added the `sideeffect` op (in 2017) and expects front-ends to insert that op into loops at their discretion. LLVM had to pick *some* default for loops, and it happened to choose the one that aligns with C++'s behavior, intentionally or otherwise. Of course, there is still some optimization work left to be done, such as to merge consecutive `sideeffect` ops into one. (This is what is blocking the Rust front-end from using it.) So on that basis, the bug is in the front-end (clang) that doesn't insert the op in loops. – Arnavion Jan 29 '20 at 21:22
  • @Arnavion: Is there any way to indicate that operations may be deferred unless or until the results are used, but that if data would cause a program to loop endlessly, trying to proceed past data dependencies would make the program *worse than useless*? Having to add phony side-effects which would prevent the former useful optimizations to prevent the optimizer from making a program worse than useless doesn't sound like a recipe for efficiency. – supercat Jan 30 '20 at 03:43
  • 2
    That discussion probably belongs on the LLVM / clang mailing lists. FWIW the LLVM commit that added the op did also teach several optimization passes about it. Also, Rust experimented with inserting `sideeffect` ops to the start of every function and did not see any runtime performance regression. The only issue is a *compile time* regression, apparently due to the lack of fusion of consecutive ops like I mentioned in my previous comment. – Arnavion Jan 30 '20 at 17:03
  • The bug appears to be fixed in LLVM 12 (see bug report). Could you perhaps update your answer? – fuz Apr 14 '21 at 14:57
  • 1
    That it took them 15(!) years to fix a major bug like this is pathetic however. I've forever (pun intended) lost trust trust in this compiler now. Since this bug wasn't considered severe or prioritized to fix, it's would seem that clang is maintained by a bunch of PC programmers. Every bare metal embedded system which has made the mistake of using this compiler over the past two decades is potentially a ticking bomb, since all such systems rely on an eternal loop in main(). – Lundin May 05 '22 at 06:49
  • not fixed completely, see https://stackoverflow.com/a/75403722/3257283 – kerzol Feb 09 '23 at 19:57
64

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

The simplest solution:

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

Godbolt link

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0___________
  • 60,014
  • 4
  • 34
  • 74
  • Oh, nice! This looks like it works well. Is `asm volatile` not required? – nneonneo Jan 27 '20 at 09:01
  • I do not think is needed, as side effect cannot be optimized.. but it does not harm as well – 0___________ Jan 27 '20 at 09:12
  • 4
    Just saying "it's a bug in clang" is sufficient though. I'd like to try a few things out here first though, before I yell "bug". – Lundin Jan 27 '20 at 09:35
  • 3
    @Lundin I do not know if it is a bug. Standard is not technically precise in this case – 0___________ Jan 27 '20 at 10:00
  • 4
    Luckily, GCC is open source and I can write a compiler that optimizes away your example. And I could do so for any example you come up with, now and in the future. – Thomas Weller Jan 27 '20 at 16:32
  • 1
    Is this portable? – Cruncher Jan 27 '20 at 18:06
  • @Cruncher no as it uses "asm" extension – 0___________ Jan 27 '20 at 18:07
  • Then I don't see why @nneonneo's objection to per-function attributes doesn't apply here "Those aren't exactly portable...I was hoping for something more "standard C". While that's certainly a solution, part of the question is also "why is Clang even doing this in the first place when it's clearly against the standard?"" – Cruncher Jan 27 '20 at 18:11
  • 3
    @nneonneo: A GNU C Basic asm statement is implicitly `volatile`, like an Extended Asm statement with no output operands. If you wrote `asm("" : "=r"(dummy));` and didn't use the `dummy` result, it *would* be optimized away. You would need `asm volatile` to tell the compiler that there were side effects (or reading of a changing input, like rdtsc) *as well as* the direct effect of producing the output. So yes, side effects can't be optimized away, but the key point is whether or not the compiler assumes there are side effects! https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Volatile – Peter Cordes Jan 27 '20 at 20:32
  • 4
    @ThomasWeller: GCC devs wouldn't accept a patch that optimizes away this loop; it would violate documented = guaranteed behaviour. See my previous comment: `asm("")` is implicitly `asm volatile("");` and thus the asm statement must run as many times as it does in the abstract machine https://gcc.gnu.org/onlinedocs/gcc/Basic-Asm.html. (Note that it's *not* safe for its side effects to include any memory or registers; you need Extended asm with a `"memory"` clobber if you want to read or write memory that you ever access from C. Basic asm is only safe for stuff like `asm("mfence")` or `cli`.) – Peter Cordes Jan 27 '20 at 20:37
  • 2
    Yeah, a compiler that optimizes away a loop in "any example you come up with, now and in the future" would have to violate even ISO C rules if you used assignment to a `volatile int sink;` as a side-effect. At that point, nobody cares about portability to a compiler that isn't even a C compiler anymore. – Peter Cordes Jan 27 '20 at 20:55
  • Just to add another possible simple solution, seems like explicitely inlining the function makes it work, but only if the function is not static: https://godbolt.org/z/cSBvAV – bracco23 Jan 28 '20 at 13:51
  • @PeterCordes: Provided that a compiler manages to process, in the fashion described by the Standard, at least one program that exercises the translation limits in N1570 5.2.4.1, its ability to do anything else whatsoever without hitting documented or undocumented translation limits would seem to be a Quality of Implementation issue outside the Standard's jurisdiction. – supercat Jan 29 '20 at 04:56
36

This is a Clang bug

... when inlining a function containing an infinite loop. The behaviour is different when while(1); appears directly in main, which smells very buggy to me.

See @Arnavion's answer for a summary and links. The rest of this answer was written before I had confirmation that it was a bug, let alone a known bug.


To answer the title question: How do I make an infinite empty loop that won't be optimized away?? -
make die() a macro, not a function, to work around this bug in Clang 3.9 and later. (Earlier Clang versions either keeps the loop or emits a call to a non-inline version of the function with the infinite loop.) That appears to be safe even if the print;while(1);print; function inlines into its caller (Godbolt). -std=gnu11 vs. -std=gnu99 doesn't change anything.

If you only care about GNU C, P__J__'s __asm__(""); inside the loop also works, and shouldn't hurt optimization of any surrounding code for any compilers that understand it. GNU C Basic asm statements are implicitly volatile, so this counts as a visible side-effect that has to "execute" as many times as it would in the C abstract machine. (And yes, Clang implements the GNU dialect of C, as documented by the GCC manual.)


Some people have argued that it might be legal to optimize away an empty infinite loop. I don't agree1, but even if we accept that, it can't also be legal for Clang to assume statements after the loop are unreachable, and let execution fall off the end of the function into the next function, or into garbage that decodes as random instructions.

(That would be standards-compliant for Clang++ (but still not very useful); infinite loops without any side effects are UB in C++, but not C.
Is while(1); undefined behavior in C? UB lets the compiler emit basically anything for code on a path of execution that will definitely encounter UB. An asm statement in the loop would avoid this UB for C++. But in practice, Clang compiling as C++ doesn't remove constant-expression infinite empty loops except when inlining, same as when compiling as C.)


Manually inlining while(1); changes how Clang compiles it: infinite loop present in asm. This is what we'd expect from a rules-lawyer POV.

#include <stdio.h>
int main() {
    printf("begin\n");
    while(1);
    //infloop_nonconst(1);
    //infloop();
    printf("unreachable\n");
}

On the Godbolt compiler explorer, Clang 9.0 -O3 compiling as C (-xc) for x86-64:

main:                                   # @main
        push    rax                       # re-align the stack by 16
        mov     edi, offset .Lstr         # non-PIE executable can use 32-bit absolute addresses
        call    puts
.LBB3_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB3_1                   # infinite loop


.section .rodata
 ...
.Lstr:
        .asciz  "begin"

The same compiler with the same options compiles a main that calls infloop() { while(1); } to the same first puts, but then just stops emitting instructions for main after that point. So as I said, execution just falls off the end of the function, into whatever function is next (but with the stack misaligned for function entry so it's not even a valid tailcall).

The valid options would be to

  • emit a label: jmp label infinite loop
  • or (if we accept that the infinite loop can be removed) emit another call to print the 2nd string, and then return 0 from main.

Crashing or otherwise continuing without printing "unreachable" is clearly not ok for a C11 implementation, unless there's UB that I haven't noticed.


Footnote 1:

For the record, I agree with @Lundin's answer which cites the standard for evidence that C11 doesn't allow assumption of termination for constant-expression infinite loops, even when they're empty (no I/O, volatile, synchronization, or other visible side-effects).

This is the set of conditions that would let a loop be compiled to an empty asm loop for a normal CPU. (Even if the body wasn't empty in the source, assignments to variables can't be visible to other threads or signal handlers without data-race UB while the loop is running. So a conforming implementation could remove such loop bodies if it wanted to. Then that leaves the question of whether the loop itself can be removed. ISO C11 explicitly says no.)

Given that C11 singles out that case as one where the implementation can't assume the loop terminates (and that it's not UB), it seems clear they intend the loop to be present at run-time. An implementation that targets CPUs with an execution model that can't do an infinite amount of work in finite time has no justification for removing an empty constant infinite loop. Or even in general, the exact wording is about whether they can be "assumed to terminate" or not. If a loop can't terminate, that means later code is not reachable, no matter what arguments you make about math and infinities and how long it takes to do an infinite amount of work on some hypothetical machine.

Further to that, Clang isn't merely an ISO C compliant DeathStation 9000, it's intended to be useful for real-world low-level systems programming, including kernels and embedded stuff. So whether or not you accept arguments about C11 allowing removal of while(1);, it doesn't make sense that Clang would want to actually do that. If you write while(1);, that probably wasn't an accident. Removal of loops that end up infinite by accident (with runtime variable control expressions) can be useful, and it makes sense for compilers to do that.

It's rare that you want to just spin until the next interrupt, but if you write that in C that's definitely what you expect to happen. (And what does happen in GCC and Clang, except for Clang when the infinite loop is inside a wrapper function).

For example, in a primitive OS kernel, when the scheduler has no tasks to run it might run the idle task. A first implementation of that might be while(1);.

Or for hardware without any power-saving idle feature, that might be the only implementation. (Until the early 2000s, that was I think not rare on x86. Although the hlt instruction did exist, IDK if it saved a meaningful amount of power until CPUs started having low-power idle states.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    Out of curiousity, is anyone actually using clang for embedded systems? I've never seen it and I work exclusively with embedded. gcc only "recently" (10 years ago) entered the embedded market and I use that one sceptically, preferably with low optimizations and always with `-ffreestanding -fno-strict-aliasing`. It works fine with ARM and perhaps with legacy AVR. – Lundin Jan 28 '20 at 09:14
  • 1
    @Lundin: IDK about embedded, but yes people do build kernels with clang, at least sometimes Linux. Presumably also Darwin for MacOS. – Peter Cordes Jan 28 '20 at 09:37
  • 2
    https://bugs.llvm.org/show_bug.cgi?id=965 this bug looks relevant, but I'm not sure it is what we are seeing here. – bracco23 Jan 28 '20 at 10:59
  • 1
    @lundin - I'm pretty sure we used GCC (and a lot of other toolkits) for embedded work all through the 90's, with RTOS's like VxWorks and PSOS. I don't understand why you say GCC only entered the embedded market recently. – Jeff Learman Feb 19 '20 at 16:17
  • 2
    @JeffLearman Became mainstream recently, then? Anyway, the gcc strict aliasing fiasco only happened after the introduction of C99, and newer versions of it no longer seem to go bananas upon encountering strict aliasing violations either. Still, I remain sceptic whenever I use it. As for clang, the latest version is evidently completely broken when it comes to eternal loops, so it can't be used for embedded systems. – Lundin Feb 19 '20 at 16:22
  • @Lundin: For the record, modern GCC even with `-fstrict-aliasing` somewhat tries to support "obvious" cases like `*(float*)&an_int`, unlike the initial implementation of type-based aliasing optimizations, which happily "broke" code with that UB. But it's not guaranteed, so if need that to always work, you should be using `gcc -fno-strict-aliasing` with `-O2` or `-O3`, like kernels such as Linux do. GCC's handling of "simple" aliasing cases is only there to sometimes enable naive use of unsafe legacy code, especially porting from MSVC where MS docs encouraged that idiom. – Peter Cordes Nov 26 '22 at 04:03
15

Just for the record, Clang also misbehaves with goto:

static void die() {
nasty:
    goto nasty;
}

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

It produces the same output as in the question, i.e.:

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

I see don't see any way to read this as permitted in C11, which only says:

6.8.6.1(2) A goto statement causes an unconditional jump to the statement prefixed by the named label in the enclosing function.

As goto is not an "iteration statement" (6.8.5 lists while, do and for) nothing about the special "termination-assumed" indulgences apply, however you want to read them.

Per original question's Godbolt link compiler is x86-64 Clang 9.0.0 and flags are -g -o output.s -mllvm --x86-asm-syntax=intel -S --gcc-toolchain=/opt/compiler-explorer/gcc-9.2.0 -fcolor-diagnostics -fno-crash-diagnostics -O2 -std=c11 example.c

With others such as x86-64 GCC 9.2 you get the pretty well perfect:

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

Flags: -g -o output.s -masm=intel -S -fdiagnostics-color=always -O2 -std=c11 example.c

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jonathanjo
  • 349
  • 2
  • 9
  • A conforming implementation could have an undocumented translation limit on execution time or CPU cycles which could cause arbitrary behavior if exceeded, or if a programs inputs made exceeding the limit inevitable. Such things are a Quality of Implementation issue, outside the Standard's jurisdiction. It would seem odd that the maintainers of clang would be so insistent on their right to produce a poor quality implementation, but the Standard does allow it. – supercat Jan 29 '20 at 04:08
  • 2
    @supercat thanks for comment ... why would exceeding a translation limit do anything other than fail the translation phase and refuse to execute? Also: "**5.1.1.3 Diagnostics** A conforming implementation shall produce ... diagnostic message ... if a preprocessing translation unit or translation unit contains a violation of **any syntax rule or constraint** ...". I can't see how erroneous behaviour at the execution phase can ever conform. – jonathanjo Jan 29 '20 at 07:48
  • The Standard would be completely impossible to implement if implementation limits had to all be resolved at build time, since one could write a Strictly Conforming program which would require more bytes of stack than there are atoms in the universe. It's unclear whether runtime limitations should be lumped with "translation limits", but such a concession is clearly necessary, and there's no other category in which it could be put. – supercat Jan 29 '20 at 15:40
  • 1
    I was responding to your comment about "translation limits". Of course there are also execution limits, I confess I don't understand why you're suggesting they should be lumped with translation limits or why you say that's necessary. I just don't see any reason for saying `nasty: goto nasty` can be conforming and not spin the CPU(s) until user or resource exhaustion intervenes. – jonathanjo Jan 29 '20 at 17:08
  • 1
    The Standard makes no reference to "execution limits" that I could find. Things like function call nesting are usually handled by stack allocation, but a conforming implementation that limits function calls to a depth of 16 could build 16 copies of every function, and have a call to `bar()` within `foo()` be processed as a call from `__1foo` to `__2bar`, from `__2foo` to `__3bar`, etc. and from `__16foo` to `__launch_nasal_demons`, which would then allow all automatic objects to be statically allocated, and would make what's *usually* a "run-time" limit into a translation limit. – supercat Jan 29 '20 at 17:17
  • Unless there's something I haven't noticed that would classify translation limits as syntax rules or constraint, I see nothing that would require a diagnostic for any violation thereof. IMHO, the best way to give the Standard "teeth" would be to recognize that many implementations are used in contexts where some attempted program executions could produce results that were much worse than useless, and recognize that the most important job of the Standard should be to maximize the set of source texts whose behavior could be guaranteed to be no worse than useless. – supercat Jan 29 '20 at 17:26
  • If there were a directive that said "Do not process this program unless it can be statically validated as not exceeding any translation limits that would jump the rails in ways that might be worse than useless", along with other directives to indicate what kinds of behaviors could be assumed to be, at worst, useless, and which must be presumed worse than useless, then the ability to *usefully* process programs could be treated as a Quality of Implementation issue, while forebearance of worse-than-useless behaviors would be mandated for conformance. – supercat Jan 29 '20 at 17:33
6

I'll play the devil's advocate and argue that the standard does not explicitly forbid a compiler from optimizing out an infinite loop.

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)

Let's parse this. An iteration statement that satisfies certain criteria may be assumed to terminate:

if (satisfiesCriteriaForTerminatingEh(a_loop)) 
    if (whatever_reason_or_just_because_you_feel_like_it)
         assumeTerminates(a_loop);

This doesn't say anything about what happens if the criteria aren't satisfied and assuming that a loop may terminate even then isn't explicitly forbidden as long as other rules of the standard are observed.

do { } while(0) or while(0){} are after all iteration statements (loops) that don't satisfy the criteria that allow a compiler to just assume on a whim that they terminate and yet they obviously do terminate.

But can the compiler just optimize while(1){} out?

5.1.2.3p4 says:

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

This mentions expressions, not statements, so it's not 100% convincing, but it certainly allows calls like:

void loop(void){ loop(); }

int main()
{
    loop();
}

to be skipped. Interestingly, clang does skip it, and gcc doesn't.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • "This doesn't say anything about what happens if the criteria aren't satisfied" But it does, 6.8.5.1 The while statement: "The evaluation of the controlling expression takes place before each execution of the loop body." That's it. This is a value computation (of a constant expression), it falls under the rule of the abstract machine 5.1.2.3 that defines the term evaluation: "_Evaluation_ of an expression in general includes both value computations and initiation of side effects." And according to the same chapter, all such evaluations are sequenced and evaluated as specified by the semantics. – Lundin Jan 27 '20 at 11:57
  • 1
    @Lundin So `while(1){}` is an infinite sequence of `1` evaluations intertwined with `{}` evaluations, but where in the standard does it say those evaluations need to take **nonzero** time? The gcc behavior is more useful, I guess, cuz you don't need tricks involving memory access or tricks outside of the language. But I'm not convinced that the standard forbids this optimization in clang. If making `while(1){}` nonoptimizable is the intention, the standard ought to be explicit about it and infinite looping should be listed as an observable side effect in 5.1.2.3p2. – Petr Skocik Jan 27 '20 at 12:21
  • 1
    I think it is specified, if you treat the `1` condition as a value computation. Execution time does not matter - what matters is what `while(A){} B;` may _not_ be optimized away entirely, not optimized to `B;` and not re-sequenced to `B; while(A){}`. To quote the C11 abstract machine, emphasis mine: "The presence of a sequence point between the evaluation of expressions A and B implies that **every value computation** and side effect **associated with A is sequenced before every value computation** and side effect **associated with B**." The value of `A` is clearly used (by the loop). – Lundin Jan 27 '20 at 13:00
  • @Lundin : "The evaluation of the controlling expression takes place before each execution of the loop body." only promises evaluation of the controlling expression if the loop body is ever executed. If the entire loop body is removed, there is no execution of the loop body. Consequently, this sentence does not guarantee that the controlling expression is ever evaluated. – Eric Towers Jan 27 '20 at 17:30
  • @Lundin The abstract machine and its sequence points describe semantics. Implementations don't need to follow it (http://port70.net/~nsz/c/c11/n1570.html#5.1.2.3p9, http://port70.net/~nsz/c/c11/n1570.html#5.1.2.3p6) as long as **observable behavior** of the program is honored (http://port70.net/~nsz/c/c11/n1570.html#5.1.2.3p6). Volatile accesses or IO do need to follow the abstract machine strictly, but there's no such rule for infinite loops. Maybe there should be one. – Petr Skocik Jan 27 '20 at 18:06
  • It's definitely not OK for clang to remove an infinite loop *but then still assume the code after is unreachable* and let execution fall off the end of a function into garbage and crash, without even doing the 2nd print. Unless you're arguing that an infinite loop with a constant controlling expression is UB. This looks like a clang bug after inlining a function containing in infinite loop; stand-alone defs compile to loops, not `ret` or `ud2`. And manual inlining does what @Lundin and I expect: https://godbolt.org/z/6FLGqa – Peter Cordes Jan 27 '20 at 20:52
  • 3
    +1 Even though it seems to me like "execution hangs indefinitely without any output" is a "side-effect" in any definition of "side-effect" that makes sense and is useful beyond just the standard in a vacuum, this helps explain the mindset from which it can makes sense to someone. – mtraceur Jan 27 '20 at 20:56
  • `while(0){}` can be shown to terminate without any special help from the standard. Note that the phrasing in the standard is an extra case where a loop *may be assumed by the implementation to terminate*. That's why it's worded that way, rather than "a loop with no side effects and a constant controlling expression must not be assumed to terminate". That would specify what `while(0);` doesn't terminate, and that non-existent wording is what that paragraph in your answer seems to be discussing. – Peter Cordes Jan 27 '20 at 21:27
  • @PeterCordes Yeah the answer's TL;DR is basically: 1) Just because a loop isn't a loop that may be assumed to terminate doesn't mean it's necessarily a loop that never terminates. 2) The standard seems to lack any indication that infinite looping should be considered an "observable" side-ffect, and that means optimizing out infinite loops is "OK" per the letter of the standard... It think the C standard wants to allow infinite loops to remain (why else http://port70.net/~nsz/c/c11/n1570.html#6.8.5p6 and especially note157), but I don't think it says it. – Petr Skocik Jan 27 '20 at 21:48
  • 1
    @PSkocik: I don't see the point of 1). I thought that was already obvious to everyone. Of course you can write non-infinite loops in C. Anyway, as for 2), yes I accept that there is some argument to be made about removing infinite loops. But did you miss the fact that clang *also* treats later statements as unreachable and makes asm that just falls off the end of the function (not even a `ret`)? It can't be legal to remove an infinite loop *and* treat statements after it as unreachable, unless that path of execution contains UB. See [my answer](//stackoverflow.com/a/59939032/224132). – Peter Cordes Jan 28 '20 at 01:21
  • @mtraceur: If a loop condition is not constant, having the branch loop back once before taking the path out would have the same effect as looping back enough times for the program to repeat the same state and then taking the exit path. If the loop condition is constant true, however, there wouldn't *be* an exit path. – supercat Jan 29 '20 at 04:20
  • 1
    Near *"optimizing out an infinite loop"*: It is not entirely clear whether *"it"* refers to the standard or the compiler - perhaps rephrase? Given *"though it probably should"* and not *"though it probably shouldn't"*, it is probably the standard that *"it"* refers to. – Peter Mortensen Jan 29 '20 at 13:22
  • @PeterMortensen Thanks. Removed that part. – Petr Skocik Jan 29 '20 at 13:41
2

I have been convinced this is just a plain old bug. I leave the my tests below and in particular the reference to the discussion in the standard committee for some reasoning I previously had.


I think this is undefined behavior (see end), and Clang just has one implementation. GCC indeed works as you expect, optimizing out only the unreachable print statement but leaving the loop. Some how Clang is oddly making decisions when combining the in-lining and determining what it can do with the loop.

The behavior is extra weird - it removes the final print, so "seeing" the infinite loop, but then getting rid of the loop as well.

It's even worse as far as I can tell. Removing the inline we get:

die: # @die
.LBB0_1: # =>This Inner Loop Header: Depth=1
  jmp .LBB0_1
main: # @main
  push rax
  mov edi, offset .Lstr
  call puts
.Lstr:
  .asciz "begin"

so the function is created, and the call optimized out. This is even more resilient than expected:

#include <stdio.h>

void die(int x) {
    while(x);
}

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

results in a very non-optimal assembly for the function, but the function call is again optimized out! Even worse:

void die(x) {
    while(x++);
}

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

I made a bunch of other test with adding a local variable and increasing it, passing a pointer, using a goto etc... At this point I would give up. If you must use clang

static void die() {
    int volatile x = 1;
    while(x);
}

does the job. It sucks at optimizing (obviously), and leaves in the redundant final printf. At least the program does not halt. Maybe GCC after all?

Addendum

Following discussion with David, I yield that the standard does not say "if the condition is constant, you may not assume the loop terminates". As such, and granted under the standard there is no observable behavior (as defined in the standard), I would argue only for consistency - if a compiler is optimizing out a loop because it assume it terminates, it should not optimize out following statements.

Heck n1528 has these as undefined behavior if I read that right. Specifically

A major issue for doing so is that it allows code to move across a potentially non-terminating loop

From here I think it can only devolve into a discussion of what we want (expected?) rather than what is allowed.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kabanus
  • 24,623
  • 6
  • 41
  • 74
2

It seems that this is a bug in the Clang compiler. If there isn't any compulsion on the die() function to be a static function, do away with static and make it inline:

#include <stdio.h>

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

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

It's working as expected when compiled with the Clang compiler and is portable as well.

Compiler Explorer (godbolt.org) - Clang 9.0.0 -O3 -std=c11 -pedantic-errors

main:                                   # @main
        push    rax
        mov     edi, offset .Lstr
        call    puts
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        jmp     .LBB0_1
.Lstr:
        .asciz  "begin"
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
H.S.
  • 11,654
  • 2
  • 15
  • 32
1

The following appears to work for me:

#include <stdio.h>

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

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

at godbolt

Explicitly telling Clang not to optimize that one function causes an infinite loop to be emitted as expected. Hopefully there's a way to selectively disable particular optimizations instead of just turning them all off like that. Clang still refuses to emit code for the second printf, though. To force it to do that, I had to further modify the code inside main to:

volatile int x = 0;
if (x == 0)
    die();

It looks like you'll need to disable optimizations for your infinite loop function, then ensure that your infinite loop is called conditionally. In the real world, the latter is almost always the case anyway.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bta
  • 43,959
  • 6
  • 69
  • 99
  • 1
    It's not necessary for the second `printf` to be generated if the loop actually does go forever, because in that case the second `printf` really is unreachable and therefore can be deleted. (Clang's error is in both detecting unreachability and then deleting the loop such that the unreachable code is reached). – nneonneo Jan 28 '20 at 00:29
  • GCC documents `__attribute__ ((optimize(1)))`, but clang ignores it as unsupported: https://godbolt.org/z/4ba2HM. https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html – Peter Cordes Jan 28 '20 at 02:33
0

A conforming implementation may, and many practical ones do, impose arbitrary limits on how long a program may execute or how many instructions it would execute, and behave in arbitrary fashion if those limits are violated or--under the "as-if" rule--if it determines that they will inevitably be violated. Provided that an implementation can successfully process at least one program that nominally exercises all the limits listed in N1570 5.2.4.1 without hitting any translation limits, the existence of limits, the extent to which they are documented, and the effects of exceeding them, are all Quality of Implementation issues outside the jurisdiction of the Standard.

I think the intention of the Standard is quite clear that compilers shouldn't assume that a while(1) {} loop with no side-effects nor break statements will terminate. Contrary to what some people might think, the authors of the Standard were not inviting compiler writers to be stupid or obtuse. A conforming implementation might usefully to decide to terminate any program which would, if not interrupted, execute more side-effect free instructions than there are atoms in the universe, but a quality implementation shouldn't perform such action on the basis of any assumption about termination but rather on the basis that doing so could be useful, and wouldn't (unlike clang's behavior) be worse than useless.

Lundin
  • 195,001
  • 40
  • 254
  • 396
supercat
  • 77,689
  • 9
  • 166
  • 211
-2

The loop doesn’t have any side effects, and so it can be optimized out.

The loop is effectively an infinite number of iterations of zero units of work. This is undefined in mathematics and in logic and the standard doesn't say whether an implementation is permitted to complete an infinite number of things if each thing can be done in zero time. Clang's interpretation is perfectly reasonable in treating infinity times zero as zero rather than infinity. The standard doesn't say whether or not an infinite loop can end if all the work in the loops is in fact completed.

The compiler is permitted to optimize out anything that's not observable behavior as defined in the standard. That includes execution time. It is not required to preserve the fact that the loop, if not optimized, would take an infinite amount of time. It is permitted to change that to a much shorter run time—in fact, that the point of most optimizations. Your loop was optimized.

Even if Clang translated the code naively, you could imagine an optimizing CPU that can complete each iteration in half the time the previous iteration took. That would literally complete the infinite loop in a finite amount of time. Does such an optimizing CPU violate the standard? It seems quite absurd to say that an optimizing CPU would violate the standard if it's too good at optimizing. The same is true of a compiler.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/206731/discussion-on-answer-by-david-schwartz-how-do-i-make-an-infinite-loop-that-won). – Samuel Liew Jan 27 '20 at 15:47
  • 5
    Judging from the experience you have (from your profile) I can only conclude that this post is written in bad faith just to defend the compiler. You're seriously arguing that something that takes an infinite amount of time can be optimized to execute in half the time. That is ridiculous on every level and you know that. – pipe Jan 29 '20 at 18:40
  • 1
    @pipe: I think the maintainers of clang and gcc are hoping a future version of the Standard will make their compilers' behavior permissible, and the maintainers of those compilers will be able to pretend that such a change was merely a correction of a longstanding defect in the Standard. That's how they've treated C89's Common Initial Sequence guarantees, for example. – supercat Jan 29 '20 at 20:08
  • @S.S.Anne: Hmm... I don't think that's sufficient to block some of the unsound inferences gcc and clang draw from the results of pointer-equality comparisons. – supercat Feb 21 '20 at 16:23
  • @supercat There are others tons. – S.S. Anne Feb 21 '20 at 16:23