GCC 6.3を使用して、次のC++コード:
#include <cmath>
#include <iostream>
void norm(double r, double i)
{
double n = std::sqrt(r * r + i * i);
std::cout << "norm = " << n;
}
次のx86-64アセンブリを生成します。
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)
std::sqrt
の呼び出しでは、GCCは最初にsqrtsd
を使用してそれを実行し、結果をスタックに保存します。オーバーフローした場合、libc sqrt
関数を呼び出します。ただし、xmm0
を保存した後、operator<<
への2回目の呼び出しの前に、xmm0
への最初の呼び出しでoperator<<
が失われたため、スタックから値を復元しません。 )。
単純なstd::cout << n;
を使用すると、さらに明確になります。
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)
GCCがlibc sqrt
によって計算されたxmm0
値を使用しないのはなぜですか?
結果を計算するためにsqrt
を呼び出す必要はありません。 SQRTSD命令によって既に計算されています。 sqrt
に負の数が渡されると、標準に従って必要な動作を生成するためにsqrt
を呼び出します(たとえば、errno
を設定したり、浮動小数点例外を発生させたりします。 )。 PXOR、UCOMISD、およびJBE命令は、引数が0より小さいかどうかをテストし、そうでない場合はsqrt
への呼び出しをスキップします。