Given this code, why do compilers emit instructions for the unreachable part of the function? The recursion is infinite.
int x;
int a (int b) {
b = a (b);
b += x;
return b;
}
Godbolt Compiler Explorer with GCC and Clang outputs.
GCC 4.9 -O3
produces this output (Intel Syntax).
update: GCC15 still produces the same asm, but -Wall
warns infinite recursion detected [-Winfinite-recursion]
for the b = a (b);
statement.
a:
sub rsp, 8 # realign the stack by 16 before a call
call a
mov edx, DWORD PTR x[rip]
add rsp, 8 # restore RSP
lea eax, [rax+rdx*8] # b += 8*x
add eax, edx # b += x
ret
and Clang 3.4 -O3
emits this output.
update: Clang 10 and earlier are the same, Clang 11 and later optimize this into an infinite loop, and with -Wall
warn about the infinite recursion.
a: # @a
push rax # realign the stack by 16
call a
add eax, dword ptr [rip + x] # b += x
pop rdx # restore RSP
ret
Part of the code is clearly unreachable. Since the very first statement of the function is
b = a (b);
the function will forever keep calling itself recursively (until the stack overflows and you get a segfault). This means that you will never go beyond that line, and therefore, the rest of the code is unreachable. Reachability optimization should in theory remove the code, correct?
Both compilers were running on x86-64 and with the following flags
-O3
- max optimization-march=native
- [unnecessary] use machine specific optimizations when possible (update: used -march=haswell
when regenerating the Godbolt links.)I was thinking that they should have returned something more along the lines (pun intended) of this:
GCC (Intel Syntax):
a:
.L1:
jmp .L1
Clang (AT&T Syntax):
a:
.LBB0_1:
jmp .LBB0_1
(editor's note: Clang 11 and later do emit exactly that.)
So overall, why don't either of the compilers collapse the function into a single recursive jump due to the rest of the code being unreachable?
(These variations are included in the above Godbolt link.)
For the following code:
int j (int x) {
while (1) {};
x++;
return x;
}
GCC returns:
j:
.L2:
jmp .L2
Clang returns:
j: # @j
.LBB0_1: # =>This Inner Loop Header: Depth=1
jmp .LBB0_1
(In C, an infinite loop with a constant as the condition is well-defined, even without I/O, volatile
access, or sync ops inside the loop. Unlike C++11, but like C++26. Before C11, there was no rule about assuming empty loops terminate, so this code has well-defined behaviour in all versions of C.)
For this code:
int r (int x) {
return r (x);
}
GCC generates a recursive jump:
r:
.L2:
jmp .L2
Clang 3.4 returns cleanly early.
r: # @r
ret
Clang 12 and later make an infinite loop like GCC.
This loop doesn't involve an "iteration statement" (like do
, while
, or for
), so there's no rule about the compiler being allowed to assume it terminates, so clang 11's ret
was probably a bug. (Unlike in C++11 where a thread can be assumed to eventually do I/O, a volatile access, or a sync operation. In C the infinite-loop termination rule is specific to iteration statements. However, there are implementation-defined limits on call depth, which infinite recursion will exceed. And tail-recursion optimization isn't guaranteed. So this and the first example do have that problem.)
The compilers you're using probably implement data flow analysis only at the block level within a single function frame, not taking into consideration recursion. (Or perhaps, only interesting recursion, namely tail recursion.) Since the recursive call isn't a tail call, it isn't interesting from an optimization point of view.
Your function has a problem: the way it is compiled, it blows up the stack. It is compiled that way because the call isn't a tail call; it is not a legitimate optimization to treat it as one.
The call could be considered a "pseudo tail call" on grounds that that the code after the call is never invoked, and so if we remove that code, then the recursive call is the last thing which the function does. Then we could reduce the stack-blowing code to a mere infinite loop. This cannot really be called an optimization, though; it's the replacement of one bug manifestation by a different bug manifestation.