When compile(x86-64 gcc 11.2 with -O2) the code below:
double foo(unsigned int x) {
return x/2.5;
}
double zoo(int x) {
return x/2.5;
}
int main(){
return 0;
}
I got the assembly like this:
foo(unsigned int):
mov edi, edi
pxor xmm0, xmm0
cvtsi2sd xmm0, rdi
divsd xmm0, QWORD PTR .LC0[rip]
ret
zoo(int):
pxor xmm0, xmm0
cvtsi2sd xmm0, edi
divsd xmm0, QWORD PTR .LC0[rip]
ret
main:
xor eax, eax
ret
.LC0:
.long 0
.long 1074003968
Why does GCC insert mov edi, edi
for the function doing floating-point division on an unsigned int
but not for the signed one?
CodeLink:
https://gcc.godbolt.org/z/f1hKGrW6T
5
I am not well-versed in this field, but this interesting question urged me to google around and it is such a fascinating spiral. Let me share my findings.
- The purpose of
mov edi, edi
is to zero the top 32 bits ofrdi
register. (edi
actually refers to the lower 32 bits ofrdi
.) - Why it happens: 32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register. (http://x86asm.net/articles/x86-64-tour-of-intel-manuals/)
- Why was this behaviour introduced: to avoid partial register stall
Partial register stall is a problem that occurs when we write to part of a 32-bit register and later read from the whole register or a bigger part of it.
They cause performance penalty (Why doesn’t GCC use partial registers?)
- Why is this relevant to our question? I do not 100% understand the reason but here is what I found:
Since an n-bit bitstring can be interpreted semantically both as an unsigned as well as signed integer, we use sign extension to make things clear. (https://cs.stackexchange.com/questions/81320/signed-and-unsigned-loads-in-a-32-bit-registers)
I plan to read more into these and update the answer once I gain a better understanding.
1