Bug 286342 - libc++: compiling with -fmodules broken when including <locale>
Summary: libc++: compiling with -fmodules broken when including <locale>
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 15.0-CURRENT
Hardware: Any Any
: --- Affects Some People
Assignee: Dimitry Andric
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-04-25 10:26 UTC by Jordan Gordeev
Modified: 2025-06-01 09:48 UTC (History)
7 users (show)

See Also:


Attachments
A fix for the problem (1.13 KB, patch)
2025-05-16 15:36 UTC, Jordan Gordeev
no flags Details | Diff
Improved patch suitable for FreeBSD 14.3 (1.18 KB, patch)
2025-05-18 15:02 UTC, Jordan Gordeev
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jordan Gordeev 2025-04-25 10:26:03 UTC
The following two-line C++ program fails to compile with -fmodules on FreeBSD 15.0-CURRENT:
#include <locale>
int main() {}

The command executed is:
clang++ -fmodules demo.cpp

The error message is:
/usr/include/c++/v1/locale:730:68: error: use of undeclared identifier 'strtoll_l'

Verified on FreeBSD/arm64 15.0-CURRENT, snapshot from 2025-04-22.

Without -fmodules the program compiles successfully.

On FreeBSD 14.2 with or without -fmodules the program compiles successfully, so this can be considered a regression.

