FLD指令x64位

5

我在64位系统中使用FLD指令遇到了一点小问题... 想要将双精度浮点数加载到堆栈指针FPU的st0寄存器中,但似乎无法实现。 在Delphi x32中,我可以使用以下代码:

function DoSomething(X:Double):Double;
asm

  FLD    X
   // Do Something ..
  FST Result

end;

很不幸,在x64中,相同的代码无法运行。

定义“不工作”的含义。它是否崩溃?它是否无法编译?它是否没有返回预期的结果? - Michael
2
你有没有看过 Delphi 帮助文档中关于 Win64 兼容性的内容?它们说在 Win64 中没有 10 字节的 Extended 类型。这表明 Delphi Win64 不使用 FPU(x86),而是使用 SSE。因此,使用 FPU 指令会有问题。同时,在使用 BAsm x64 时要小心——存在会破坏数据甚至导致程序控制流反转的错误。 - Arioch 'The
在x86_64中,除非需要扩展精度,否则不应使用FPU。SSE更快且结果更一致。 - phuclv
3个回答

5
Delphi继承了Microsoft x64调用约定。因此,如果函数/过程的参数是float/double类型,则它们将传递到XMM0L、XMM1L、XMM2L和XMM3L寄存器中。
但是,您可以在参数前使用var作为解决方法,例如:
function DoSomething(var X:Double):Double;
asm
  FLD  qword ptr [X]
  // Do Something ..
  FST Result
end;

2
不错的解决方法。然而有一个限制,即您无法传递常量字面量,例如DoSomething(1.0),或声明为Single的变量。 - Ville Krumlinde
@Ville Krumlinde:确实,如果您需要使用常量参数调用函数,则应首先在“const”部分声明常量。 :) - GJ.

4
在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结果怎么样!!!为什么编译器接受加载结果..这是一个bug吗!! - SMP3
1
@SMP3:原来当你执行“FST Result”时,BASM会在堆栈上为结果分配临时存储空间,然后在结尾添加一条额外的指令,将xmm0加载为该值。我之前并不知道这一点。你可以在调试器的反汇编视图中自行查看。 - Ville Krumlinde
1
这是一个 bug 吗?不是。在 x64 上使用 SSE 而不是 x87。但你应该停止使用汇编让编译器来完成工作。 - David Heffernan

0

在x86-64代码中,您不需要使用传统的x87堆栈寄存器,因为SSE2是基线,是x86-64 ISA的必需部分。 您可以并且应该使用{{link1:addsd}},mulsdsqrtsd等XMM寄存器上的标量FP数学运算。 (或者对于float使用addss

如果它们是函数的前四个参数之一,则Windows x64调用约定将float / double FP参数传递到XMM0..3中。 (即第3个总参数进入xmm2,如果它是FP,则第3个FP参数进入xmm2。)它返回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
@rxantos:但是相对于什么失去了精度呢?x86-64编译器已经在float/double上使用SSE2进行数学计算,因此这是要比较的标准。此外,一些32位编译器(特别是MSVC)将x87单元设置为64位精度(53位尾数),以更接近C FLT_EVAL_METHOD=1语义,因此如果您使用该实现,那么额外的精度也不会存在。 - Peter Cordes
@rxantos:对于大多数科学计算来说,double 的精度已经足够了。对于一些问题,通过仔细的数值设计,可以使用32位的float来每个SIMD指令完成2倍的工作量。如果你真的关心FP舍入误差,你可以采取一些措施,比如Kahan求和来补偿数组求和时的误差,或者是成对求和。使用多个SIMD累加器展开是朝着这个方向迈出的一步,通常可以减少舍入误差。 - Peter Cordes
@rxantos:但是,如果你可以免费获得它(在性能方面),80位临时精度对于许多计算来说非常好,其中你没有故意进行补偿,并且双重舍入问题(到80位,然后再到64位)不会抵消其优点。请参见https://randomascii.wordpress.com/2012/03/21/intermediate-floating-point-precision/有关中间精度的信息,特别是在MSVC中,还有[Did any compiler fully use Intel x87 80-bit floating point?](https://retrocomputing.stackexchange.com/a/9760)。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接