The primary benefit is simplicity of specification. The as-if rule cannot really accommodate the notion that a program can have behavior which is defined and yet may be observably inconsistent with sequential program execution. Further, the authors of the C and C++ Standards use the phrase "Undefined Behavior" as a catch-all for situations where they didn't think it necessary to exercise jurisdiction, in many cases because they expected that compiler writers would be better placed than the Committee to understand their customers' needs.
Most of the useful optimizations which are facilitated by specifying that if no individual action within a loop would be sequenced relative to some later piece of code, execution of the loop as a whole need not be treated as sequenced either. This is a bit "hand-wavy" with regard to what code comes "after" an endless loop, but it makes clear what it would allow a compiler to do. Among other things, if no individual action within a loop would be sequenced before program termination, then execution of the loop as a whole may be omitted entirely.
An important principle that such a rule would embody, which is missing from the present rule, is that a transform that would introduce a dependency between code within a loop and code outside the loop would also introduce a sequencing relationship. If a loop would exit if a certain condition is true, and code after the loop checks that condition, a compiler may use the result from the earlier check to avoid having to repeat the test, but that would mean that code after the loop was relying upon a value computed within the loop.
A concrete example which illustrates the useful and reckless ways the rule can be applied is shown below:
char arr[65537];
unsigned test(unsigned x, unsigned y)
{
unsigned i=1;
while((i & y) != x)
i*=17;
return i;
}
void test2(unsigned x)
{
test(x, 65535);
if (x < 65536)
arr[x] = 2;
}
There are two individually-useful optimizations that a compiler could apply when in-lining test
into test2
:
A compiler could recognize that a test to see whether x == (i & 65535)
could only report "true" in cases where x
is less than 65536, rendering the if
test that in test2()
redundant.
A compiler could recognize that because the only effect of the loop is to compute i
, and the value of i
will end up being ignored when test()
is invoked from within test2()
, the code for the loop is redundant.
Eliminating the loop while keeping the isolated if
would likely be better than doing the reverse, but the ability of code to uphold what's likely a fundamental requirement--that it not write arr
past element 65535--is reliant upon either the loop or the if
test being retained. Either would be redundant in the presence of the other, but the absence of either would make the other essential.
Note that giving compilers the freedom to resequence code while keeping data dependencies would not preclude the possibility that a correct application might get stuck in an endless loop when fed some possible inputs, if the need to terminate the application using outside means in some cases would be acceptable. Allowing them to resequence code without regard for the resulting data dependencies, however, will have the ironic effect of speeding up primarily "erroneous" programs that cannot be relied upon to satisfy application requirements, and offer no benefit to most programs that add extra checking or dummy side effects (which would not otherwise be needed to satisfy application requirements) to guard against endless loops.
PS--as a further example of the general concept of two pieces of code being individually redundant in the presence of the other, consider the following functions:
double x,y,z;
void f1(void) {
x=sin(z);
}
void f2(void)
{
f1();
y=sin(z);
x=0;
}
The assignment x=sin(z);
is redundant in the code as written, and could be optimized out, since nothing uses the value stored into x
. The computation of sin(z)
within f2
is redundant in the code as written, and could be replaced with y=x;
since x
will already hold the value that needs to be stored into y
. It should be obvious, however, that the fact that either transformation could be legitimately applied by itself does not imply that both may be legitimately applied together. The idea that this principle should apply to the kinds of optimization used in the first example snippet would probably have been obvious to the people who wrote the Standard, but unfortunately not to the people who wrote clang and gcc.
for (i=0;i<9;++i) ;
for not having any effect (except settingi = 10
, maybe), it might also optimize away similar infinite loops (unless an exception has been coded). To evaluate the side-effect of the loop as whole, the compiler has to examine the state after the loop (which, per definition, does not exist after an endless loop). Also as the loop never ends, it makes little sense to consider the statements after the loop. So maybe it's better to undefine the semantics... – Glorianewhile(true);
is UB? – DisparateN1528
in my answer which gives a rationale for this optimization. Also related to: https://mcmap.net/q/15528/-are-compilers-allowed-to-eliminate-infinite-loops – Neurology