web-dev-qa-db-ja.com

GCCが結果を使用せずにlibcのsqrt()を呼び出すのはなぜですか?

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値を使用しないのはなぜですか?

56
Benoît

結果を計算するためにsqrtを呼び出す必要はありません。 SQRTSD命令によって既に計算されています。 sqrtに負の数が渡されると、標準に従って必要な動作を生成するためにsqrtを呼び出します(たとえば、errnoを設定したり、浮動小数点例外を発生させたりします。 )。 PXOR、UCOMISD、およびJBE命令は、引数が0より小さいかどうかをテストし、そうでない場合はsqrtへの呼び出しをスキップします。

76
Ross Ridge