Introduction
I encountered a problem with Currency in one of our applications. I was getting different results in Win32 and Win64. I found an article here that shows a similar problem but that one was fixed in XE6. The first thing I tried to do was create a MCVE to duplicate the problem. That's where the wheels fell off. What looks like the identical code in the MCVE produces a different result compared to the application. The generated code 64 bit is different. So my question morphed into why are they different and once I figure that out then I can create a suitable MCVE.
I have a method that is calculating a total. This method calls another method to get a value that needs to be added to the total. The method returns a single. I assign the single value to a variable and then add it to the total which is a Currency. In my main application the value for the total is used later on but adding that to the MCVE doesn't change the behavior. I made sure that the compiler options were the same.
In my main application, the result from the calculation is $2469.6001 in Win32 and 2469.6 in Win64 but I can't duplicate this in the MCVE. Everything on the Compiling options page was the same and optimizations were disabled.
Attempted MCVE
Here is the code for my attempted MCVE. This mimics the actions in the original application.
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.
This code generates the following assembler for the last two lines of 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
Main Application
This is an extract from the main application. It's difficult to give more information but I don't think that will change the nature of the question. In this class, FBulkTotal is declared as a Currency that is strict private. UpdateTotals is public.
procedure TMainApplicationClass.UpdateTotals(aMyObject: TMyObject);
var
bulkTotal: Single;
begin
..
bulkTotal := grouping.GetTotal(aMyObject, Self);
FBulkTotal := FBulkTotal + bulkTotal;
..
end;
The generated code for these two lines is:
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
What's strange is that the generated code is different. The MCVE has a cvtsi2sd followed by a cvtss2sd but that main application uses a movss in place of the cvtss2sd when copying the contents of the single value into the xmm1 register. I pretty sure that is what is causing the different result but without being able to create a MCVE, I can't even confirm that it is a problem with the compiler.
Question
My question is what can cause these differences in code generation? I assumed that the optimizations could do this type of thing but I made sure those were the same.