浮点数类型转换指令
程序员文章站
2022-05-28 12:09:17
...
在浮点寄存器概述一节中对浮点寄存器和浮点传送指令进行了相关介绍,这里将继续给出浮点数和整数数据类型之间以及不同浮点格式之间进行转换的指令集合,它们都是对单个数据值进行操作的标量指令。
首先来看一下将浮点数转换成整数的指令。
这些指令把一个从 XMM 寄存器(图中用 X 表示)或内存(图中用带内存范围下标的 M 表示)中读出的浮点值转换成对应的整数类型,并将结果写入一个通用寄存器(如 %rax 和 %ebx 等,图中用带范围下标的 R 表示)。在把浮点值转换成整数时,指令会执行截断,把值向 0 进行舍入。
将整数转换为浮点数则使用下面的指令。
这组指令使用了不太常见的三操作数格式,有两个源和一个目的。第一个操作数读自于内存或一个通用目的寄存器。第二个操作数目前可以忽略,通常写成和目的操作数一样,因为它的值只会影响结果的高位字节,而目标必须是 XMM 寄存器,不需要使用高位字节。比如指令“vcvtsi2sdq %rax, %xmm1, %xmm1”将从寄存器 %rax 读出一个长整数,把它转换成 double 类型,并把结果存放进 XMM 寄存器 %xmm1 的低 8 位字节中。
而对于不同浮点数格式之间的转换,GCC 的当前版本生成的汇编指令就比较奇怪了。比如,要将一个单精度浮点值转换成双精度,使用的不是类似于“vcvtss2sd %xmm0, %xmm0, %xmm0”这样直观的指令,而是会使用下面两条指令:
vunpck1ps %xmm0, %xmm0, %xmm0 ; Replicate first vector element
vcvtps2pd %xmm0, %xmm0 ; Convert two vector elements to double
其中,vunpck1ps 指令通常用来交叉放置来自两个 XMM 寄存器的值,把它们存储到第三个寄存器中。也就是说,如果一个源寄存器的内容为字 [s3, s2, s1, s0],另一个源寄存器为字 [d3, d2, d1, d0],那么目的寄存器的值会是 [s1, d1, s0, d0]。所以对于上面这种三个操作数都使用同一个寄存器的情况,假设原始寄存器的值为 [x3, x2, x1, x0],那么这条指令会将该寄存器的值更新为 [x1, x1, x0, x0]。而 vcvtps2pd 指令则会把源 XMM 寄存器中的两个低位单精度值扩展成目的 XMM 寄存器中的两个双精度值,因此对之前的 [x1, x1, x0, x0] 结果应用这条指令会得到值 [dx0, dx0],这里 dx0 表示将 x0 转换成双精度后的结果。因而上面两条指令的最终效果就是将原始的 %xmm0 低位 4 字节中的单精度值转换成双精度值,再将其两个副本保存到 %xmm0 中。
同理,对于把双精度转换为单精度,GCC 也没有直接使用指令“vcvtsd2ss %xmm0, %xmm0, %xmm0”,而是会产生类似的代码:
vmovddup %xmm0, %xmm0 ; Replicate first vector element
vcvtpd2psx %xmm0, %xmm0 ; Convert two vector elements to single
假设这些指令开始执行前寄存器 %xmm0 保存着两个双精度值 [dx1, dx0]。那么, vmovddup 指令会把它设置成 [dx0, dx0],接着 vcvtpd2psx 指令会把这两个值转换成单精度,再存放到该寄存器的低位一半中,并将高位一半设置为 0,得到结果 [0.0, 0.0, x0, x0]。
以下面的一个浮点转换的 C 函数为例。
则 GCC 生成的 x86-64 汇编代码类似如下。
参考书籍:《深入理解计算机系统》第三章——程序的机器级表示。
首先来看一下将浮点数转换成整数的指令。
这些指令把一个从 XMM 寄存器(图中用 X 表示)或内存(图中用带内存范围下标的 M 表示)中读出的浮点值转换成对应的整数类型,并将结果写入一个通用寄存器(如 %rax 和 %ebx 等,图中用带范围下标的 R 表示)。在把浮点值转换成整数时,指令会执行截断,把值向 0 进行舍入。
将整数转换为浮点数则使用下面的指令。
这组指令使用了不太常见的三操作数格式,有两个源和一个目的。第一个操作数读自于内存或一个通用目的寄存器。第二个操作数目前可以忽略,通常写成和目的操作数一样,因为它的值只会影响结果的高位字节,而目标必须是 XMM 寄存器,不需要使用高位字节。比如指令“vcvtsi2sdq %rax, %xmm1, %xmm1”将从寄存器 %rax 读出一个长整数,把它转换成 double 类型,并把结果存放进 XMM 寄存器 %xmm1 的低 8 位字节中。
而对于不同浮点数格式之间的转换,GCC 的当前版本生成的汇编指令就比较奇怪了。比如,要将一个单精度浮点值转换成双精度,使用的不是类似于“vcvtss2sd %xmm0, %xmm0, %xmm0”这样直观的指令,而是会使用下面两条指令:
vunpck1ps %xmm0, %xmm0, %xmm0 ; Replicate first vector element
vcvtps2pd %xmm0, %xmm0 ; Convert two vector elements to double
其中,vunpck1ps 指令通常用来交叉放置来自两个 XMM 寄存器的值,把它们存储到第三个寄存器中。也就是说,如果一个源寄存器的内容为字 [s3, s2, s1, s0],另一个源寄存器为字 [d3, d2, d1, d0],那么目的寄存器的值会是 [s1, d1, s0, d0]。所以对于上面这种三个操作数都使用同一个寄存器的情况,假设原始寄存器的值为 [x3, x2, x1, x0],那么这条指令会将该寄存器的值更新为 [x1, x1, x0, x0]。而 vcvtps2pd 指令则会把源 XMM 寄存器中的两个低位单精度值扩展成目的 XMM 寄存器中的两个双精度值,因此对之前的 [x1, x1, x0, x0] 结果应用这条指令会得到值 [dx0, dx0],这里 dx0 表示将 x0 转换成双精度后的结果。因而上面两条指令的最终效果就是将原始的 %xmm0 低位 4 字节中的单精度值转换成双精度值,再将其两个副本保存到 %xmm0 中。
同理,对于把双精度转换为单精度,GCC 也没有直接使用指令“vcvtsd2ss %xmm0, %xmm0, %xmm0”,而是会产生类似的代码:
vmovddup %xmm0, %xmm0 ; Replicate first vector element
vcvtpd2psx %xmm0, %xmm0 ; Convert two vector elements to single
假设这些指令开始执行前寄存器 %xmm0 保存着两个双精度值 [dx1, dx0]。那么, vmovddup 指令会把它设置成 [dx0, dx0],接着 vcvtpd2psx 指令会把这两个值转换成单精度,再存放到该寄存器的低位一半中,并将高位一半设置为 0,得到结果 [0.0, 0.0, x0, x0]。
以下面的一个浮点转换的 C 函数为例。
double fcvt(int i, float *fp, double *dp, long *lp){ float f = *fp; double d = *dp; long l = *lp; *lp = (long) d; *fp = (float) i; *dp = (double) l; return (double) f; }
则 GCC 生成的 x86-64 汇编代码类似如下。
;double fcvt(int i, float *fp, double *dp, long *lp) ;i in %edi, fp in %rsi, dp in %rdx, lp in %rcx fcvt: vmovss (%rsi), %xmm0 ; Get f = *fp movq (%rcx), %rax ; Get l = *lp vcvttsd2siq (%rdx), %r8 ; Get d = *dp and convert to long movq %r8, (%rcx) ; Store at lp vcvtsi2ss %edi, %xmm1, %xmm1 ; Convert i to float vmovss %xmm1, (%rsi) ; Store at fp vcvtsi2sdq %rax, %xmm1, %xmm1 ; Convert l to double vmovsd %xmm1, (%rdx) ; Store at dp ; The following two instructions convert f to double vunpcklps %xmm0, %xmm0, %xmm0 vcvtps2pd %xmm0, %xmm0 ret ; Return f
参考书籍:《深入理解计算机系统》第三章——程序的机器级表示。