除了“编译”选项以外,还有什么可以更改64位的代码生成?

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

介绍

我在我们的一个应用程序中遇到了货币问题。我在Win32和Win64中得到了不同的结果。我在这里找到了显示类似问题的文章,但该文章已在XE6中修复。我尝试做的第一件事是创建一个MCVE来复制该问题。那是车轮掉下来的地方。与应用程序相比,看起来像MCVE中的相同代码会产生不同的结果。生成的代码64位不同。因此,我的问题变成了为什么它们不同,一旦我弄清楚了,便可以创建合适的MCVE。

我有一种计算总数的方法。此方法调用另一个方法来获取需要添加到总计中的值。该方法返回单个。我将单个值分配给变量,然后将其添加到总计(货币)中。在我的主应用程序中,稍后将使用合计值,但将其添加到MCVE不会改变行为。我确保编译器选项相同。

在我的主应用程序中,计算的结果在Win32中为$ 2469.6001,在Win64中为2469.6,但是我无法在MCVE中重复。“编译选项”页面上的所有内容都相同,并且优化被禁用。

尝试MCVE

这是我尝试的MCVE的代码。这模仿了原始应用程序中的动作。

program Project4;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TTestClass = class
  strict private
    FMyCurrency: Currency;
    function GetTheValue: Single;
  public
    procedure Calculate;
    property MyCurrency: Currency read FMyCurrency write FMyCurrency;
  end;

procedure TTestClass.Calculate;
var
  myValue: Single;
begin
  FMyCurrency := 0.0;
  myValue := GetTheValue;
  FMyCurrency := FMyCurrency + myValue;
end;

function TTestClass.GetTheValue: Single;
var
  myValueExact: Int32;
begin
  myValueExact := 1159354778; // 2469.60009765625;
  Result := PSingle(@myValueExact)^;
end;

var
  testClass: TTestClass;
begin
  testClass := TTestClass.Create;
  try
    testClass.Calculate;
    WriteLn(CurrToStr(testClass.MyCurrency));
    ReadLn;
  finally
    testClass.Free;
  end;
end.
Run Code Online (Sandbox Code Playgroud)

这段代码为TTestClass.Calculate的最后两行生成以下汇编器:

Project4.dpr.25: myValue := GetTheValue;
00000000004242A8 488B4D40         mov rcx,[rbp+$40]
00000000004242AC E83F000000       call TTestClass.GetTheValue
00000000004242B1 F30F11452C       movss dword ptr [rbp+$2c],xmm0
Project4.dpr.26: FMyCurrency := FMyCurrency + myValue;
00000000004242B6 488B4540         mov rax,[rbp+$40]
00000000004242BA 488B4D40         mov rcx,[rbp+$40]
00000000004242BE F2480F2A4108     cvtsi2sd xmm0,qword ptr [rcx+$08]
00000000004242C4 F3480F5A4D2C     cvtss2sd xmm1,qword ptr [rbp+$2c]
00000000004242CA F20F590D16000000 mulsd xmm1,qword ptr [rel $00000016]
00000000004242D2 F20F58C1         addsd xmm0,xmm1
00000000004242D6 F2480F2DC8       cvtsd2si rcx,xmm0
00000000004242DB 48894808         mov [rax+$08],rcx
Run Code Online (Sandbox Code Playgroud)

主要用途

这是从主应用程序中摘录的。很难提供更多信息,但我认为这不会改变问题的性质。在此类中,FBulkTotal被声明为严格私有的货币。UpdateTotals是公开的。

procedure TMainApplicationClass.UpdateTotals(aMyObject: TMyObject);
var
  bulkTotal: Single;
begin
  ..
        bulkTotal := grouping.GetTotal(aMyObject, Self);
        FBulkTotal := FBulkTotal + bulkTotal;
  ..
end;
Run Code Online (Sandbox Code Playgroud)

这两行生成的代码是:

TheCodeUnit.pas.7357: bulkTotal := grouping.GetTotal(aMyObject, Self);
0000000006DB0804 488B4D68         mov rcx,[rbp+$68]
0000000006DB0808 488B9598000000   mov rdx,[rbp+$00000098]
0000000006DB080F 4C8B8590000000   mov r8,[rbp+$00000090]
0000000006DB0816 E8551C0100       call grouping.GetTotal
0000000006DB081B F30F114564       movss dword ptr [rbp+$64],xmm0
TheCodeUnit.pas.7358: FBulkTotal := FBulkTotal + bulkTotal;
0000000006DB0820 488B8590000000   mov rax,[rbp+$00000090]
0000000006DB0827 488B8D90000000   mov rcx,[rbp+$00000090]
0000000006DB082E F3480F2A8128010000 cvtsi2ss xmm0,qword ptr [rcx+$00000128]
0000000006DB0837 F30F104D64       movss xmm1,dword ptr [rbp+$64]
0000000006DB083C F30F590D54020000 mulss xmm1,dword ptr [rel $00000254]
0000000006DB0844 F30F58C1         addss xmm0,xmm1
0000000006DB0848 F3480F2DC8       cvtss2si rcx,xmm0
0000000006DB084D 48898828010000   mov [rax+$00000128],rcx
Run Code Online (Sandbox Code Playgroud)

奇怪的是,生成的代码是不同的。MCVE有一个cvtsi2sd和一个cvtss2sd,但是当将单个值的内容复制到xmm1寄存器中时,该主应用程序使用movs代替cvtss2sd。我很确定这是导致不同结果的原因,但是由于无法创建MCVE,我什至无法确认这是编译器存在的问题。

我的问题是什么会导致代码生成中的这些差异?我以为优化可以做这种事情,但是我确保它们是相同的。

Sil*_*ior -1

处理货币时不应使用任何浮点类型值。

我建议您观看 Computerphile 的浮点数视频,其中他解释了计算机如何处理浮点值以及为什么在处理货币时不应使用浮点值。 https://www.youtube.com/watch?v=PZRI1IfStY0