Context
C++23 makes many floating point math functions constexpr. Since those math functions’ implementations are quite involved, the path of least resistance is to make the corresponding clang builtins constexpr, and libc++'s cmath can simply call them. Many of the C23 floating point math functions have been implemented in LLVM libc, which are correctly rounded to all rounding modes, and they can used to back floating point math constexpr computations. Moreover, direct use of LLVM libc’s implementations without going through LLVM libc’s build system is proved to be a feasible strategy for sharing runtime implementations with [RFC] Project Hand In Hand (LLVM-libc/libc++ code sharing).
Proposal
We propose to directly use LLVM libc’s floating point math function implementations in APFloat, so that they can be used by clang to make builtin math functions constexpr, enabling C++23 constexpr math functions in libc++.
Otherwise, I think the direction mostly aligns with what other folks had in mind.
Note that we want to align the names of builtins across implementations (gcc, clang, msvc), which boils down to using the same names as GCC.
Given the large number of built-ins, what can we do in table gen?
Regarding floating point madness, the idea is to have correctly rounded constexpr math functions, and, hopefully, environment-independent.
Are there non-IEEE platforms we care about?
Do we need a way to have builtins selectively constexpr based on targets? Of course, our ability to support anything will depend on LLVM-libc support.
I think MSVC is also looking at using LLVM libc.
GCC has been using MPFR, which is why they have had constexpr math builtins for years, but the last time I checked, LLVM libc is the closest we have to MPFR that works on a wide variety of hosts, with a compatible license
I think the main non-IEEE float type to worry about is x86’s 80 bit long double.
For MSVC, that would be more difficult for two main reasons.
The first issue is API that LLVM-libc is providing to other LLVM projects isn’t intended to be stable. If we want to change the internal API and both pieces are in the monorepo, we can change both sides at once. Anyone trying to use the internal API outside the monorepo might be broken without warning.
The other issue is that LLVM-libc currently doesn’t support MSVC. I haven’t tried it but I would assume that it would require some changes. Adding support for another compiler isn’t impossible, but it is something that needs a more explicit discussion.
In general, I’m broadly in favor of reusing libc’s math implementations
to get support for math lib functions in APFloat. But in looking
through the current draft PR, I do have some concerns:
First, the use of the host platform for math computations gives me some
pause, in case somebody decides to compile LLVM with -ffast-math or
denormal flushing gets introduced by third parties without anyone
realize. I would feel somewhat better if we had some test results that
could fail if the host platform were compiled with -ffast-math.
A deeper concern is in supporting types other than float and double,
as host support for these types is less widespread. For the smaller
types (even all the weird 8-bit types that are only used in MLIR), it
should be sufficient to emulate them in at worst double and then round
them to the ultimate target; I don’t think there’s a high risk of
double-rounding in such scenarios, but for the small types, it’s easy
enough to exhaustively test all the cases.
How support for the larger-than-double types would look is something
that I think should be done early. long double ends up being one of
four different types on common platforms, so it is out of the question
to use that type for LLVM. Instead, it seems to me that you’ll have to
have some way to let the implementation of exp for (say) fp128 rely on
being able to do the leverage the soft float implementation of APFloat
anyways. And if you’re using soft-float for the largest types, maybe you
can also use them even for float and double and sidestep issues
around users screwing their compiler over with -ffast-math.
The other interesting intersection to me is the environment support for
these functions. The current libc implementations rely on modifying the
host floating-point environment, but for the APFloat implementation,
it makes sense to have explicit exception outputs and rounding mode
input parameters, as we already do for the core floating-point operations.
Thanks @cor3ntin for bringing up the tracking issues for libc++ constexpr math. I wanted to add it to the RFC post, but it looks like I can’t edit the post anymore. I’ll keep referring to those issues in the future PRs about this.
From the implementation point of view, I don’t see any problem with these questions. It’s probably something to discuss in detail in the clang / libc++ meetings.
In addition to the current IEEE 754 types: fp16, fp32, fp64, fp128, we will support the following non-IEEE 754 types: fp80, bfloat16, and double-double in LLVM libc. My goal is to make all of them available on all platforms, so that hopefully we can make the builtins constexpr unconditionally.
I agree that if LLVM is compiled with -ffast-math then all bets are off. We should definitely have some tests / warnings / guards against compiling LLVM with -ffast-math or denormal flushing.
At the moment, LLVM libc does have a templated soft-float implementations for all basic operations that are currently used for fp16 in many targets because their runtime libraries (libgcc or compiler-rt) are not correct for all rounding modes. They can also be a fallback for targets with funny float or double types.
For fp80 and fp128, our initial investigation together with the CORE-MATH researchers shows that for math functions, only use double-double and 128-bit integers in the implementations are actually faster than using fp80 and fp128 types in the computations, and they can be quite fast and a lot more portable.
With that, in LLVM libc, I plan to implement funcf80, funcf128, funcdd for fp80, fp128, and double-double variants of the math functions available for all targets, and the long double variant funcl will just be an alias to the corresponding type on specific targets.
For -ffast-math sneaked in building LLVM, I think we should warn the users instead of side-stepping on this part, because they are in for surprises in many other places than just APFloat.
About rounding mode support, at the moment, LLVM libc implementation only follows the current rounding mode of the floating point environment. A simple rounding-mode enforcing wrapper should be enough to support static-rounding-mode math functions. I already saw such wrappers APFloat, so I assume we can reuse them.
We should set up a meeting to discuss more about explicit exception support in more detail, and have some github issues to keep track of it.