6

uintCのto-castに相当するものをGHCHaskellコンパイラに実装したいと思いdoubleます。すでにint-to- doubleusingFILDまたはを実装していCVTSI2SDます。これらの操作の署名されていないバージョンがありますか、それとも変換前の最上位ビットをゼロにすることになっていますuintか(したがって範囲が失われます)?

4

6 に答える 6

5

誰かが言ったように、「良い芸術家はコピーします;偉大な芸術家は盗みます」。したがって、他のコンパイラ作成者がこの問題をどのように解決したかを確認できます。簡単なスニペットを使用しました:

volatile unsigned int x;
int main()
{
  volatile double  y = x;
  return y;
}

(コンパイラが変換を最適化しないようにするために揮発性物質が追加されました)

結果(無関係な指示はスキップされました):

Visual C ++ 2010 cl / Ox(x86)

  __real@41f0000000000000 DQ 041f0000000000000r ; 4.29497e+009

  mov   eax, DWORD PTR ?x@@3IC          ; x
  fild  DWORD PTR ?x@@3IC           ; x
  test  eax, eax
  jns   SHORT $LN4@main
  fadd  QWORD PTR __real@41f0000000000000
$LN4@main:
  fstp  QWORD PTR _y$[esp+8]

したがって、基本的にコンパイラは、符号ビットが設定された場合に備えて調整値を追加します。

Visual C ++ 2010 cl / Ox(x64)

  mov   eax, DWORD PTR ?x@@3IC          ; x
  pxor  xmm0, xmm0
  cvtsi2sd xmm0, rax
  movsdx    QWORD PTR y$[rsp], xmm0

raxコンパイラは符号ビットがクリアされることを知っているので、ここで調整する必要はありません。

Visual C ++ 2012 cl / Ox

  __xmm@41f00000000000000000000000000000 DB 00H, 00H, 00H, 00H, 00H, 00H, 00H
  DB 00H, 00H, 00H, 00H, 00H, 00H, 00H, 0f0H, 'A'

  mov   eax, DWORD PTR ?x@@3IC          ; x
  movd  xmm0, eax
  cvtdq2pd xmm0, xmm0
  shr   eax, 31                 ; 0000001fH
  addsd xmm0, QWORD PTR __xmm@41f00000000000000000000000000000[eax*8]
  movsd QWORD PTR _y$[esp+8], xmm0

これは、符号ビットがクリアされたかセットされたかに応じて、ブランチレスコードを使用して0またはマジック調整を追加します。

于 2014-08-04T14:22:37.770 に答える
3

より良い方法があります

__m128d _mm_cvtsu32_sd(__m128i n) {
    const __m128i magic_mask = _mm_set_epi32(0, 0, 0x43300000, 0);
    const __m128d magic_bias = _mm_set_sd(4503599627370496.0);
    return _mm_sub_sd(_mm_castsi128_pd(_mm_or_si128(n, magic_mask)), magic_bias);
}
于 2012-12-06T00:39:39.130 に答える
3

IEEE倍精度形式のプロパティの一部を利用して、慎重に作成された指数を追加しながら、符号なしの値を仮数の一部として解釈できます。

Bits 63 62-52     51-0
     S  Exp       Mantissa
     0  1075      20 bits 0, followed by your unsigned int

1075は、倍精度浮動小数点数のIEEE指数バイアス(1023)と、仮数の52ビットの「シフト」量に由来します。仮数の先頭に暗黙の「1」があり、後で減算する必要があることに注意してください。

それで:

double uint32_to_double(uint32_t x) {
    uint64_t xx = x;
    xx += 1075ULL << 52;         // add the exponent
    double d = *(double*)&xx;    // or use a union to convert
    return d - (1ULL << 52);     // 2 ^^ 52
}

プラットフォームにネイティブ64ビットがない場合は、整数ステップにSSEを使用するバージョンが役立つ場合がありますが、それはもちろん異なります。

