FLD 指令 x64 位

Posted

技术标签:

【中文标题】FLD 指令 x64 位【英文标题】:FLD instruction x64 bit 【发布时间】:2013-04-03 11:42:34 【问题描述】:

我对 x64 位的 FLD 指令有一点问题... 想将 Double 值加载到 st0 寄存器中的堆栈指针 FPU,但这似乎是不可能的。 在 Delphi x32 中,我可以使用以下代码:

function DoSomething(X:Double):Double;
asm

  FLD    X
   // Do Something ..
  FST Result

end;

很遗憾,在 x64 中,相同的代码不起作用。

【问题讨论】:

定义“不起作用”。它会崩溃吗?它不编译吗?它没有返回预期的结果吗? 您是否阅读过 Delphi 帮助中有关 Win64 兼容性的信息?他们告诉 Win64 中没有 10 字节 Extended 类型。这表明 Delphi Win64 不使用 FPU (x86)。它改用 SSE。因此使用 FPU 指令是有问题的。使用 BAsm x64 时也要小心 - 存在破坏数据甚至逆向程序控制流的错误。 在 x86_64 中,除非您需要扩展精度,否则不应使用 FPU。 SSE 的结果更快、更一致 【参考方案1】:

Delphi 继承了Microsoft x64 Calling Convention。 因此,如果函数/过程的参数是浮点/双精度,则它们会在 XMM0L、XMM1L、XMM2L 和 XMM3L 寄存器中传递。

但是您可以在参数之前使用var 作为解决方法,例如:

function DoSomething(var X:Double):Double;
asm
  FLD  qword ptr [X]
  // Do Something ..
  FST Result
end;

【讨论】:

不错的解决方法。尽管您不能传递诸如 DoSomething(1.0) 之类的常量文字或声明为 Single 的变量的限制。 @Ville Krumlinde:确实,如果您需要使用常量参数调用函数,而不是在const 部分中,请首先声明常量。 :)【参考方案2】:

在 x64 模式下,浮点参数在 xmm 寄存器中传递。所以当 Delphi 试图编译 FLD X 时,它变成了 FLD xmm0 但没有这样的指令。您首先需要将其移动到内存中。

结果也是如此,应该在xmm0中传回。

试试这个(未测试):

function DoSomething(X:Double):Double;
var
  Temp : double;
asm
  MOVQ qword ptr Temp,X
  FLD Temp
  //do something
  FST Temp
  MOVQ xmm0,qword ptr Temp
end;

【讨论】:

>所以当Delphi 试图编译FLD X 时,它变成了FLD XMM0 ...这个FLD 结果怎么样!!!为什么编译器接受加载结果..这是一个错误! @SMP3 :事实证明,当您执行“FST 结果”时,BASM 会在堆栈上为结果分配一个临时存储空间,然后在末尾添加一条额外的指令以使用该值加载 xmm0。我不知道。在调试器的反汇编视图中自己查看。 这是一个错误吗?不,在 x64 上使用 SSE 而不是 x87。但是你应该停止做 asm 并让编译器完成工作。【参考方案3】:

您不需要在 x86-64 代码中使用旧版 x87 堆栈寄存器,因为 SSE2 是基线,是 x86-64 ISA 的必需部分。 您可以并且应该在 XMM 寄存器上使用 addsdmulsdsqrtsd 等进行标量 FP 数学运算。 (或 addss 浮动)

Windows x64 调用约定在 XMM0..3 中传递浮点/双浮点参数,如果它们是函数的前四个参数之一。 (即如果是 FP,则第 3 个总 arg 进入 xmm2,而不是 xmm2 中的第 3 个 FP arg。)它在 XMM0 中返回 FP 值。

只有在函数内部确实需要 80 位精度时才使用 x87。 (fsinfyl2x 之类的指令并不快,通常可以通过使用 SSE/SSE2 指令的普通数学库来完成。

function times2(X:Double):Double;
asm
    addsd  xmm0, xmm0       // upper 8 bytes of XMM0 are ignored
    ret
end

存储到内存并重新加载到 x87 寄存器会花费您大约 10 个延迟周期而没有任何好处。 SSE/SSE2 标量指令与 x87 等效指令一样快或更快,并且更易于编程和优化,因为您永远不需要 fxch;它是一种平面寄存器设计,而不是基于堆栈的。 (https://agner.org/optimize/)。此外,您有 15 个 XMM 寄存器。


当然,您通常根本不需要内联汇编。如果编译器不为您执行此操作,它可能对手动向量化很有用。

【讨论】:

“传统”的精度是 80 位,而“现代”的精度是 32 或 64 位,具体取决于指令。您将在计算过程中失去精度。这对于游戏来说是可以的,但对于某些应用程序来说就不行了。 @rxantos:与什么相比失去精度? x86-64 编译器已经在 float/double 上使用 SSE2 进行数学运算,因此这是比较的标准。此外,一些 32 位编译器(尤其是 MSVC)将 x87 单元设置为 64 位精度(53 位尾数)以更接近C FLT_EVAL_METHOD=1 语义,因此如果您使用该实现,则无论如何都不存在额外的精度. @rxantos: double 对于大多数科学计算来说已经足够精确了,并且通过对某些问题进行仔细的数值设计,您可以使用 32 位 float 将每条 SIMD 指令的工作量提高 2 倍。如果您真的关心 FP 舍入误差,您可以在对数组求和或成对求和时执行 Kahan 求和来补偿错误。 (Unrolling with multiple SIMD accumulators is a step in that direction,通常会减少舍入误差。) @rxantos: 但是是的,如果你可以免费获得它(就性能而言),80 位临时精度对于许多你不是故意补偿它的计算来说是很好的,并且在哪里双舍入问题(到 80 位,然后到 64 位)并没有超过好处。另请参阅randomascii.wordpress.com/2012/03/21/… re:中间精度,尤其是在 MSVC 中,另见 Did any compiler fully use Intel x87 80-bit floating point?

以上是关于FLD 指令 x64 位的主要内容,如果未能解决你的问题,请参考以下文章

SetThreadContext 只修改 x64 中 RIP 的最后 32 位

X86和X86_64和X64有什么区别?

x64 弹出指令(操作码 + rd)

x64 跳转指令

如何使用 x64dbg 记录程序执行的 CPU 指令?

“常言的x86”理解