llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.61k stars 11.82k forks source link

[clang] gcc compatibility regarding constexpr evaluation of __builtin_* for math functions #62049

Open chris-durand opened 1 year ago

chris-durand commented 1 year ago

GCC allows to evaluate all math built-ins in a constexpr context. libstdc++ even provides extended constexpr math functions, like constexpr std::sin. That library extension is in violation of the C++ standard but libstdc++ developers decided to keep it nevertheless.

The feature is especially useful in embedded code bases for small devices to do complex pre-computations at compile-time.

Could it be acceptable to make most math built-ins available from a constexpr context in Clang? The change should not cause any non-conformities since functions in libc++ are still not constexpr. Users who heavily rely on the constexpr behaviour could define their own functions using the built-ins and more easily migrate from GCC to Clang.

shafik commented 1 year ago

CC @AaronBallman @tbaederr for some feedback but I think this is reasonable

AaronBallman commented 1 year ago

I think it's fine to add constexpr builtin math functions where we can, but this might be a heavy lift in terms of ensuring correctness for some of the functions. e.g., ensuring that a transcendental function gives correct output in all the various floating-point types (float, double, long double, fp16 etc) for each target could be quite a bit of work.

At the very least, C++23 made a lot of math functions constexpr so those are potentially great targets for builtins.

llvmbot commented 1 year ago

@llvm/issue-subscribers-clang-frontend

tbaederr commented 1 year ago

To make this more actionable, is there a list of existing math builtins that aren't possible to use in a constexpr context in clang right now?

AaronBallman commented 1 year ago

To make this more actionable, is there a list of existing math builtins that aren't possible to use in a constexpr context in clang right now?

Anything without this marking, I believe: https://github.com/llvm/llvm-project/blob/f555fd5d8386d2d4f61a2675142af6b41a2f2972/clang/include/clang/Basic/Builtins.def#L108

At least, those are the ones that don't currently work -- I'm less certain on the "not possible" question because that's often a matter of how much effort someone is willing to put in to trying to implement it. @jcranmer-intel might have some more insight into that.

jcranmer-intel commented 1 year ago

At least, those are the ones that don't currently work -- I'm less certain on the "not possible" question because that's often a matter of how much effort someone is willing to put in to trying to implement it. @jcranmer-intel might have some more insight into that.

Roughly speaking, any function that is required by IEEE 754 should be reasonable to have a builtin constexpr evaluation. The functions that boil down to polynomial evaluation (basically anything with sin, cos, tan, pow, exp, or log in its name (except frexp, ldexp and logb), along with hypot, rqsrt, and compound) are going to be dicier, since there's no ready implementation in, e.g., APFloat for these methods, and different standard libraries will have different precisions. It's not impossible to implement the latter functions, it's just going to require asking some questions about which implementation gets used (borrowing libc's implementation may be the best solution going forward, given that llvm-libc is aiming for correctly-rounded implementation).

chris-durand commented 1 year ago

The functions that boil down to polynomial evaluation (basically anything with sin, cos, tan, pow, exp, or log in its name (except frexp, ldexp and logb), along with hypot, rqsrt, and compound) are going to be dicier, since there's no ready implementation in, e.g., APFloat for these methods, and different standard libraries will have different precisions. It's not impossible to implement the latter functions, it's just going to require asking some questions about which implementation gets used (borrowing libc's implementation may be the best solution going forward, given that llvm-libc is aiming for correctly-rounded implementation).

The LLVM constant-folding implementation also calls libc's double versions of those functions, see llvm/lib/Analysis/ConstantFolding.cpp. long double is not supported at all.

It would make a lot of sense if constexpr built-ins are consistent with what the optimizer evaluates at compile time. A target for this feature could be adding constexpr built-ins for math functions the optimizer already constant-folds.

P0533R9, which has been accepted into C++23, reasons that constexpr invocations of math functions should be rejected if errno would be set or any floating point exception except FE_INEXACT would be raised. That is exactly what the optimizer is checking now in llvm_fenv_testexcept(). In that case constant-folding fails and a run-time call is emitted.

chris-durand commented 1 year ago

I have prototyped an implementation for built-ins of some of those transcendental functions (like sin, cos, log) that evaluates them using the host libc, which is identical to what LLVM constant folding does: https://github.com/chris-durand/llvm-project/compare/main...chris-durand:llvm-project:feature/constexpr_math_builtins

Only float and double variants are implemented. long double would probably require a completely different implementation strategy due to varying precision on different platforms.

Does that approach seem acceptable? If yes, I'll continue and add tests.

AaronBallman commented 1 year ago

Does that approach seem acceptable? If yes, I'll continue and add tests.

Oh wow, today I learned that the LLVM backend is doing constant folding in a way that isn't conforming for C. I filed https://github.com/llvm/llvm-project/issues/62479 to track that issue.

I think it could be okay for us to rely on something like the implementation within llvm-libc when it's finished (on the assumption it will work correctly, with correct rounding, for the various targets we support), but I don't think we can rely on the host system's libc implementation (it very likely is not correctly rounding, depending on the implementation, for example) and we need to perform the calculations with at least as much precision as the source type, which makes long double an issue.

I think that unless/until llvm-libc is in a state where it can do this work for us, the implementation likely should live on APFloat and not be following the behavior of LLVM's constant folding. However, we should wait to hear if the LLVM folks have reason to believe my issue is a non-issue.

cor3ntin commented 1 year ago

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p1383r1.pdf should find its way into C++26. However, we should first progress https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0533r9.pdf - which is a C++23 feature, and which sidestep the floating point questions