Clang neither solves the halting problem, nor it is evaluating the loop, potentially forever. It is only optimizing the loop once, as part of the compilation process.
Let's compile the code as an optimizing compiler would
There are various optimizations intermingled here, so try to make this very simple.
First, square(int n)
is called with n = 10
. This is important because 10
is a constant, so the compiler can now create a copy of this function for this specific invocation. This is called function specialization, and is also encouraged by inline
. The specialized function is now:
inline int square_10() // Const parameter removed
{
int k = 0;
while ( true )
{
if( k == 10*10 ) // Const value replaced
return k;
k++;
}
};
Here, 10*10
is trivially optimized to 100
. There is not much code left (this will be important later).
There is a local variable k
initialized to 0, and there is the while(true)
loop, with only two statements. One is the if
, the other is the local increment of k
. From the latter, the compiler can extrapole the range of k
as all positive numbers (until it reaches undefined behavior).
The internal compiled code is now approximately represented in memory as:
inline int square_10()
{
// k = $ALL_POSITIVE_INT (compiler internal state)
while ( true )
if( k == 100 )
return k;
};
This can also be trivially optimized in two steps. First the if
will be eventually true because 100
is in the range of k
, so the return
can be reached. And when the return
is reached, k
is restricted to 100
.
There is no other instructions that change the result, so the only possible effect of this code is optimized to:
inline int square_10()
{
return 100;
}
There are no calls for square(int n)
, so the "normal" function is never generated. The code is now reduced to an inline function that returns a constant. This is inlined to:
int main()
{
return 100;
}
Well, this is fine and dandy, but let's test some assumptions from above.
Constant argument and multiplication elimination
You can try replacing 10
by getchar()
from the original example, and you will see the compiler will not optimize the code to a constant anymore.
It will eliminate the loop, but keep the mul
, as now the function result literally depends on the function argument: the return is only constant for a constant parameter.
Range inspection and loop elimination
You can further further replace k++
by k = getchar()
, and you will see the compiler will not eliminate the loop (as observed with the jumping around, je
and jne
).
It also will keep the imul
around, as the compiler cannot assume any value of k
beyond the initial zero.
An optimizing compiler only cares for code that cause effects
Inserting an putchar(0);
anywhere inside the while
would disable the loop elimination. An putchar(0);
before the while
generates some additional assembly, and a putchar(0);
after the while
causes nothing, because it is detected as dead code from the same optimizations above.
TL:DR;
A mix of function specialization, constant propagation, constant folding, range inspection, code invariant analysis and dead code elimination can generate these results. There is no evaluation involved.
if (a == b) return a;
andif (a == b) return b;
are pretty obviously equivalent. The compiler is allowed to assume that loop exits, and that return statement is the only way it can exit, so the compiler optimizes the loop to just the return statement. $\endgroup$return
from the function is of the valuek
guarded by the conditionk == n * n
. So if the function ever returns anything then it must returnn * n
, and AFAIK C++ compilers are allowed to assume termination of side-effect-free loops even if they can't prove it. $\endgroup$while(true)
. You can do so in a bounded amount of time, without having to actually simulate the loop. And nothing stops a compiler from applying the same logic that you did. But neither of you have solved the halting problem. $\endgroup$