(original) (raw)
Hi Jason,
The different behavior between Linux and Windows comes form the difference of the calling conversion. Windows uses 4 registers for arguments passing which Linux uses 6.
https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#parameter-passing
Thanks
Pengfei
From: llvm-dev On Behalf Of
Jason Hafer via llvm-dev
Sent: Friday, March 5, 2021 10:21 PM
To: Craig Topper
Cc: llvm-dev@lists.llvm.org
Subject: Re: \[llvm-dev\] Is it legal to pass a half by value on x86\_64?
Hi All,
Thank you very much for all the great information. This is awesome!
To circle back on Craig's questions.
I did notice LLVM 11 behave very differently.
\*\* Per: What does "incorrect math operations" mean?
The half is passed to the function as a float. The function does operations with other half numbers. On Windows when we don't get the float to half conversation the input is always truncated to 0.0.
\*\* Per: "Do you have a more complete IR file for Windows that I can take a look at?"
I can get you our IR if you want, but I think it is more convoluted than required. I was working on a unit test and I think all one needs to see the anomaly is:
define void @foo(i8, i8, i8, i8, half) {
; CHECK-I686: callq \_\_gnu\_f2h\_ieee
%6 = alloca half
store half %4, half\* %6, align 1
ret void
}
x86\_64-pc-windows gives:
push rax
.seh\_stackalloc 8
.seh\_endprologue
movss xmm0, dword ptr \[rsp + 48\] # xmm0 = mem\[0\],zero,zero,zero
movss dword ptr \[rsp + 4\], xmm0 # 4-byte Spill
pop rax
ret
.seh\_handlerdata
.text
.seh\_endproc
What I find extremely interesting is the behavior seems has something to do with the stack? For dropping the inputs by one then even Windows will generate the conversion.
define void @foo(i8, i8, i8, half) {
; CHECK-I686: callq \_\_gnu\_f2h\_ieee
%5 = alloca half
store half %3, half\* %5, align 1
ret void
}
x86\_64-pc-windows gives:
sub rsp, 40
.seh\_stackalloc 40
.seh\_endprologue
movabs rax, offset \_\_gnu\_f2h\_ieee
movaps xmm0, xmm3
call rax
mov word ptr \[rsp + 38\], ax
add rsp, 40
ret
.seh\_handlerdata
.text
.seh\_endproc
\*\* If interested, here is a dissection of our real asm.
For both Windows and Linux our IR calls c2\_foo() with a half(2):
...
call void @c2\_foo(i8\* %S\_6, \[21 x i8\*\]\* %ptr\_gvar\_instance\_7, %emlrtStack\* %c2\_b\_st\_, \[18 x float\]\* @15, half 0xH4000, \[18 x i8\]\* %t10)
They both register this in c2\_foo as:
...
%c2\_in2\_ = alloca half
store half %c2\_in2, half\* %c2\_in2\_, align 1
When we compile them, they both send 0x40000000 to c2\_foo (a single).
The Linux c2\_foo() asm addresses this with a float2half conversion:
...
mov qword ptr \[rsp + 448\], rdi
mov qword ptr \[rsp + 440\], rsi
mov qword ptr \[rsp + 432\], rdx
mov qword ptr \[rsp + 424\], rcx
movabs rcx, offset \_\_gnu\_f2h\_ieee # <---Convert Here
mov qword ptr \[rsp + 336\], r8 # 8-byte Spill
call rcx
mov word ptr \[rsp + 422\], ax
mov rcx, qword ptr \[rsp + 336\] # 8-byte Reload
mov qword ptr \[rsp + 408\], rcx
mov qword ptr \[rsp + 392\], 0
mov qword ptr \[rsp + 384\], 0
mov qword ptr \[rsp + 376\], 0
mov qword ptr \[rsp + 368\], 0
mov rdx, qword ptr \[rsp + 432\]
mov qword ptr \[rsp + 360\], rdx
mov rdx, qword ptr \[rsp + 432\]
mov rdx, qword ptr \[rdx + 8\]
mov qword ptr \[rsp + 352\], rdx
mov rdx, qword ptr \[rsp + 440\]
mov rdx, qword ptr \[rdx + 56\]
mov qword ptr \[rsp + 344\], rdx
mov dword ptr \[rsp + 400\], 0
jmp .LBB9\_9
The Windows c2\_foo() asm is missing this conversion but treats the value as if it has been converted.
...
mov rax, qword ptr \[rsp + 424\]
movss xmm0, dword ptr \[rsp + 416\] # xmm0 = mem\[0\],zero,zero,zero # <-- moves the data like it wants to convert but never does
mov qword ptr \[rsp + 344\], rcx
mov qword ptr \[rsp + 336\], rdx
mov qword ptr \[rsp + 328\], r8
mov qword ptr \[rsp + 320\], r9
mov qword ptr \[rsp + 304\], 0
mov qword ptr \[rsp + 296\], 0
mov qword ptr \[rsp + 288\], 0
mov qword ptr \[rsp + 280\], 0
mov rcx, qword ptr \[rsp + 328\]
mov qword ptr \[rsp + 272\], rcx
mov rcx, qword ptr \[rsp + 328\]
mov rcx, qword ptr \[rcx + 8\]
mov qword ptr \[rsp + 264\], rcx
mov rcx, qword ptr \[rsp + 336\]
mov rcx, qword ptr \[rcx + 56\]
mov qword ptr \[rsp + 256\], rcx
mov dword ptr \[rsp + 312\], 0
mov qword ptr \[rsp + 248\], rax # 8-byte Spill
movss dword ptr
From: Wang, Pengfei <pengfei.wang@intel.com>
Sent: Friday, March 5, 2021 7:30 AM
To: Sjoerd Meijer <Sjoerd.Meijer@arm.com>; Jason Hafer <jhafer@mathworks.com>
Cc: llvm-dev <llvm-dev@lists.llvm.org>
Subject: RE: Is it legal to pass a half by value on x86\_64?
I guess it’s designed for language portability. You can use this type across different platforms. Nevertheless, I’m not a FE expert, so I cannot think out other intentions.
The \_Float16 is a primitive type in the latest x86 ABI, but there’s no X86 target that supports it yet. So you cannot use it on X86 by now. I think that’s the difference from \_\_fp16 and why should use it.
We also have some discussion here. https://reviews.llvm.org/D97318
Thanks
Pengfei
From: Sjoerd Meijer <Sjoerd.Meijer@arm.com>
Sent: Friday, March 5, 2021 5:49 PM
To: Jason Hafer <jhafer@mathworks.com>; Wang, Pengfei <pengfei.wang@intel.com>
Cc: llvm-dev <llvm-dev@lists.llvm.org>
Subject: Re: Is it legal to pass a half by value on x86\_64?
\_\_fp16 is a pure storage format. You cannot pass it by value, because only ABI permissive types can be passed by value while \_\_fp16 is not one of them.
Yep. Any specific reason to use a pure storage format? The native type is \_Float16 and would give some benefits, but this is not yet supported on x86, see also:
Cheers,
Sjoerd.
From: llvm-dev <llvm-dev-bounces@lists.llvm.org> on behalf of Wang, Pengfei via llvm-dev <llvm-dev@lists.llvm.org>
Sent: 05 March 2021 06:28
To: Jason Hafer <jhafer@mathworks.com>
Cc: llvm-dev <llvm-dev@lists.llvm.org>
Subject: Re: \[llvm-dev\] Is it legal to pass a half by value on x86\_64?
Hi Jason,
\_\_fp16 is a pure storage format. You cannot pass it by value, because only ABI permissive types can be passed by value while \_\_fp16 is not one of them.
- if "define void @foo(i8, i8, i8, i8, half) " is even legal to use
half as a target independent type is legal for LLVM. It’s not legal for unsupported target like X86\. The behavior depends on how we lowering it. But I don’t know why there’s differences between Linux and Windows. Maybe because “\_\_gnu\_f2h\_ieee” is a Linux only function?
Thanks
Pengfei
From: llvm-dev <llvm-dev-bounces@lists.llvm.org>
On Behalf Of Jason Hafer via llvm-dev
Sent: Friday, March 5, 2021 10:46 AM
To: llvm-dev@lists.llvm.org
Cc: Jason Hafer <jhafer@mathworks.com>
Subject: \[llvm-dev\] Is it legal to pass a half by value on x86\_64?
Hello,
I am attempting to understand an anomaly I am seeing when dealing with half on Windows and could use some help.
Using LLVM 8 or 10, if I have IR of the flavor below:
define void @foo(i8, i8, i8, i8, half) {
%6 = alloca half
store half %4, half\* %6, align 1
...
ret void
}
Using x86\_64-pc-linux, we convert the float passed in with \_\_gnu\_f2h\_ieee.
Using x86\_64-pc-windows I do not get the conversion, so we end up with incorrect math operations.
While investigating I noticed clang gave me the error below:
error: parameters cannot have \_\_fp16 type; did you forget \* ?
void foo(int dc1, int dc2,int dc3,int dc4, \_\_fp16 in)
So, this got me wondering if "define void @foo(i8, i8, i8, i8, half) " is even legal to use or if I should rather pass by ref? I have yet to find documentation to convince me one way or the other. Thus, I was hoping someone here might be able to shed some light on the issue.
Thank you in advance!
Cheers,
JP