Recent

Author Topic: Different SysUtils object code for Windows/Linux on x86_64  (Read 643 times)

Starchild

  • New Member
  • *
  • Posts: 13
Hello,

I'm finding myself with a peculiar problem. I have a Pascal command-line application using Zeos (trunk) to access databases. I'd like to use this application on a 64-bit Linux server, so I installed a cross-compiler (x86_64-win64 to x86_64-linux, latest fixes3_2 revision) using FPCUpDeluxe and was able to successfully make a binary. The binary runs and is able to connect to the database etc., but as soon as it tries to access a timestamp field, it crashes with a floating point exception. When compiling for Win64, it works fine though.

I was able to trace the exception down to SysUtils.TimestampToMSecs (called from DateTimeToNative in Zeos):
Code: [Select]
.text:000000000047AE20                 lea     rsp, [rsp-18h]
.text:000000000047AE25                 mov     [rsp+18h+var_18], rdi
.text:000000000047AE29                 fild    dword ptr [rsp+18h+var_18+4]
.text:000000000047AE2D                 lea     rax, unk_67FA40
.text:000000000047AE34                 fild    qword ptr [rax]
.text:000000000047AE36                 fmulp   st(1), st
.text:000000000047AE38                 fild    dword ptr [rsp+18h+var_18]
.text:000000000047AE3B                 faddp   st(1), st
.text:000000000047AE3D                 fistp   [rsp+18h+var_10]
.text:000000000047AE41                 fild    [rsp+18h+var_10]
.text:000000000047AE45                 lea     rsp, [rsp+18h]
.text:000000000047AE4A                 retn
It crashes in the fild instruction with code 0xC0000090.

I then went and looked at the Win64 binary, and to my surprise the code there is much cleaner, utilizing SSE2:
Code: [Select]
.text:0000000100049640                 movsxd  rdx, dword ptr [rcx+4]
.text:0000000100049644                 imul    rdx, 5265C00h
.text:000000010004964B                 movsxd  rax, dword ptr [rcx]
.text:000000010004964E                 add     rax, rdx
.text:0000000100049651                 retn

So knowing that in terms of application logic, everything should be fine, I figured that it's probably just the x87 instruction set being a bitch (I really don't feel like diving into it deeper, it usually causes nothing but headaches). I then wanted to get FPC to emit SSE2 code for the Linux build of SysUtils too, but to no avail. I fiddled with the cross compiler options in FPCUpDeluxe (-CpCOREAVX -CfAVX -OpCOREAVX, also other variations for -Cf...) but it's being extremely stubborn and will always output the same legacy code. I don't get why, because the underlying processor architecture is the same for both systems and I don't see anything OS-specific in that function. The only thing I can think of is that it has some dumb ABI restrictions or gets confused by one of the million floating-point type aliases (Comp in this case).

By the way, when I cross-compile to i386-win32, I get the same FPU code with fild and it crashes at the same place. Native x64 is legit the only thing that's working.

It'd be great if someone could shed some light on this frustrating matter.

PascalDragon

  • Hero Member
  • *****
  • Posts: 716
  • Compiler Developer
Re: Different SysUtils object code for Windows/Linux on x86_64
« Reply #1 on: May 27, 2019, 09:16:26 am »
On x86_64-win64 Microsoft had decreed that the x87 FPU is deprecated, thus the highest precision type is Double there (and Extended is an alias to that) and the SSE instructions are always used.
On all other x86_64 targets however this is not the case. Thus the highest precision type is the 80-bit Extended type and the x87 FPU is used by default for the operations especially as the result for TimeStampToMSecs is a Comp which is a 64-bit integer type handled by the FPU on the x86 family (except Win64).

Would you please try to find out what value is passed to TimeStampToMSecs, write up a simple example program showing the crash and file a bug report?

nanobit

  • New Member
  • *
  • Posts: 43
Re: Different SysUtils object code for Windows/Linux on x86_64
« Reply #2 on: May 27, 2019, 12:57:49 pm »
@Starchild

Your report is missing information, does not say which line and which input value.
If the crash is at the start, you should consider:
Placing a floating point operation before your DateTimeToNative(), (which calls TimeStampToMSecs) to see if this call is really the cause. Your crash location might be delayed (is first fpu operation after the faulty fpu operation).

