Win32和Win64中的exAllArithmeticExceptions结果不一致

Gra*_*ter 8 delphi delphi-10-seattle

我的一位同事发现了Delphi编译的Win32和Win64代码在处理NaN方面的差异.以下面的代码为例.当以32位编译时,我们得不到消息,但是当用64位编译时,我们得到两个比较返回true.

program TestNaNs;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Math;

var
  nanDouble: Double;
  zereDouble: Double;
  nanSingle: Single;
  zeroSingle: Single;
begin
  SetExceptionMask(exAllArithmeticExceptions);
  nanSingle := NaN;
  zeroSingle := 0.0;
  if nanSingle <> zeroSingle then
    WriteLn('nanSingle <> zeroSingle');

  nanDouble := NaN;
  zereDouble := 0.0;
  if nanDouble <> zereDouble then
    WriteLn('nanDouble <> zeroDouble');

  ReadLn;
end.
Run Code Online (Sandbox Code Playgroud)

我对IEEE标准的理解是<>应该返回true,但所有其他操作都应该返回false.所以在这种情况下,看起来64位版本是正确的,32位版本是不正确的.两者生成的代码与生成SSE代码的64位版本非常不同.

对于32位:

TestNaNs.dpr.21: if nanSingle <> zeroSingle then
0041A552 D905E01E4200     fld dword ptr [$00421ee0]
0041A558 D81DE41E4200     fcomp dword ptr [$00421ee4]
0041A55E 9B               wait 
0041A55F DFE0             fstsw ax
0041A561 9E               sahf 
0041A562 7419             jz $0041a57d
Run Code Online (Sandbox Code Playgroud)

对于64位:

TestNaNs.dpr.21: if nanSingle <> zeroSingle then
000000000042764E F3480F5A05C9ED0000 cvtss2sd xmm0,qword ptr [rel $0000edc9]
0000000000427657 F3480F5A0DC4ED0000 cvtss2sd xmm1,qword ptr [rel $0000edc4]
0000000000427660 660F2EC1         ucomisd xmm0,xmm1
0000000000427664 7A02             jp Project63 + $68
0000000000427666 7420             jz Project63 + $88
Run Code Online (Sandbox Code Playgroud)

我的问题是这个.这是Delphi编译器的问题还是对Intel CPU的警告?

Dav*_*nan 4

IEEE 754 标准定义了浮点计算的算术格式、运算、舍入规则、例外等。Delphi 编译器在可用的硬件单元之上实现浮点运算。对于 32 位 Windows 编译器,这是 x87 单元,对于 64 位 Windows 编译器,这是 SSE 单元。这两个硬件单元均符合 IEEE 754 标准。

\n\n

您观察到的差异出现在语言实现级别。让我们更详细地看看这两个版本。

\n\n

32位Windows编译器

\n\n

比较语句编译为:

\n\n
\nTestNaNs.dpr.19: 如果 nanDouble <> ZeroDouble 则\n0041C4C8 DD05C03E4200 fld qword ptr [$00423ec0]\n0041C4CE DC1DC83E4200 fcomp qword ptr [$00423ec8]\n0041C4D4 9B 等待\n0041C4D5 D FE0 fstsw ax\n0041C4D7 9E sahf \n0041C4D8 7419 jz $0041c4f3\n
\n\n

英特尔软件开发人员手册称,无序比较由标志 C3、C2 和 C0 设置为 1 表示。完整的表格如下:

\n\n
\n条件 C3 C2 C0\nST(0) > 源 0 0 0\nST(0) < 源 0 0 1\nST(0) = 源 1 0 0\n无序 1 1 1\n
\n\n

当您在调试器下检查 FPU 时,您可以看到情况就是如此。

\n\n
\n0041C4D5 DFE0 fstsw ax\n0041C4D7 9E sahf \n0041C4D8 7419 jz $0041c4f3\n
\n\n

这会将 FPU 状态寄存器中的各个位传输到 CPU 标志中,有关标志所在位置的准确详细信息,请参阅手册。如果设置了 ZF,则会进行分支。ZF 的值来自 C3 FPU 标志,从上表中读取,该标志是为无序情况设置的。

\n\n

事实上,整个分支代码可以用伪代码表示为:

\n\n
\n如果 C3 = 1 则跳转\n
\n\n

因此,查看上表,很明显,如果其中一个操作数是 NaN,则任何浮点相等比较都会计算为 equals。

\n\n

64位Windows编译器

\n\n

比较语句编译为:

\n\n
\nTestNaNs.dpr.19: 如果 nanDouble <> ZeroDouble 则\n0000000000428EB8 F20F100548E50000 movsd xmm0,qword ptr [rel $0000e548]\n0000000000428EC0 660F2E0548E50000 ucomisd xmm0 ,qword ptr [rel $0000e548]\n0000000000428EC8 7A02 jp TestNaNs + $5C\n0000000000428ECA 7420 jz TestNaNs + $7C\n
\n\n

比较由ucomisd指令执行。手册给出了这个伪代码:

\n\n
\nRESULT \xe2\x86\x90 UnorderedCompare(SRC1[63:0] <> SRC2[63:0]) {\n(* 设置 EFLAGS *)\nCASE (RESULT) OF\n GREATER_THAN: ZF, PF, CF \ xe2\x86\x90 000;\n 小于:ZF、PF、CF \xe2\x86\x90 001;\n 等于:ZF、PF、CF \xe2\x86\x90 100;\n 无序:ZF、PF、CF \xe2\x86\x90 111;\nESAC;\nOF、AF、SF \xe2\x86\x90 0;\n
\n\n

请注意,在此指令中,ZF、PF 和 CF 标志与 x87 单元上的 C3、C2 和 C0 标志完全相同。

\n\n

分支由以下代码处理:

\n\n
\n0000000000428EC8 7A02 jp TestNaNs + $5C\n0000000000428ECA 7420 jz TestNaNs + $7C\n
\n\n

请注意,首先测试奇偶校验标志 PF(指令jp),然后测试零标志 ZF(jz指令)。因此,编译器发出了代码来处理无序情况(即操作数之一是 NaN)。这首先用jp。一旦处理完毕,编译器就会检查零标志 ZF,当且仅当两个操作数相等时才设置该标志(因为 NaN 已被处理)。

\n\n

结论

\n\n

不同的行为是由于不同的编译器在如何实现比较运算符方面采取了不同的选择。在这两种情况下,硬件均符合 IEEE 754 标准,并且完全能够按照标准的规定比较 NaN。

\n\n

我最好的猜测是,32 位编译器的决定是很久以前做出的。其中一些决定值得怀疑。在我看来,与 NaN 操作数的相等比较应该评估不等于,而不管其他操作数如何。通过保持向后兼容性的愿望感受到的历史的重量意味着这些有问题的决定从未得到解决。

\n\n

最近,当 64 位编译器创建时,Embarcadero 工程师决定纠正其中一些错误。他们大概觉得突破新架构让他们可以自由地这样做。

\n\n

在理想情况下,通过设置编译器开关,可以将 32 位编译器配置为与 64 位编译器具有相同的行为方式。

\n