私のプラットフォームでは、これは次のようにコンパイルされます

0000000000000000 <uint32_to_double>:
   0:   48 b8 00 00 00 00 00    movabs $0x4330000000000000,%rax
   7:   00 30 43 
   a:   89 ff                   mov    %edi,%edi
   c:   48 01 f8                add    %rdi,%rax
   f:   c4 e1 f9 6e c0          vmovq  %rax,%xmm0
  14:   c5 fb 5c 05 00 00 00    vsubsd 0x0(%rip),%xmm0,%xmm0 
  1b:   00 
  1c:   c3                      retq

かなり良さそうです。これ0x0(%rip)は魔法の二重定数であり、上位32ビットのゼロ化や定数のリロードなどのいくつかの命令をインライン化すると消えます。

于 2012-12-06T10:40:58.040 に答える
2

FILDを使用してint-to-doubleをすでに実装しています...
これらの操作の符号なしバージョンはありますか

正確にx87FILDオペコードを使用する場合は、uint64をuint63(div 2)にシフトしてから、2ずつ戻しますが、すでにdoubleになっているため、x87uint64からdoubleへの変換にはオーバーヘッドで1回のFMUL実行が必要です。

例:0xFFFFFFFFFFFFFFFFU-> + 1.8446744073709551e + 0019

厳密なフォームルールでコード例を投稿できませんでした。後でやってみます。

    //inline
    double    u64_to_d(unsigned _int64 v){

    //volatile double   res;
    volatile unsigned int tmp=2;
    _asm{
    fild  dword ptr tmp
    //v>>=1;
    shr   dword ptr v+4, 1
    rcr   dword ptr v, 1
    fild  qword ptr v

    //save lsb
    //mov   byte ptr tmp, 0  
    //rcl   byte ptr tmp, 1

    //res=tmp+res*2;
    fmulp st(1),st
    //fild  dword ptr tmp
    //faddp st(1),st 

    //fstp  qword ptr res
    }

    //return res;
    //fld  qword ptr res
}

VCはx86出力を生成しました

        //inline
        double    u64_to_d(unsigned _int64 v){
    55                   push        ebp  
    8B EC                mov         ebp,esp  
    81 EC 04 00 00 00    sub         esp,04h  

        //volatile double   res;
        volatile unsigned int tmp=2;
    C7 45 FC 02 00 00 00 mov         dword ptr [tmp], 2  
        _asm{
        fild  dword ptr tmp
    DB 45 FC             fild        dword ptr [tmp]  
        //v>>=1;
        shr   dword ptr v+4, 1
    D1 6D 0C             shr         dword ptr [ebp+0Ch],1  
        rcr   dword ptr v, 1
    D1 5D 08             rcr         dword ptr [v],1  
        fild  qword ptr v
    DF 6D 08             fild        qword ptr [v]  

        //save lsb
    //    mov   byte ptr [tmp], 0  
    //C6 45 FC 00        mov         byte ptr [tmp], 0
    //    rcl   byte ptr tmp, 1
    //D0 55 FC           rcl         byte ptr [tmp],1  

        //res=tmp+res*2;
        fmulp st(1),st
    DE C9                fmulp       st(1),st  
    //    fild  dword ptr tmp
    //DB 45 FC           fild        dword ptr [tmp]  
    //    faddp st(1),st 
    //DE C1              faddp       st(1),st  


        //fstp  qword ptr res
        //fstp        qword ptr [res]  
    }

        //return res;
        //fld         qword ptr [res]  

    8B E5                mov         esp,ebp  
    5D                   pop         ebp  
    C3                   ret  
}

投稿しました(おそらく、テキストファイル内の誤ったASCII文字をすべて手動で削除しました)。

于 2014-08-04T12:19:40.337 に答える
1

私が正しく理解している場合は、32ビットのuintをスタック上の一時領域に移動し、次のdwordをゼロにしてから、fild qword ptrを使用して、現在64ビットの符号なし整数をdoubleとしてロードできるはずです。