Starchild

  • New Member
  • *
  • Posts: 13
Re: Different SysUtils object code for Windows/Linux on x86_64
« Reply #3 on: May 27, 2019, 04:12:02 pm »
On x86_64-win64 Microsoft had decreed that the x87 FPU is deprecated, thus the highest precision type is Double there (and Extended is an alias to that) and the SSE instructions are always used.
On all other x86_64 targets however this is not the case. Thus the highest precision type is the 80-bit Extended type and the x87 FPU is used by default for the operations especially as the result for TimeStampToMSecs is a Comp which is a 64-bit integer type handled by the FPU on the x86 family (except Win64).

Would you please try to find out what value is passed to TimeStampToMSecs, write up a simple example program showing the crash and file a bug report?
That's rather unfortunate. I wonder though, why is Comp handled by the FPU? If it's not the 80-bit type then SSE should do just fine with it?

Turns out there already was a bug report for this, I've added my findings to it: https://bugs.freepascal.org/view.php?id=35147

Just curious, what do I need to manipulate in order to make the Linux64 compiler behave like Win64 in this regard? I know it'll break interop with other code badly, but I'd still like to try for shits&giggles.

Your report is missing information, does not say which line and which input value.
If the crash is at the start, you should consider:
Placing a floating point operation before your DateTimeToNative(), (which calls TimeStampToMSecs) to see if this call is really the cause. Your crash location might be delayed (is first fpu operation after the faulty fpu operation).
You can see there are plenty FPU operations in that function. The faulty operation is fistp (it doesn't write anything to the memory operand given to it and also doesn't pop the value from the FPU stack) and the condition is raised by the following instruction, fild, as I said in the first post.


Edit: I found the exact reason. That unk_67FA40 operand in the first instruction listing is supposed to be a pointer to the 64-bit constant MSecsPerDay. For some reason, it has a value of 0x4194997000000000 which is completely wrong and causes the multiplication result to be out of bounds. That in turn makes fistp unable to operate correctly. After patching the value in the binary to be 0x5265C00 (86400000), everything including the database stuff is now working correctly.
« Last Edit: May 27, 2019, 04:42:37 pm by Starchild »

PascalDragon

  • Hero Member
  • *****
  • Posts: 716
  • Compiler Developer
Re: Different SysUtils object code for Windows/Linux on x86_64
« Reply #4 on: May 28, 2019, 09:02:11 am »
On x86_64-win64 Microsoft had decreed that the x87 FPU is deprecated, thus the highest precision type is Double there (and Extended is an alias to that) and the SSE instructions are always used.
On all other x86_64 targets however this is not the case. Thus the highest precision type is the 80-bit Extended type and the x87 FPU is used by default for the operations especially as the result for TimeStampToMSecs is a Comp which is a 64-bit integer type handled by the FPU on the x86 family (except Win64).

Would you please try to find out what value is passed to TimeStampToMSecs, write up a simple example program showing the crash and file a bug report?
That's rather unfortunate. I wonder though, why is Comp handled by the FPU? If it's not the 80-bit type then SSE should do just fine with it?
Because it's a type that is defined to use 80-bit precision if available. And that is the case on the x86 family (again with the exception of Win64, where Microsoft decreed that the x87 is deprecated).

Turns out there already was a bug report for this, I've added my findings to it: https://bugs.freepascal.org/view.php?id=35147
Good. :)

Just curious, what do I need to manipulate in order to make the Linux64 compiler behave like Win64 in this regard? I know it'll break interop with other code badly, but I'd still like to try for shits&giggles.
You could try to disable the Extended type inside the compiler like it's done for Win64, but it could just as well be that something will simply bomb then... *shrugs*

Edit: I found the exact reason. That unk_67FA40 operand in the first instruction listing is supposed to be a pointer to the 64-bit constant MSecsPerDay. For some reason, it has a value of 0x4194997000000000 which is completely wrong and causes the multiplication result to be out of bounds. That in turn makes fistp unable to operate correctly. After patching the value in the binary to be 0x5265C00 (86400000), everything including the database stuff is now working correctly.
At least that narrows down the problem. :D