This is an archive of the discontinued LLVM Phabricator instance.

[libc++][math.h] Add double overloads
ClosedPublic

Authored by philnik on Nov 23 2022, 5:07 PM.

Details

Reviewers
ldionne
Group Reviewers
Restricted Project
Commits
rG928c81f3d1cb: [libc++][math.h] Add double overloads
Summary

This allows libc++ to work with libcs that don't provide a proper math.h implementation.

Diff Detail

Event Timeline

philnik created this revision.Nov 23 2022, 5:07 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 23 2022, 5:07 PM
philnik requested review of this revision.Nov 23 2022, 5:07 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 23 2022, 5:07 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript

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:

  1. 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?
  2. 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)?
  3. 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.
  4. 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
546

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).

  1. 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?

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

  1. 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

  1. 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.

  1. 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.

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...).

  1. 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).

  1. 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).

__buitin_memcpy works the same whether -fno-builtins is used or not, right? It's just that memcpy doesn't behave like __builtin_memcpy?

  1. 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).

__buitin_memcpy works the same whether -fno-builtins is used or not, right? It's just that memcpy doesn't behave like __builtin_memcpy?

Correct, sorry for being unclear.

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:

  1. on top of a C library that does not provide these math functions, and
  2. -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?

philnik updated this revision to Diff 480687.Dec 6 2022, 4:37 PM

Fix diff

ldionne accepted this revision.Dec 8 2022, 8:12 AM
ldionne added inline comments.
libcxx/include/math.h
545

Can you add a comment explaining how this works? Obviously just need to have one comment for the whole file.

This revision is now accepted and ready to land.Dec 8 2022, 8:12 AM
This revision was landed with ongoing or failed builds.Dec 8 2022, 1:45 PM
This revision was automatically updated to reflect the committed changes.
philnik marked an inline comment as done.