于 2012-12-05T23:18:16.223 に答える
0

AVX-512より前では、x86には符号なし<->FP命令がありません。
(AVX-512Fでは、vcvtusi2sdvcvtsd2usi、およびそれぞれssのバージョンを参照してください。また、64ビット整数を含むパックされたSIMD変換も新しくなります。AVX-512Fより前では、パックされた変換変換はint32_tとの間でやり取りできました。)


64ビットコードでは、符号なし32ビット-> FPは簡単です。u32をi64にゼロ拡張し、符号付き64ビット変換を使用するだけです。 すべてのuint32_t値は、非負のint64_tとして表すことができます。

逆方向の場合は、範囲外のFP入力で問題がなければ、FP-> i64に変換し、u32に切り捨てます。(i64の範囲外の場合は0を含み、それ以外の場合は2の補数i64ビットパターンのlow32を取ります。)


u32-> FP:コンパイラ出力については@IgorSkochinskyの回答を参照してください。x86-64 GCCとClangは、x64MSVCと同じトリックを使用します。重要な部分は、64ビットにゼロ拡張して変換することです。32ビットレジスタへの書き込みは暗黙的に64ビットにゼロ拡張されるmov r32, r32ため、値が32ビット演算で書き込まれたことがわかっている場合は必要ない場合があります。(または、自分でメモリからロードする必要がある場合)。

; assuming your input starts in EDI, and that RDI might have garbage in the high half
; like a 32-bit function arg.

    mov     eax, edi              ; mov-elimination wouldn't work with  edi,edi
    vcvtsi2sd xmm0, xmm7, rax     ; where XMM7 is some cold register to avoid a false dep

(ゼロ拡張のために別の命令が必要な場合)以外の選択はmov edi,edi、同じレジスタケースで動作しないmov-eliminationによって動機付けられます。x86のMOVは本当に「無料」である可能性がありますか?を参照してください。なぜこれをまったく再現できないのですか?

AVXがない場合、または使用する最近書き込まれていないレジスタがわからない場合は、pxor xmm0, xmm0設計が不十分なレジスタにcvtsi2sdマージする前に使用することをお勧めします。GCCは偽のdepを宗教的に破ります。ループで運ばれるdepチェーンが単一の関数内に存在しない限り、clangはかなりキャバリアーです。そのため、ループで呼び出される可能性のある、インライン化されていない個別の関数間の相互作用によって速度が低下する可能性があります。xorps命令を追加すると、cvtsi2ssを使用してこの関数が作成され、追加が最大5倍速くなるのはなぜですか?を参照してください。これがclangを噛む例(ただし、GCCは問題ありません)。

この回答は、GCCの最適化ミスのバグレポートにもリンクしています。ここでは、変換での誤った依存関係などを回避するために「コールド」レジスタを再利用するというアイデアについて詳しく説明してい[v]sqrtsdます。これも1入力操作です。


32ビットモード:

コンパイラが異なれば、戦略も異なります。 gcc -O3 -m32 -mfpmath=sse -msseregparmこれは、GCCが何をするかを確認するための良い方法であり、ST0ではなくXMM0で返されるため、実際に便利な場合にのみx87を使用します。(たとえば、64ビット-> FPを使用fild)。

いくつかのu32とu64->floatまたはdoubleテスト関数をgccとclangを使用してGodboltに配置しましたが、この回答は主に、他の回答ではカバーできなかった質問のx86-64部分に回答することを目的としており、廃止された32ビットではありませんcodegen。したがって、ここでコードとasmをコピーして、分析するつもりはありません。

doubleすべてを正確に表すことができることを説明u32します。これにより、署名付き変換の範囲を簡単(double)(int)(u32 - 2^31) + double(2^31)にシフトできます。しかし、u32->floatはそれほど簡単ではありません。

于 2021-09-18T08:38:45.217 に答える