我需要将32位和64位的无符号整数转换为xmm寄存器中的浮点值。x86指令可以将有符号整数转换为单精度和双精度浮点值,但没有用于无符号整数的指令。
奖励内容:如何将xmm寄存器中的浮点值转换为32位和64位无符号整数?
不要介意我把Janus的答案当作模板(毕竟我很喜欢C++):
在 i7 上使用 gcc -march=native -O3
编译生成,因此包括 -mavx
。
uint2float
和相反转换都是预期的,长整型转换只有在数值大于263-1时才有特殊情况。
0000000000000000 <ulong2double>:
0: 48 85 ff test %rdi,%rdi
3: 78 0b js 10 <ulong2double+0x10>
5: c4 e1 fb 2a c7 vcvtsi2sd %rdi,%xmm0,%xmm0
a: c3 retq
b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
10: 48 89 f8 mov %rdi,%rax
13: 83 e7 01 and $0x1,%edi
16: 48 d1 e8 shr %rax
19: 48 09 f8 or %rdi,%rax
1c: c4 e1 fb 2a c0 vcvtsi2sd %rax,%xmm0,%xmm0
21: c5 fb 58 c0 vaddsd %xmm0,%xmm0,%xmm0
25: c3 retq
0000000000000030 <ulong2float>:
30: 48 85 ff test %rdi,%rdi
33: 78 0b js 40 <ulong2float+0x10>
35: c4 e1 fa 2a c7 vcvtsi2ss %rdi,%xmm0,%xmm0
3a: c3 retq
3b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
40: 48 89 f8 mov %rdi,%rax
43: 83 e7 01 and $0x1,%edi
46: 48 d1 e8 shr %rax
49: 48 09 f8 or %rdi,%rax
4c: c4 e1 fa 2a c0 vcvtsi2ss %rax,%xmm0,%xmm0
51: c5 fa 58 c0 vaddss %xmm0,%xmm0,%xmm0
55: c3 retq
0000000000000060 <uint2double>:
60: 89 ff mov %edi,%edi
62: c4 e1 fb 2a c7 vcvtsi2sd %rdi,%xmm0,%xmm0
67: c3 retq
0000000000000070 <uint2float>:
70: 89 ff mov %edi,%edi
72: c4 e1 fa 2a c7 vcvtsi2ss %rdi,%xmm0,%xmm0
77: c3 retq
-march=core2
和-m64
(也许是隐式的,就像你的情况一样)就可以得到这个结果。这里的所有AVX指令都有传统的SSE2变体。例如,最后一个vcvtsi2ss %rdi,%xmm0,%xmm0
可以是cvtsi2ss %rdi,%xmm0
。有趣的是,这在SSE1中也可以工作,但uint2double
中的cvtsi2sd
需要SSE2。 - Janus Troelsen-m64
,请将-m32
添加到编译选项中以生成32位代码。 - Peter Cordes这是GCC生成的代码。我将它们封装在函数中,但您可以轻松删除堆栈处理。并非所有代码都使用SSE来执行实际工作(ulonglong转换不使用),如果您找到相应的指令,请告诉我。Clang几乎相同。
% cat tofloats.c
double ulonglong2double(unsigned long long a) {
return a;
}
float ulonglong2float(unsigned long long a) {
return a;
}
double uint2double(unsigned int a) {
return a;
}
float uint2float(unsigned int a) {
return a;
}
% gcc -msse4.2 -g -Os -c tofloats.c && objdump -d tofloats.o
00000000 <ulonglong2double>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 10 sub $0x10,%esp
6: 8b 55 0c mov 0xc(%ebp),%edx
9: 8b 45 08 mov 0x8(%ebp),%eax
c: 89 55 f4 mov %edx,-0xc(%ebp)
f: 85 d2 test %edx,%edx
11: 89 45 f0 mov %eax,-0x10(%ebp)
14: df 6d f0 fildll -0x10(%ebp)
17: 79 06 jns 1f <ulonglong2double+0x1f>
19: d8 05 00 00 00 00 fadds 0x0
1f: dd 5d f8 fstpl -0x8(%ebp)
22: dd 45 f8 fldl -0x8(%ebp)
25: c9 leave
26: c3 ret
00000027 <ulonglong2float>:
27: 55 push %ebp
28: 89 e5 mov %esp,%ebp
2a: 83 ec 10 sub $0x10,%esp
2d: 8b 55 0c mov 0xc(%ebp),%edx
30: 8b 45 08 mov 0x8(%ebp),%eax
33: 89 55 f4 mov %edx,-0xc(%ebp)
36: 85 d2 test %edx,%edx
38: 89 45 f0 mov %eax,-0x10(%ebp)
3b: df 6d f0 fildll -0x10(%ebp)
3e: 79 06 jns 46 <ulonglong2float+0x1f>
40: d8 05 00 00 00 00 fadds 0x0
46: d9 5d fc fstps -0x4(%ebp)
49: d9 45 fc flds -0x4(%ebp)
4c: c9 leave
4d: c3 ret
0000004e <uint2double>:
4e: 55 push %ebp
4f: 89 e5 mov %esp,%ebp
51: 83 ec 08 sub $0x8,%esp
54: 66 0f 6e 45 08 movd 0x8(%ebp),%xmm0
59: 66 0f d6 45 f8 movq %xmm0,-0x8(%ebp)
5e: df 6d f8 fildll -0x8(%ebp)
61: c9 leave
62: c3 ret
00000063 <uint2float>:
63: 55 push %ebp
64: 89 e5 mov %esp,%ebp
66: 83 ec 08 sub $0x8,%esp
69: 66 0f 6e 45 08 movd 0x8(%ebp),%xmm0
6e: 66 0f d6 45 f8 movq %xmm0,-0x8(%ebp)
73: df 6d f8 fildll -0x8(%ebp)
76: c9 leave
77: c3 ret
% cat toints.c
unsigned long long float2ulonglong(float a) {
return a;
}
unsigned long long double2ulonglong(double a) {
return a;
}
unsigned int float2uint(float a) {
return a;
}
unsigned int double2uint(double a) {
return a;
}
% gcc -msse4.2 -g -Os -c toints.c && objdump -d toints.o
00000000 <float2ulonglong>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 53 push %ebx
4: 83 ec 0c sub $0xc,%esp
7: d9 45 08 flds 0x8(%ebp)
a: d9 05 00 00 00 00 flds 0x0
10: d9 c9 fxch %st(1)
12: db e9 fucomi %st(1),%st
14: 73 0d jae 23 <float2ulonglong+0x23>
16: dd d9 fstp %st(1)
18: dd 4d f0 fisttpll -0x10(%ebp)
1b: 8b 45 f0 mov -0x10(%ebp),%eax
1e: 8b 55 f4 mov -0xc(%ebp),%edx
21: eb 13 jmp 36 <float2ulonglong+0x36>
23: de e1 fsubp %st,%st(1)
25: dd 4d f0 fisttpll -0x10(%ebp)
28: 8b 55 f4 mov -0xc(%ebp),%edx
2b: 8b 45 f0 mov -0x10(%ebp),%eax
2e: 8d 8a 00 00 00 80 lea -0x80000000(%edx),%ecx
34: 89 ca mov %ecx,%edx
36: 83 c4 0c add $0xc,%esp
39: 5b pop %ebx
3a: 5d pop %ebp
3b: c3 ret
0000003c <double2ulonglong>:
3c: 55 push %ebp
3d: 89 e5 mov %esp,%ebp
3f: 53 push %ebx
40: 83 ec 0c sub $0xc,%esp
43: dd 45 08 fldl 0x8(%ebp)
46: d9 05 00 00 00 00 flds 0x0
4c: d9 c9 fxch %st(1)
4e: db e9 fucomi %st(1),%st
50: 73 0d jae 5f <double2ulonglong+0x23>
52: dd d9 fstp %st(1)
54: dd 4d f0 fisttpll -0x10(%ebp)
57: 8b 45 f0 mov -0x10(%ebp),%eax
5a: 8b 55 f4 mov -0xc(%ebp),%edx
5d: eb 13 jmp 72 <double2ulonglong+0x36>
5f: de e1 fsubp %st,%st(1)
61: dd 4d f0 fisttpll -0x10(%ebp)
64: 8b 55 f4 mov -0xc(%ebp),%edx
67: 8b 45 f0 mov -0x10(%ebp),%eax
6a: 8d 8a 00 00 00 80 lea -0x80000000(%edx),%ecx
70: 89 ca mov %ecx,%edx
72: 83 c4 0c add $0xc,%esp
75: 5b pop %ebx
76: 5d pop %ebp
77: c3 ret
00000078 <float2uint>:
78: 55 push %ebp
79: 89 e5 mov %esp,%ebp
7b: 83 ec 08 sub $0x8,%esp
7e: d9 45 08 flds 0x8(%ebp)
81: dd 4d f8 fisttpll -0x8(%ebp)
84: 8b 45 f8 mov -0x8(%ebp),%eax
87: c9 leave
88: c3 ret
00000089 <double2uint>:
89: 55 push %ebp
8a: 89 e5 mov %esp,%ebp
8c: 83 ec 08 sub $0x8,%esp
8f: dd 45 08 fldl 0x8(%ebp)
92: dd 4d f8 fisttpll -0x8(%ebp)
95: 8b 45 f8 mov -0x8(%ebp),%eax
98: c9 leave
99: c3 ret
FISTTP使用截断(chop)作为舍入模式将ST中的值转换为带符号整数,将结果传输到目标,并弹出ST。FISTTP接受字、短整型和长整型目标。
对寄存器ST(0)和ST(i)的内容执行无序比较,并根据结果在EFLAGS寄存器中设置状态标志ZF、PF和CF(请参见下表)。对于比较,零的符号被忽略,因此-0.0等于+0.0。
fisttp
是在SSE3中引入的,尽管它操作的是x87寄存器。https://www.felixcloutier.com/x86/fisttp。如果没有SSE3,您可以使用当前的FP舍入模式(通常是最近)与`fistp`一起使用,而不是截断。或者像编译器在SSE1/2标量数学之前所做的那样更改FP舍入模式,然后再改回来,这包括引入了像`cvttsd2si`这样的截断转换。(转换为有限宽度的有符号整数,因此在32位模式下没有64位整数,不像x87) - Peter Cordes1.0 / 2^32
并在两个32位半中使用XMM FP->整数转换,但这也比将数据反弹到x87寄存器中要慢。除非你有AVX-512用于打包转换为uint64_t(https://www.felixcloutier.com/x86/vcvtpd2uqq),因此`VCVTPD2UQQ xmm1,xmm1/
vmovq [esp],xmm1(或
movd/
pextrd`直接将两个32位半字移动到整数寄存器)。 - Peter Cordesvcvtuqq2ps
(打包的uint64_t转换为打包的单精度)以及vcvtusi2sd xmm1, xmm2, r/m64{er}
等指令,只有在64位模式下才可用。与SSE1相同,这些指令具有不方便的合并到某个寄存器的语义,而不是零扩展到一个新的xmm寄存器中。
float
或double
转换为uint64_t
vcvttsd2usi
或打包)。还添加了有符号或无符号64位整数的打包转换(例如vcvttpd2uqq
打包双精度或vcvttps2uqq
将float32转换为uint64_t)。
在AVX-512之前,在64位模式下或使用x87时,使用零扩展到非负64位有符号整数可以轻松完成32位无符号整数的标量转换。但是64位无符号整数即使在64位模式下也是一个问题。
vcvttsd2usi eax, xmm0
在AVX-512中可在32位或64位模式下工作。(或使用ss
版本;float vs. double)
vcttvsd2usi rax, xmm0
我不确定高半部分的垃圾是否会影响速度,表示子规格浮点/双精度。我猜可能不会,因为将其舍入为整数与微小的标准化值没有区别。
";;; 32-bit mode can use packed 64-bit conversion then get the 2 halves
vcvttpd2uqq xmm1, xmm0 ; 2x truncating uint64_t from double conversions
vmovd eax, xmm1 ; extract the halves to integer registers
vpextrd edx, xmm1, 1 ; edx:eax = (uint64_t)xmm0[0]
vmovq [esp],xmm1
。t
。(如果您没有更改MXCSR,则默认舍入模式为最接近的偶数作为平局。)vcvtpd2uqq xmm1, xmm0 ; (uint64_t)nearbyint(xmm0)
所有新的AVX-512 FP转换指令都提供了截断和当前舍入模式版本。这对于打包转换来说是有意义的;EVEX舍入模式覆盖仅适用于ZMMM向量,而不适用于XMM/YMM。
我有点惊讶他们费心制作vcvttsd2usi
的操作码,而不是只是将其作为VCVTSD2USI r64,xmm1 / m64 {rz-sae}
的别名来覆盖舍入模式以朝向零的截断。 (您还可以覆盖到最近,向上或向下)。这会抑制该指令的FP异常,因此也许他们想支持检测通过检查MXCSR标志不精确或溢出的转换的代码。
float->int
转换,如果你愿意在NaN
、INF
、溢出等方面做出一些妥协,那么有非常快速的方法。 - Mysticialfloat->int
转换,你需要分支来捕获所有边角情况。(或者使用条件移动进行黑客操作) - Mysticial