Other headers in libc++ (for example <vector> and <iostream>) include <locale> so the problem affects even programs that don't include <locale> directly.
Comment 1 Dimitry Andric freebsd_committer freebsd_triage 2025-04-26 12:13:03 UTC
Unfortunately I know very little about how modules work, so I won't really be able to help. It's probably better to take the problem upstream.
Comment 2 Shane 2025-04-27 10:54:03 UTC
Any chance your current build is less than 7 hours old? We just had a commit changing the llvm build settings, see https://mail-archive.freebsd.org/cgi/getmsg.cgi?fetch=425187+0+current/freebsd-current
Comment 3 Kurt Jaeger freebsd_committer freebsd_triage 2025-04-27 10:58:43 UTC
(In reply to Shane from comment #2)
You mean: "is older than 7 hours" ? So it should be retried with a more recent build ?
Comment 4 Shane 2025-04-27 23:21:43 UTC
I was thinking more that you just built current and the change may have introduced this result, but yeah the other way of it fixing this issue could be possible.
Comment 5 Jordan Gordeev 2025-05-11 15:36:33 UTC
Tested on arm64 with the following FreeBSD versions:

15.0-CURRENT (official snapshot from 2025-05-08): problem still present
14.3-BETA2 (official install image): problem present
13.5-RELEASE (official install image): problem present
Comment 6 Mark Millard 2025-05-11 20:53:22 UTC
FYI:

Note: I named things to use: -fmodules fmodules-test.cpp

# grep -r strtoll_l /usr/include/ | less
/usr/include/c++/v1/locale:    long long __ll                                               = strtoll_l(__a, &__p2, __base, _LIBCPP_GET_C_LOCALE);
/usr/include/c++/v1/__locale_dir/locale_base_api.h:long long strtoll_l(const char* str, char** str_end, locale_t);
/usr/include/xlocale/_stdlib.h:long long                 strtoll_l(const char *, char **, int, locale_t);

FYI for the /usr/include/c++/v1/locale use of strtoll_l:
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _Tp
__num_get_signed_integral(const char* __a, const char* __a_end, ios_base::iostate& __err, int __base) {
  if (__a != __a_end) {
    __libcpp_remove_reference_t<decltype(errno)> __save_errno = errno;
    errno                                                     = 0;
    char* __p2;
    long long __ll                                               = strtoll_l(__a, &__p2, __base, _LIBCPP_GET_C_LOCALE);
. . .

So, as stands, only *.h headers declare strtoll_l .

I got clang++19 installed on a Linux and it got:

$ grep -r strtoll_l /usr/lib/llvm-19/include/c++/v1/
/usr/lib/llvm-19/include/c++/v1/locale:    long long __ll                                               = strtoll_l(__a, &__p2, __base, _LIBCPP_GET_C_LOCALE);
/usr/lib/llvm-19/include/c++/v1/__support/xlocale/__strtonum_fallback.h:inline _LIBCPP_HIDE_FROM_ABI long long strtoll_l(const char* __nptr, char** __endptr, int __base, locale_t) {
/usr/lib/llvm-19/include/c++/v1/__locale_dir/locale_base_api.h:long long strtoll_l(const char* str, char** str_end, locale_t);
/usr/lib/llvm-19/include/c++/v1/__locale_dir/locale_base_api/ibm.h:inline _LIBCPP_HIDE_FROM_ABI long long strtoll_l(const char* __nptr, char** __endptr, int __base, locale_t locale) {
/usr/lib/llvm-19/include/c++/v1/__locale_dir/locale_base_api/musl.h:inline _LIBCPP_HIDE_FROM_ABI long long strtoll_l(const char* __nptr, char** __endptr, int __base, locale_t) {
/usr/lib/llvm-19/include/c++/v1/__locale_dir/locale_base_api/win32.h:#define strtoll_l _strtoi64_l

It was able to execute:

$ clang++-19 -fmodules fmodules-test.cpp
$ 

without complaints.

But on FreeBSD [main] there was no __strtonum_fallback.h :

# find -s / -name __strtonum_fallback.h -print
#
Comment 7 Jordan Gordeev 2025-05-16 15:36:31 UTC
Created attachment 260449 [details]
A fix for the problem

I'm attaching a patch that fixes the problem.

Here is my analysis of the problem:
The libc++ header <locale> needs symbols like strtoll_l() or sscanf_l().
The header file <xlocale.h> provides those symbols if it is included *after* <stdio.h> and <stdlib.h>.

When compiling without clang modules (which is the default), the header <locale> includes many other headers which transitively include <stdio.h> and <stdlib.h>. So, when the line in <locale> that includes <xlocale.h> is reached, the necessary prerequisite headers have already been included.

When compiling with clang modules the header <xlocale.h> is included from two submodules. The inclusion in /usr/include/c++/v1/__locale_dir/locale_base_api.h is done without first including <stdio.h> and <stdlib.h> and therefore doesn't provide the necessary symbols. The inclusion in <locale> is considered a semantic import of the submodule defined by __locale_dir/locale_base_api.h.

If you remove the include line for <xlocale.h> from __locale_dir/locale_base_api.h, the header <xlocale.h> will no longer be considered as belonging to the submodule defined by __locale_dir/locale_base_api.h and the include for <xlocale.h> in <locale> will properly provide the necessary symbols.
Comment 8 Mark Millard 2025-05-16 15:56:26 UTC
(In reply to Jordan Gordeev from comment #7)

Are you going to submit this upstream? The
proposed change is not to FreeBSD-specific
code but to upstream code that is imported.
Comment 9 Jordan Gordeev 2025-05-18 15:02:15 UTC
Created attachment 260505 [details]
Improved patch suitable for FreeBSD 14.3

I'm attaching a new patch. The improvement over the previous patch is that when compiling without clang modules the change from the patch is negligible.

This patch is suitable for inclusion in FreeBSD 14.3 which is close to being released.
It is also suitable for 15.0-CURRENT.

The patch relies on __has_feature(modules) compile-time check which can tell us if we are compiling with clang modules or not. The check allows riskier changes to be applied only for the case they are trying to fix ("with clang modules") and leave the other case ("without clang modules") unaffected. Adding this kind of check to libc++ code is not a big deal since there already is such a check in /usr/include/c++/v1/__config. 

To validate the effect of the patch when compiling without clang modules, compare the output of "clang++ -E demo.cpp" before and after applying the patch. The only observable changes are some line numbers in locale_base_api.h shifting slightly due to the addition of 6 extra lines.
Comment 10 Mark Millard 2025-05-18 17:44:02 UTC
(In reply to Jordan Gordeev from comment #9)

I do not know if FreeBSD or libc++ target
more than clang++ and g++ versions for
compatibility. __has_feature(x) is not from
the language standard, any version) but g++
has explicitly added support for clang++
compatibility.

https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension

documents a usage technique via the text:

#ifndef __has_feature         // Optional of course.
  #define __has_feature(x) 0  // Compatibility with non-clang compilers.
#endif
#ifndef __has_extension
  #define __has_extension __has_feature // Compatibility with pre-3.0 compilers.
#endif

...
#if __has_feature(cxx_rvalue_references)
// This code will only be compiled with the -std=c++11 and -std=gnu++11
// options, because rvalue references are only standardized in C++11.
#endif

#if __has_extension(cxx_rvalue_references)
// This code will be compiled with the -std=c++11, -std=gnu++11, -std=c++98
// and -std=gnu++98 options, because rvalue references are supported as a
// language extension in C++98.
#endif
Comment 11 commit-hook freebsd_committer freebsd_triage 2025-05-18 20:06:04 UTC
A commit in branch main references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=c809b0184d0a6543bc5327d4252fa56a07ce4689

commit c809b0184d0a6543bc5327d4252fa56a07ce4689
Author:     Jordan Gordeev <jgopensource@proton.me>
AuthorDate: 2025-05-18 19:55:05 +0000
Commit:     Dimitry Andric <dim@FreeBSD.org>
CommitDate: 2025-05-18 20:04:42 +0000

    libc++: fix compiling <locale> with -fmodules

    In /usr/include/c++/v1/__locale_dir/locale_base_api.h, xlocale.h is
    included without first including stdio.h and stdlib.h, which causes
    functions like strtoll_l() or sscanf_l() to not be declared.

    When compiling with -fmodules, locale_base_api.h is processed separately
    due to a declaration in /usr/include/c++/v1/module.modulemap, and this
    will cause errors due to the above undeclared symbols.

    Meanwhile, upstream has substantially reorganized this part of libc++'s
    headers, so apply a minimalistic workaround: specifically when compiling
    with -fmodules, add includes of stdio.h and stdlib.h.

    PR:             286342
    MFC after:      1 week

 .../llvm-project/libcxx/include/__locale_dir/locale_base_api.h    | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
Comment 12 commit-hook freebsd_committer freebsd_triage 2025-05-25 09:21:15 UTC
A commit in branch stable/14 references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=cab029073dc6cb5814df44556e585b8fdb1c9846

commit cab029073dc6cb5814df44556e585b8fdb1c9846
Author:     Jordan Gordeev <jgopensource@proton.me>
AuthorDate: 2025-05-18 19:55:05 +0000
Commit:     Dimitry Andric <dim@FreeBSD.org>
CommitDate: 2025-05-25 09:20:32 +0000

    libc++: fix compiling <locale> with -fmodules

    In /usr/include/c++/v1/__locale_dir/locale_base_api.h, xlocale.h is
    included without first including stdio.h and stdlib.h, which causes
    functions like strtoll_l() or sscanf_l() to not be declared.

    When compiling with -fmodules, locale_base_api.h is processed separately
    due to a declaration in /usr/include/c++/v1/module.modulemap, and this
    will cause errors due to the above undeclared symbols.

    Meanwhile, upstream has substantially reorganized this part of libc++'s
    headers, so apply a minimalistic workaround: specifically when compiling
    with -fmodules, add includes of stdio.h and stdlib.h.

    PR:             286342
    MFC after:      1 week

    (cherry picked from commit c809b0184d0a6543bc5327d4252fa56a07ce4689)

 .../llvm-project/libcxx/include/__locale_dir/locale_base_api.h    | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
Comment 13 commit-hook freebsd_committer freebsd_triage 2025-05-25 09:22:17 UTC
A commit in branch stable/13 references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=1c12934eba86c7294becc23c7518e0fb9b996fa8

commit 1c12934eba86c7294becc23c7518e0fb9b996fa8
Author:     Jordan Gordeev <jgopensource@proton.me>
AuthorDate: 2025-05-18 19:55:05 +0000
Commit:     Dimitry Andric <dim@FreeBSD.org>
CommitDate: 2025-05-25 09:20:41 +0000

    libc++: fix compiling <locale> with -fmodules

    In /usr/include/c++/v1/__locale_dir/locale_base_api.h, xlocale.h is
    included without first including stdio.h and stdlib.h, which causes
    functions like strtoll_l() or sscanf_l() to not be declared.

    When compiling with -fmodules, locale_base_api.h is processed separately
    due to a declaration in /usr/include/c++/v1/module.modulemap, and this
    will cause errors due to the above undeclared symbols.

    Meanwhile, upstream has substantially reorganized this part of libc++'s
    headers, so apply a minimalistic workaround: specifically when compiling
    with -fmodules, add includes of stdio.h and stdlib.h.

    PR:             286342
    MFC after:      1 week

    (cherry picked from commit c809b0184d0a6543bc5327d4252fa56a07ce4689)

 .../llvm-project/libcxx/include/__locale_dir/locale_base_api.h    | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
Comment 14 commit-hook freebsd_committer freebsd_triage 2025-05-28 16:27:46 UTC
A commit in branch releng/14.3 references this bug:

URL: https://cgit.FreeBSD.org/src/commit/?id=a0d22d7934c697d8af90f1a7f902e13dcf9b3a60

commit a0d22d7934c697d8af90f1a7f902e13dcf9b3a60
Author:     Jordan Gordeev <jgopensource@proton.me>
AuthorDate: 2025-05-18 19:55:05 +0000
Commit:     Dimitry Andric <dim@FreeBSD.org>
CommitDate: 2025-05-28 16:26:27 +0000

    libc++: fix compiling <locale> with -fmodules

    In /usr/include/c++/v1/__locale_dir/locale_base_api.h, xlocale.h is
    included without first including stdio.h and stdlib.h, which causes
    functions like strtoll_l() or sscanf_l() to not be declared.

    When compiling with -fmodules, locale_base_api.h is processed separately
    due to a declaration in /usr/include/c++/v1/module.modulemap, and this
    will cause errors due to the above undeclared symbols.

    Meanwhile, upstream has substantially reorganized this part of libc++'s
    headers, so apply a minimalistic workaround: specifically when compiling
    with -fmodules, add includes of stdio.h and stdlib.h.

    PR:             286342
    Approved by:    re (cperciva)
    MFC after:      1 week

    (cherry picked from commit c809b0184d0a6543bc5327d4252fa56a07ce4689)
    (cherry picked from commit cab029073dc6cb5814df44556e585b8fdb1c9846)

 .../llvm-project/libcxx/include/__locale_dir/locale_base_api.h    | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)