最近我遇到了以下一组向量指令:
movq xmm0, rcx
punpckldq xmm0, 0x4530000043300000
subpd xmm0, 0x4330000000000000
haddpd xmm0, xmm0
我根据常量找到的唯一合理信息是一个名为EmitFPVectorU64ToDouble
. 运行时行为似乎证实了这些指令的转换确实无符号整数上标量双精度浮点。
我正在寻找的是解释为什么这些指令能达到结果,背后的理论。
最近我遇到了以下一组向量指令:
movq xmm0, rcx
punpckldq xmm0, 0x4530000043300000
subpd xmm0, 0x4330000000000000
haddpd xmm0, xmm0
我根据常量找到的唯一合理信息是一个名为EmitFPVectorU64ToDouble
. 运行时行为似乎证实了这些指令的转换确实无符号整数上标量双精度浮点。
我正在寻找的是解释为什么这些指令能达到结果,背后的理论。
我想知道同样的事情。这些指令背后的想法是将 QWORD 拆分为两个 DWORD,然后分别生成两个对应的浮点数并将它们相加。这似乎是如何工作的:
代码段中的操作数看起来不完整,让我们将缺失的更高 QWORD 添加到操作数中:
movq xmm0, rcx
punpckldq xmm0, 0x00000000000000004530000043300000
subpd xmm0, 0x45300000000000004330000000000000
haddpd xmm0, xmm0
首先MOVQ 将QWORD 从 RCX 复制到 XMM0 的下半部分。然后,使用PUNPCKLDQ指令在XMM0 中构造两个双精度浮点数。它交错来自源和目标的两个 DWORD。在伪代码中,这可能如下所示:
xmm0 = ((0x43300000<<32)|xmm0[0:31]) | (((45300000<<32)|xmm0[32:63])<<64)
因此,每个双精度数都由两部分组成:一些魔术常量 + 32 位整数数据。为了理解这些魔法的作用,我们需要看一下 double 的内存布局。
因此,魔术设置了符号、指数和小数的20 个更高位。对于第一个双精度数(具有 0x43300000 魔法的那个),指数设置为 1075,这使得小数中最低有效位的贡献恰好为 1。这个技巧允许用任何无符号整数输入替换小数位,而无需转换。
LSB_contribution: 2^(1075-1023)*2^(-52) = 2^52*2^(-52) = 1
第二个魔术将指数设置为 1107,以使 LSB 的贡献等于 2^32 而不是 1,以匹配更高 DWORD 中的位大小。
由于浮点表示有隐含的 1 位添加到小数,因此结果值具有恒定偏移量,由SUBPD指令删除。它减去具有相同魔术常数和所有小数位设置为 0 的双精度值。
此时 XMM0 包含两个双精度值,它们对应于输入的 DWORDS,可以与HADDPD 相加以获得 XMM0 下半部分的最终结果(因此这是 64 位无符号整数到 64 位浮点数的转换)。