uint
Cのto-castに相当するものをGHCHaskellコンパイラに実装したいと思いdouble
ます。すでにint
-to- double
usingFILD
またはを実装していCVTSI2SD
ます。これらの操作の署名されていないバージョンがありますか、それとも変換前の最上位ビットをゼロにすることになっていますuint
か(したがって範囲が失われます)?
6 に答える
誰かが言ったように、「良い芸術家はコピーします;偉大な芸術家は盗みます」。したがって、他のコンパイラ作成者がこの問題をどのように解決したかを確認できます。簡単なスニペットを使用しました:
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またはマジック調整を追加します。
より良い方法があります
__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);
}
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ビットのゼロ化や定数のリロードなどのいくつかの命令をインライン化すると消えます。
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文字をすべて手動で削除しました)。
私が正しく理解している場合は、32ビットのuintをスタック上の一時領域に移動し、次のdwordをゼロにしてから、fild qword ptrを使用して、現在64ビットの符号なし整数をdoubleとしてロードできるはずです。
AVX-512より前では、x86には符号なし<->FP命令がありません。
(AVX-512Fでは、vcvtusi2sd
とvcvtsd2usi
、およびそれぞれ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
はそれほど簡単ではありません。