En utilisant GCC 6.3, le code C++ suivant:
#include <cmath>
#include <iostream>
void norm(double r, double i)
{
double n = std::sqrt(r * r + i * i);
std::cout << "norm = " << n;
}
génère l'assembly x86-64 suivant:
norm(double, double):
mulsd %xmm1, %xmm1
subq $24, %rsp
mulsd %xmm0, %xmm0
addsd %xmm1, %xmm0
pxor %xmm1, %xmm1
ucomisd %xmm0, %xmm1
sqrtsd %xmm0, %xmm2
movsd %xmm2, 8(%rsp)
jbe .L2
call sqrt
.L2:
movl std::cout, %edi
movl $7, %edx
movl $.LC1, %esi
call std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
movsd 8(%rsp), %xmm0
movl std::cout, %edi
addq $24, %rsp
jmp std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
Pour l'appel à std::sqrt
, GCC le fait d'abord en utilisant sqrtsd
et enregistre le résultat dans la pile. S'il déborde, il appelle alors la fonction libc sqrt
. Mais cela ne sauve jamais le xmm0
après cela et avant le deuxième appel à operator<<
, il restaure la valeur de la pile (car xmm0
a été perdu lors du premier appel à operator<<
).
Avec un std::cout << n;
, c'est encore plus évident:
subq $24, %rsp
movsd %xmm1, 8(%rsp)
call sqrt
movsd 8(%rsp), %xmm1
movl std::cout, %edi
addq $24, %rsp
movapd %xmm1, %xmm0
jmp std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
Pourquoi GCC n'utilise pas le xmm0
valeur calculée par libc sqrt
?
Il n'a pas besoin d'appeler sqrt
pour calculer le résultat; il a déjà été calculé par l'instruction SQRTSD. Il appelle sqrt
pour générer le comportement requis selon la norme lorsqu'un nombre négatif est passé à sqrt
(par exemple, définissez errno
et/ou déclenchez une exception à virgule flottante ). Les instructions PXOR, UCOMISD et JBE testent si l'argument est inférieur à 0 et ignorent l'appel à sqrt
si ce n'est pas vrai.