-
Notifications
You must be signed in to change notification settings - Fork 14.7k
Description
When LLVM needs to call a builtin (such as __truncdfhf2
or __addtf3
), it will use whatever ABI is the default for the target features of the function that is currently being compiled. This means that on targets where the ABI of float types is target-feature dependant (e.g. the ABI of half
changes of 32-bit x86 depending on whether sse2
is enabled, and the ABI of fp128
changes on PowerPC depending on whether vsx
is enabled), LLVM will call the same builtin function using two different incompatible ABIs.
The simplest solution to this seems to be extending the target-abi
module flag to allow frontends to specify which float ABI gets used for function calls independent of the target features of a function. Other solutions include allowing the frontend to override the names of builtin functions on a per-function-being-compiled level.
Related Rust issues: rust-lang/rust#131819, rust-lang/rust#125109
Activity
RalfJung commentedon Oct 18, 2024
I would argue this is a per-call-site decision, see also #70563.
IIUC, the most problematic situation here is code that is generally built for i386 without any SSE support, but then has some fast-paths for when SSE2 is available. That code will presumably be linked with a compiler_rt built without SSE support, but float operations inside the functions that have SSE2 enabled will expect the callee to use the SSE register. If
call
could encode which ABI it should use, then libcall lowering could synthesize calls that use the right registers, ignoring the target features enabled for the current function.nikic commentedon Oct 18, 2024
@RalfJung For calls to builtins, there is usually no
call
at the IR level. The call is inserted as part of legalizing a (non-call) IR operation.RalfJung commentedon Oct 18, 2024
Isn't that happening on some different IR? I'm not familiar with the lower ends of LLVM -- I heard there's Machine IR there.
If this is never in any IR it should be easier -- the legalization "just" has to pass through the information which ABI to use, to make it clear that this should not be adjusted to whatever the target features of the current function are. (I'm sure it's not actually easy, the devil is always in the details.)
arsenm commentedon Oct 19, 2024
The legalization call will be inserted in SelectionDAG or gMIR
tgross35 commentedon Oct 23, 2024
For reference both gcc and clang just reject
_Float16
with-mno-sse2
, and neither seem to support__float128
or_Float128
on PowerPC.beetrees commentedon Oct 23, 2024
Both GCC and Clang support
_Float128
/__float128
on PowerPC whenvsx
is enabled. Both require the-mfloat128
option to enable it: Clang will automatically enable thevsx
feature when-mfloat128
is used whereas GCC will emit an error if-mfloat128
is used andvsx
is not enabled.RalfJung commentedon Mar 17, 2025
@EugeneZelenko why was this tagged with "clang:..."? This is mostly an LLVM backend issue, causing some issues in Rust that will have to be resolved before we can fully ship f16 / f128.
So... does this mean that these builtins will actually be built with SSE support even on targets where SSE is off-by-default?
tgross35 commentedon Mar 18, 2025
What would an ideal solution look like here? If target features are always sufficient to indicate the ABI, then would a way to change the libcalls like
attributes #0 = { "target-features"="-sse", "libcall-legaliation"="FPROUND_F32_F16=__truncdfhf2_nosse" }
be enough?Which libraries? Since Clang and GCC both reject
_Float16
on-m32
r-mno-sse2
, I'm assuming there aren't any C libraries that can be providing these builtins. In the case of Rust's i586 no-sse target, LLVM treatshalf
as au16
and uses that ABI for builtins.LLVM actually seems to use different symbols for
f16
conversions based on hardware support, using either__gnu_h2f_ieee
/__gnu_f2h_ieee
or__extendhfsf2
/__truncsdhf2
based on (iirc)SoftPromoteFloat
. I haven't seen anything to indicate this is intentional, however. https://rust.godbolt.org/z/1qq9b8Paqarsenm commentedon Mar 18, 2025
This is leaking way too specific backend details in very a bad way. There should be a separate higher level ABI control to disable SSE registers, rather than having the target features directly set ABI
tgross35 commentedon Mar 18, 2025
What would this look like, since it needs to somehow be per-function controllable? As show in the top post's examples.
LLVM could also specify different builtin names, e.g. suffix with
_softfloat
or_hardfloat
when target features mean a different ABI from the default would be used.RalfJung commentedon Mar 18, 2025
Hm, I didn't say "library" so I am confused now...
What I mean is: there are certain standard symbols LLVM expects to exist that provide these operations, such as
__truncdfhf2
or__addtf3
. I assume there's some C implementation of them somewhere. And I guess that those are built with SSE as otherwisef16
couldn't even be used?LLVM is already leaking all those details. Frontends have to know which target features affect the ABI and how to be able to guard against LLVM just making up a different unexpected ABI.
I'd love to see this reformed. :)
beetrees commentedon Mar 18, 2025
LLVM
compiler-rt
will useuint16_t
for f16 if_Float16
is not supported by the C compiler (detected here, used here for extend and here for truncate).tgross35 commentedon Mar 18, 2025
I don't believe SSE is ever somehow enabled for the purpose of supporting these symbols. As far as I can tell, when vector registers are not available, LLVM changes its default ABI for these types to pass as integers and expects builtin calls to do the same (which is reasonable). This is visible in Clang for
__float128
, it gets passed as an i128 (register pair) without SSE. Clang rejects_Float16
earlier for whatever reason, but LLVM still expectshalf
builtins to usei16
.As beetrees mentioned, compiler-rt has a fallback that uses the integer ABI but I don't think it could ever be used from C (also GCC doesn't seem to have something similar), though it does have the
__gnu_h2f_ieee
functions on Arm). We can use it from Rust or other languages, however, and our implementation effectively does the same (even though we still usef16
, thanks to the LLVM fallback).GCC rejects
__float128
and_Float16
on x86-64 without SSE for anything that would return the types or call a builtin, though it allows the types as parameters. These get passed indirectly.Some experimentation https://gcc.godbolt.org/z/Yc5s5ocE5