How is this function's assembly implementing the conditional?
Asked Answered
M

2

20

The following code,

int foo(int);
int bar(int);

int foobar(int i) {
    int a = foo(i);
    int b = bar(i);
    return a == b ? a : b;
};

with GCC trunk is compiling to this assembly:

foobar(int):
        push    rbx
        mov     ebx, edi
        call    foo(int)
        mov     edi, ebx
        pop     rbx
        jmp     bar(int)

Here is it live.

This TU has no clue what i it will be given, and it has no clue what foo and bar will return, so it has no clue whether a == b will be true or false. So it must inspect the outputs of the two calls and somehow compare them to decide which one it shuld return.

But I can't see that in the assembly. What am I missing?

Meandrous answered 7/6, 2024 at 15:25 Comment(6)
The condition goes away, return a == b ? a : b; is optimized to return b;Drainage
@3CxEZiVlQ, and why is that?Meandrous
if a==b, then returning a is the same as returning b.Assurance
@Meandrous Think about the possible cases. You have 2 options: a == b, in this case it doesn't matter what you return, or a != b, in this case you return b. Compiler has to call foo in case it has side effects, but it can safely optimise the condition to return b;, since it satisfies both cases.Septa
@Evg, I was experimenting with referential transparency (godbolt.org/z/oqxGbGMMv), and over-simplified that example, so I ended up asking this question. If I had put > instead of ==, I wouldn't even have asked the question. :DMeandrous
@Meandrous Haha no... Soooo smart.... the compiler I mean :)Louanneloucks
D
34

return a == b ? a : b; is same as return a == b ? b : b; is same as return b;.

Drainage answered 7/6, 2024 at 15:32 Comment(3)
Works with ints... I wonder what it would do to floats, where a==b doesn't mean that a is same as bTithonus
@Swift-FridayPie easy enough to see by changing the types in godbolt. With gcc -fno-signed-zeros (or -ffast-math which implies it), it compiles identically to the int version, because 0 and -0 are the only floats that compare equal without being identical. Without that flag you get a "straightforward" version with a compare and jump.Addieaddiego
clang also does the same thing with -fno-signed-zeros but without it, it generates a branchless SSE/AVX version.Addieaddiego
H
12

Consider the two cases:

(1) a is equal to b.

Then foo(i) and bar(i) must be invoked in this order. The code does so. Since the return value of bar(i) is the same as that of foo(i), it is sufficient to call bar in a tail call and let it's result return to the caller of foobar.

(2) a is not equal to b.

Then foo(i) and bar(i) must be invoked in this order. The code does so. Since the return value of bar(i) is different from that of foo(i), b, i.e., the return value of bar(i) must be returned to the caller. This can be achieved by a tail call of bar and let it's result return to the caller of foobar.

The two cases do exactly the same thing.

Hobie answered 7/6, 2024 at 15:33 Comment(3)
This would be more useful if it didn't obscure the (important) point about the tail call.Assurance
@ScottHunter The text does contain "tail call" twice, no?Hobie
Buried amid redundant text, sure, which is why I said "obscure".Assurance

© 2022 - 2025 — McMap. All rights reserved.