Введение
Я столкнулся с проблемой с валютой в одном из наших приложений. Я получал разные результаты в Win32 и Win64. Я нашел статью, которая показывает похожую проблему, но она была исправлена в XE6. Первое, что я попытался сделать, это создать MCVE для дублирования проблемы. Вот где отвалились колеса. То, что выглядит как идентичный код в MCVE, дает другой результат по сравнению с приложением. Сгенерированный код 64 бит отличается. Поэтому мой вопрос превратился в то, почему они разные, и как только я это выясню, я смогу создать подходящий MCVE.
У меня есть метод, который вычисляет итог. Этот метод вызывает другой метод, чтобы получить значение, которое необходимо добавить к итогу. Метод возвращает один. Я присваиваю единственное значение переменной, а затем добавляю его к итогу, который является валютой В моем основном приложении значение для итогового значения используется позже, но добавление его к MCVE не меняет поведение. Я убедился, что параметры компилятора были одинаковыми.
В моем основном приложении результат расчета составляет $ 2469,6001 в Win32 и 2469,6 в Win64, но я не могу продублировать это в 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.
Этот код генерирует следующий ассемблер для двух последних строк 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
Основное применение
Это выписка из основного приложения. Трудно дать больше информации, но я не думаю, что это изменит характер вопроса. В этом классе FBulkTotal объявлен как строго конфиденциальная Валюта. UpdateTotals является общедоступным.
procedure TMainApplicationClass.UpdateTotals(aMyObject: TMyObject);
var
bulkTotal: Single;
begin
..
bulkTotal := grouping.GetTotal(aMyObject, Self);
FBulkTotal := FBulkTotal + bulkTotal;
..
end;
Сгенерированный код для этих двух строк:
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
Странно, что сгенерированный код отличается. У MCVE есть cvtsi2sd, за которым следует cvtss2sd, но это основное приложение использует movss вместо cvtss2sd при копировании содержимого одного значения в регистр xmm1. Я почти уверен, что именно это приводит к другому результату, но, не имея возможности создать MCVE, я даже не могу подтвердить, что это проблема с компилятором.
Вопрос
Мой вопрос: что может вызвать эти различия в генерации кода? Я предполагал, что оптимизация может сделать это, но я убедился, что они одинаковы.