This allows libc++ to work with libcs that don't provide a proper math.h implementation.
Details
- Reviewers
ldionne - Group Reviewers
Restricted Project - Commits
- rG928c81f3d1cb: [libc++][math.h] Add double overloads
Diff Detail
- Repository
- rG LLVM Github Monorepo
Unit Tests
Event Timeline
Pinging some Clang folks to get a better understanding of how Clang builtins work (feel free to suggest other people, I'm shooting randomly based on a quick git blame): @fhahn @Izaron @aaron.ballman
@bcain also for freestanding questions
I have a few questions:
- Which math functions does Clang define as builtin? Does it define all the overloads, or only the double one that's usually coming from the C library?
- What do these builtins do, exactly? I assume they try to do something clever and then fall back to a library-provided implementation (and they assume there's a suitable C-linkage definition somewhere)?
- Why are builtins that are not reserved identifiers (e.g. cos, sin) disabled in freestanding mode? I'm asking generally, I know cmath is not part of Freestanding per se.
- More generally speaking, what's the purpose of -fno-builtins?
Sorry, this is kind of open-ended, but I feel like this is important to understand here.
Also, on a related but tangential note, @philnik and I were trying to wrap our heads around what's the purpose of compiler builtin headers. It looks like the order of includes in Clang (and GCC) is: C++ library headers > compiler builtin headers > C library headers. This is true on both Linux and Darwin, so it's not accidental, it's really by design. Now, some headers exist only in the builtin headers (e.g. <stdbool.h>), some only in the C library headers (e.g. <stdio.h>), and some in both (e.g. <stdint.h>). Can anyone shed light on the logic behind this?
Is any of this documented anywhere?
libcxx/include/math.h | ||
---|---|---|
541 | Q: Is there any reason we are not using _LIBCPP_PREFERRED_OVERLOAD? Answer: GCC does not support it. Fair enough. |
I'll take __builtin_fmax as an example (but other builtins are fairly similar to it).
The canonical builtin versions take doubles as arguments, but there are builtin versions for other types (float, half, long double, long long double), here is the list of builtins:
https://github.com/llvm/llvm-project/blob/410c1f6269779a01ad24909974eafb0f2e8d8cac/clang/include/clang/Basic/Builtins.def#L262-L266
- What do these builtins do, exactly? I assume they try to do something clever and then fall back to a library-provided implementation (and they assume there's a suitable C-linkage definition somewhere)?
If we are inside a constexpr expression, some builtins can be calculated there, in this case Clang emulates the builtin's logic:
https://github.com/llvm/llvm-project/blob/e672f5126fcfca650534ee5fd81425df36c76eb6/clang/lib/AST/ExprConstant.cpp#L14060-L14076
If we are inside a runtime-calculated expression, then builtins are mapped to some other code. For example, __builtin_fmax is mapped into calling the llvm.maxnum.f64 intrinsic:
godbolt - https://godbolt.org/z/fboz3Phf8
intrinsics docs - https://releases.llvm.org/3.6.1/docs/LangRef.html#llvm-maxnum-intrinsic
- More generally speaking, what's the purpose of -fno-builtins?
This is a loanword from GCC. GCC artificially treats some functions (alloca, cos, sin, ...) as builtins, because this way it might generate more effective code instead calling these functions directly.
From docs: https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html
for instance, calls to alloca may become single instructions which adjust the stack directly, and calls to memcpy may become inline copy loops. The resulting code is often both smaller and faster, but since the function calls no longer appear as such, you cannot set a breakpoint on those calls, nor can you change the behavior of the functions by linking with a different library
The flag disables this behaviour.
The user expectation is that freestanding mode is the minimal set of library facilities required by the language, so if the language doesn't require cos, then that identifier needs to remain available for the user to use for their own purposes. Sometimes this is so they can make their own replacement for whatever funky hardware they're on, but sometimes it's because the standard steals general purpose identifiers like tan (color name), floor (a noun), and f16subf32 (er...).
- More generally speaking, what's the purpose of -fno-builtins?
To disable all builtins regardless of whether you're in freestanding mode or not. Sometimes the builtins do things you don't want them to do and so you want to disable them (perhaps you don't like the behavior of __builtin_memcpy for some reason), and sometimes the builtins expose names you want to use (https://godbolt.org/z/cMxzojcr1).
Sorry, this is kind of open-ended, but I feel like this is important to understand here.
No worries at all, these are great questions!
Also, on a related but tangential note, @philnik and I were trying to wrap our heads around what's the purpose of compiler builtin headers. It looks like the order of includes in Clang (and GCC) is: C++ library headers > compiler builtin headers > C library headers. This is true on both Linux and Darwin, so it's not accidental, it's really by design. Now, some headers exist only in the builtin headers (e.g. <stdbool.h>), some only in the C library headers (e.g. <stdio.h>), and some in both (e.g. <stdint.h>). Can anyone shed light on the logic behind this?
Is any of this documented anywhere?
I don't know if we document it anywhere, but my understanding of the situation is: there are headers the compiler has to provide because the compiler is the only thing that can implement it (think: stdarg.h), there are headers the compiler has to provide because they're in freestanding (think: stdbool.h), and finally there are all the rest of the headers which the compiler doesn't have to care about (think: stdio.h). Some header files do an include_next because the host header file may provide additional facilities (for example, limits.h may be extended by the POSIX headers).
__buitin_memcpy works the same whether -fno-builtins is used or not, right? It's just that memcpy doesn't behave like __builtin_memcpy?
Thanks a lot @aaron.ballman and @Izaron for the additional context around builtins and builtin headers, this has been useful.
So this patch is effectively a no-op as far as we're concerned in a world with compiler builtins. However, it would make libc++ work when used in the following combination:
- on top of a C library that does not provide these math functions, and
- -fno-builtins is passed to the compiler
I think we want to do this, however I am having a hard time just LGTMing it because we are not currently validating anything wrt this patch. Can you please rebase to at least get a green CI run?
libcxx/include/math.h | ||
---|---|---|
540 | Can you add a comment explaining how this works? Obviously just need to have one comment for the whole file. |
Can you add a comment explaining how this works? Obviously just need to have one comment for the whole file.