3.11 浮点代码
处理器的浮点体系结构
包括多个方面,会影响对浮点数据操作的程序如何被映射到机器上,包括:
1) 如何存储和访问浮点数据。通常是通过某种寄存器方式来完成。
2) 对浮点数据操作的指令。
3) 想函数传递浮点数参数和从函数返回浮点数结构的规则。
4) 函数调用过程保持寄存器的规则——例如,一些寄存器被指定为调用者保存
,而其他的被指定为被调用者保存
。
历史回顾:
单指令多数据或SIMD(sim-dee)
,允许多个数据并行执行同一操作。
名字变迁过程:MMX
SSE(Streaming SIMD Extension,流式SIMD扩展)
AVX(Advanced Vector Extension,高级向量扩展)
。
每个扩展都是管理寄存器组中的数据:
MM
寄存器
MMX
,64
位;
XMM
寄存器
SSE
,128
位;
YMM
寄存器
AVX
。256
位。
AVX
浮点体系结构允许数据存储在16
个***YMM
***寄存器中,它们的名字为%ymm0~%ymm15
。每个YMM
寄存器都是256
位(32
位)。这些寄存器只保存浮点数,只使用低32
位(float
)或64
位)(double
)。每个SSE XMM
寄存器对应YMM
寄存器低32
位。
3.11.1 浮点传送和转换操作
引用内存的指令是标量
指令,意味着它们只对单个而不是一组封装好的数据值进行操作。
指令 | 源 | 目的 | 描述 |
---|---|---|---|
vmovss |
传送单精度数 | ||
vmovss |
传送单精度数 | ||
vmovsd |
传送双精度数 | ||
vmovsd |
传送双精度数 | ||
vmovaps |
传送对齐的封装好的单精度数 | ||
vmovapd |
传送对齐的封装好的双精度数 |
GCC
只用标量传送操作从内存传送数据到XMM
寄存器或从XMM
寄存器传送数据到内存。
对于两个XMM
寄存器之间传送数据,GCC
会使用两种指令(vmovss
传送单精度或vmovsd
传送双精度),两者不会影响执行速度。
指令中的a
表示.aligned
的意思。当用于读写内存时如果地址不满足16
字节对齐,会导致异常。
示例:
把浮点值转换成整数时,指令会执行截断(truncation)
,把值指向0
进行舍入,这是C
和大多数其他编程语言的要求。
指令 | 源 | 目的 | 描述 |
---|---|---|---|
vcvttss2si |
用截断的方法把单精度数转换成整数 | ||
vcvttsd2si |
用截断的方法把单双度数转换成整数 | ||
vcvttss2siq |
用截断的方法把单精度数转换成四字整数 | ||
vcvttsd2siq |
用截断的方法把双精度数转换成四字整数 |
指令 | 源1 |
源2 |
目的 | 描述 |
---|---|---|---|---|
vcvttsi2ss |
/ | 把整数转换成单精度数 | ||
vcvttsi2sd |
/ | 把整数转换成双精度数 | ||
vcvttsi2ssq |
/ | 把四字整数转换成单精度数 | ||
vcvttsi2sdq |
/ | 把四字整数转换成双精度数 |
图3-48
中的指令把整数转换成浮点数。第一个操作数读自于内存或一个通用目的寄存器。第二个操作数只会影响结果的高位字节
。
例如如下指令:
vcvtsi2sdq %rax, %xmm1, %xmm1
该指令从寄存器%rax
读出一个长整数,把它转换成数据类型double
,并把结果存放进XMM
寄存器%xmm1
的低字节中。
假设%xmm0
的低位4字节保存着一个单精度值,使用如下指令:
vcvtss2sd %xmm0, %xmm0, %xmm0
把它转换成一个双精度值,并将结果存储在寄存器%xmm0
的低8字节。
GCC
生成的代码:
Conversion from single to double precision
1 vunpcklps %xmm0, %xmm0, %xmm0 `Replace first vector element`
2 vcvtps2pd %xmm0, %xmm0 `Convert two element to double`
vunpcklps
指令通常用来交叉放置来自两个XMM
寄存器的值,把它们存储到第三个寄存器中(例如:源
= [
,
,
,
], 源
= [
,
,
,
],那么目的寄存器
= [
,
,
,
]。)。
对于使用三个相同寄存器%xmm0
的,
= [
,
,
,
]
[
,
,
,
]。
对于指令vcvtps2pd
= [
,
,
,
]
[
,
,
,
]
对于指令vunpcklps
x_1$,
,
,
]
[
,
],这里的
是将
转换成双精度后的结果。
对于双精度转换为单精度,·GCC
会产生类似的代码:
Conversion from double to single precsion
1 vmovddup %xmm0, %xmm0 `Replicate first vector element`
2 vcvtpd2psx %xmm0, %xmm0 `Convert two vector elements to single`
假设这些指令开始执行前寄存器%xmm0
保存着两个双精度值[
,
]。然后vmovddup
指令把它设置为[
,
]。vcvtpd2psx
指令把这两个值转换成单精度,再存放到该寄存器的低位一般中,并将高位一半置0
,得到结果[0.0
, 0.0
,
,
]。
**fcvt
**的所有参数都是通过通用寄存器传递的,因为它们既不是整数也不是指针。
浮点传送和转换操作指令汇总
指令 | 源1 | 源2 | 目的 | 描述 |
---|---|---|---|---|
vmovss |
M32 |
NULL |
X |
传送单精度数 |
vmovss |
X |
NULL |
M32 |
传送单精度数 |
vmovsd |
M64 |
NULL |
X |
传送双精度数 |
vmovsd |
X |
NULL |
M64 |
传送双精度数 |
vmovaps |
X |
NULL |
X |
传送 对齐的封装好的单精度数 |
vmovapd |
X |
NULL |
X |
传送 对齐的封装好的双精度数 |
vcvttss2si |
X/M32 |
NULL |
R32 |
用截断的方法把单精度数转换成整数 |
vcvttsd2si |
X/M64 |
NULL |
R32 |
用截断的方法把双精度数转换成整数 |
vcvttss2siq |
X/M32 |
NULL |
R64 |
用截断的方法把单精度数转换成四字整数 |
vcvttsd2siq |
X/M64 |
NULL |
R64 |
用截断的方法把双精度数转换成四字整数 |
vcvtsi2ss |
M32/R32 |
X |
X |
把整数转换成单精度数 |
vcvtsi2sd |
M32/R32 |
X |
X |
把整数转换成双精度数 |
vcvtsi2ssq |
M64/R64 |
X |
X |
把四字整数转换成单精度数 |
vcvtsi2sdq |
M64/R64 |
X |
X |
把四字整数转换成双精度数 |
vcvtps2pd |
X1 |
NULL |
X2 |
**把 X1中两个低位单精度值扩展成 X2中的两个双精度值 |
vunpcklps |
X1 |
X2 |
X3 |
交叉放置 X1和 X2的值存储到 X3中 |
注:NULL
表示没有该源。
练习巩固:
答案:
3.11.2 过程中的浮点代码
在x86-64
中,XMM
寄存器用来向函数传递浮点参数,以及从函数返回浮点值。
1) XMM
寄存器%xmm0~%xmm7
最多可以传递8个浮点参数(额外的可以通过栈传递)。
2) 函数使用寄存器xmm0
来返回浮点值。
3) 所有的XMM
寄存器都是调用者保存的。被调用者可以不用保存就覆盖这些寄存器中任一个。
示例:
练习巩固:(易错点:注意指针)
答案:
3.11.3 浮点运算操作
图3-49
描述了一组执行算术运算的标量AVX2
浮点指令。
单精度 | 双精度 | 效果 | 描述 |
---|---|---|---|
vaddss |
vaddsd |
浮点数加 | |
vsubss |
vsubsd |
浮点数减 | |
vmulss |
vmulsd |
浮点数乘 | |
vdivss |
vdivsd |
浮点数除 | |
vmaxss |
vmaxsd |
浮点数最大值 | |
vminss |
vminsd |
浮点数最小值 | |
sqrtss |
sqrtsd |
浮点数最大值 |
示例:
练习巩固:
3.11.4 定义和使用浮点常数
和整数运算操作不同,AVX
浮点操作不能以立即数值作为操作数。相反,编译器必须为所有的常量值分配和初始化存储空间。
示例:
标号 | 低4 位 |
高4 位 |
数值 |
---|---|---|---|
.LC2 |
.long 3435973837
.long 0xcccc cccd |
.long 1073532108
.long 0x3ffc cccc |
1.8 |
.LC3 |
.long 0
.long 0x0 |
.long 1077936128
.long 0x4040 0000 |
32.0 |
具体编码过程请使劲点击它
二进制小数编码
简述过程:
1.8
编码过程:
,
exp
位不全为零,属于情况1
,规格化的值,
是8
位蓝色二进制的值。
32.0
编码过程:
,
exp
位不全为零,属于情况1
,规格化的值,
是8
位蓝色二进制的值。
3.11.5 在浮点代码中使用位级操作
单精度 | 双精度 | 效果 | 描述 |
---|---|---|---|
vxorps |
vorps |
^ | 位级异或(EXCLUSIVE-OR ) |
vandps |
andpd |
& | 位级与(AND ) |
tips
:
产生浮点数0.0
的汇编:
vxorpd %xmm0, %xmm0, %xmm0
3.11.6 浮点比较操作
AVX
提供了两条比较浮点值的指令(类似与CMP
指令,但是操作数顺序相反):
指令 | 基于 | 描述 |
---|---|---|
ucomiss
|
比较单精度值 | |
ucomisd
|
比较双精度值 |
与cmpq
一样,遵循以相反顺序列出操作数的ATT
格式惯例。参数
必须在XMM
寄存器中。
浮点比较指令会设置三个条件码:零标志位 ZF
、进位标志位CF
和奇偶标记位PF
。
两个操作数中任意一个是NaN
时,PF
会置1
。
当任一操作数为NaN
时,就会出现无序的情况。可以通过奇偶标志位发现这种情况。通常jp(jump on parity)
指令是条件跳转,条件就是浮点比较得到一个无序的结果。
示例:
解析如下图
练习巩固:
答案: