Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FMT_ATTACH_TO_GLOBAL_MODULE does not compile with clang #4081

Closed
kamrann opened this issue Jul 21, 2024 · 6 comments
Closed

FMT_ATTACH_TO_GLOBAL_MODULE does not compile with clang #4081

kamrann opened this issue Jul 21, 2024 · 6 comments

Comments

@kamrann
Copy link
Contributor

kamrann commented Jul 21, 2024

There appear to be two independent issues:

  1. os.h:386:16: error: declaration of 'buffer_size' with internal linkage cannot be exported. constexpr implying internal linkage has an exception for module interface units, but it seems when enclosed within extern "C++" clang ceases to apply this exception. I suspect clang is probably wrong here and fmt/MSVC are correct, but not 100% sure. I filed an issue with LLVM.
  2. error: declaration of 'assert_fail' in module fmt follows declaration in the global module. This one I suspect is more likely to be clang being correct and fmt/MSVC wrong to permit it, but again unsure. clang seems to want the definitions of things previously declared within extern "C++" to also be within such a block.

So it's possible there is no error here on fmt's side, but from experience, with two discrepancies between MSVC and clang, MSVC being on the correct side of both is unlikely!

@vitaut
Copy link
Contributor

vitaut commented Jul 23, 2024

Thanks for reporting. It seems that the second issue has an easy workaround of adding extern "C++". If this is the case a PR to implement it would be welcome. Not sure about the first one. cc @DanielaE

@DanielaE
Copy link
Contributor

Comments without studying the code or its changes in detail:

  • constexpr does not imply internal linkage, at all. Please look up the related sections in the standard.
  • extern "C++" within the purview of a module has the effect that all affected entities are no longer attached to the named module ('fmt' in this case), but rather to the global module. This changes the generated symbols and linker records, plus the compiler-internal entity tables. Please look up the related sections in the standard.
  • the application of FMT_ATTACH_TO_GLOBAL_MODULE is meant to enable using the same definitions regardless of their origin, be it either from an #include or from an import. A simple test:
#include <fmt/*> // definitions from this TU
import fmt; // definitions made reachable from the module interface TU

must work then because of [basic.def.odr]/15

Unfortunately, Clang 18 is still getting this wrong. To its excuse, it doesn't claim conformance to the standard.

@kamrann
Copy link
Contributor Author

kamrann commented Jul 23, 2024

It seems that the second issue has an easy workaround of adding extern "C++". If this is the case a PR to implement it would be welcome.

Happy to provide a PR if this looks like what is needed. It does appear to fix it, I'm just not familiar enough with the standard and modules to be certain that fmt is wrong here.

constexpr does not imply internal linkage, at all. Please look up the related sections in the standard

I spent quite a bit of time looking this stuff up before posting the issues. Perhaps my interpretation of the standard is off, that's why I needed some input/confirmation.

From [dcl.constexpr]:

A constexpr specifier used in an object declaration declares the object as const

And from [basic.link]:

The name of an entity that belongs to a namespace scope has internal linkage if it is the name of
  - a non-template variable of non-volatile const-qualified type, unless
    - it is declared in the purview of a module interface unit (outside the private-module-fragment, if any) or module partition

To me this says that constexpr variable definitions at namespace scope have internal linkage. And indeed outside the extern "C++" case all compilers appear to confirm this.

extern "C++" within the purview of a module has the effect that all affected entities are no longer attached to the named module ('fmt' in this case), but rather to the global module. This changes the generated symbols and linker records, plus the compiler-internal entity tables.

Yep, I'm aware it changes the attachment, but I don't follow how this relates to the question of linkage.

One option I believe would be to change the variable to be inline constexpr; I think this would safely fix the issue regardless of whether it turns out that clang is correct or not. I'll test this.

@kamrann
Copy link
Contributor Author

kamrann commented Jul 23, 2024

PRs submitted for each change.

With these two combined I can now build fmt successfully with FMT_ATTACH_TO_GLOBAL_MODULE, however the particular usage example @DanielaE gives above does indeed fail, giving errors on clang and an ICE on MSVC.

@DanielaE
Copy link
Contributor

You were speaking about constexpr at namespace scope within the purview of a module interface unit, right?
Otherwise, the whole discussion is moot.

This is from my tests in a different project where I check the coexistence of #include and import within the same TU, without compromising name lookup of the exported entities. In fact, there's more to it: the module is built using the modularized standard library, while doctest brings its own set of required standard library header #includes. Clang fails, MSVC passes:

#include <doctest.hpp>

// this must work according to [basic.def.odr]/15
// the same definitions come from *two different* TUs:
//   - this TU by means of #include <argparse.hpp>
//   - another TU that's creating module 'argparse'
//
// the definitions must *both* be attached to the global module!

#if defined(__clang__)
// the test fails to compile on Clang 18
constexpr bool included = false;
#else
constexpr bool included = true;
#include <argparse/argparse.hpp>
#endif

import argparse;

using doctest::skip;
using doctest::test_suite;

TEST_CASE("Module is usable in mixed mode" * test_suite("module") *
          skip(not included)) {
  REQUIRE(requires {
    typename argparse::nargs_pattern;
    {argparse::nargs_pattern::optional};
    {argparse::nargs_pattern::any};
    {argparse::nargs_pattern::at_least_one};
    typename argparse::default_arguments;
    {argparse::default_arguments::none};
    {argparse::default_arguments::all};
    {argparse::default_arguments::help};
    {argparse::default_arguments::version};
    {argparse::default_arguments::none & argparse::default_arguments::all};
    typename argparse::ArgumentParser;
    typename argparse::Argument;
  });
}

@kamrann
Copy link
Contributor Author

kamrann commented Jul 24, 2024

You were speaking about constexpr at namespace scope within the purview of a module interface unit, right? Otherwise, the whole discussion is moot.

Correct.

As mentioned above, trying this with fmt (with the changes from the PRs) fails on both MSVC and clang, with endless redefinition errors. Merely

#include <fmt/format.h>
import fmt;

is sufficient to break it on both compilers. So it seems there is some way to go before this can be used fully in a way that permits diamond dependency graphs within a TU without worrying about a shared dependency being consumed as both modules and includes. Even so, it still has value now I think, because it permits linking together TUs that consumed fmt in different ways.

@vitaut vitaut closed this as completed Jul 24, 2024
mtremer pushed a commit to ipfire/ipfire-2.x that referenced this issue Feb 22, 2025
- Update from version 11.0.2 to 11.1.3
- Update of rootfile
- Changelog
    11.1.3
	- Fixed compilation on GCC 9.4 (fmtlib/fmt#4313).
	- Worked around an internal compiler error when using C++20 modules with GCC
	  14.2 and earlier (fmtlib/fmt#4295).
	- Worked around a bug in GCC 6 (fmtlib/fmt#4318).
	- Fixed an issue caused by instantiating `formatter<const T>`
	  (fmtlib/fmt#4303,
	  fmtlib/fmt#4325). Thanks @timsong-cpp.
	- Fixed formatting into `std::ostreambuf_iterator` when using format string
	  compilation (fmtlib/fmt#4309,
	  fmtlib/fmt#4312). Thanks @phprus.
	- Restored a constraint on the map formatter so that it correctly reports as
	  unformattable when the element is (fmtlib/fmt#4326).
	  Thanks @timsong-cpp.
	- Reduced the size of format specs (fmtlib/fmt#4298).
	- Readded `args()` to `fmt::format_context`
	  (fmtlib/fmt#4307,
	  fmtlib/fmt#4310). Thanks @Erroneous1.
	- Fixed a bogus MSVC warning (fmtlib/fmt#4314,
	  fmtlib/fmt#4322). Thanks @ZehMatt.
	- Fixed a pedantic mode error in the CMake config
	  (fmtlib/fmt#4327). Thanks @rlalik.
    11.1.2
	- Fixed ABI compatibility with earlier 11.x versions
	  (fmtlib/fmt#4292).
	- Added `wchar_t` support to the `std::bitset` formatter
	  (fmtlib/fmt#4285,
	  fmtlib/fmt#4286,
	  fmtlib/fmt#4289,
	  fmtlib/fmt#4290). Thanks @phprus.
	- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via
	  `add_subdirectory` (fmtlib/fmt#4283).
	- Updated docs for meson (fmtlib/fmt#4291).
	  Thanks @trim21.
	- Fixed a compilation error in chrono on nvcc
	  (fmtlib/fmt#4297,
	  fmtlib/fmt#4301). Thanks @breyerml.
	- Fixed various warnings
	  (fmtlib/fmt#4288,
	  fmtlib/fmt#4299). Thanks @GamesTrap and @edo9300.
    11.1.1
	- Fixed ABI compatibility with earlier 11.x versions
	  (fmtlib/fmt#4278).
	- Defined CMake components (`core` and `doc`) to allow docs to be installed
	  separately (fmtlib/fmt#4276).
	  Thanks @carlsmedstad.
    11.1.0
	- Improved C++20 module support
	  (fmtlib/fmt#4081,
	  fmtlib/fmt#4083,
	  fmtlib/fmt#4084,
	  fmtlib/fmt#4152,
	  fmtlib/fmt#4153,
	  fmtlib/fmt#4169,
	  fmtlib/fmt#4190,
	  fmtlib/fmt#4234,
	  fmtlib/fmt#4239).
	  Thanks @kamrann and @Arghnews.
	- Reduced debug (unoptimized) binary code size and the number of template
	  instantiations when passing formatting arguments. For example, unoptimized
	  binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and
	  ~60% on clang (x86-64).
	  GCC:
	  - Before: 161 instructions of which 105 are in reusable functions
	    ([godbolt](https://www.godbolt.org/z/s9bGoo4ze)).
	  - After: 116 instructions of which 60 are in reusable functions
	    ([godbolt](https://www.godbolt.org/z/r7GGGxMs6)).
	  Clang:
	  - Before: 310 instructions of which 251 are in reusable functions
	    ([godbolt](https://www.godbolt.org/z/Ts88b7M9o)).
	  - After: 194 instructions of which 135 are in reusable functions
	    ([godbolt](https://www.godbolt.org/z/vcrjP8ceW)).
	- Added an experimental `fmt::writer` API that can be used for writing to
	  different destinations such as files or strings
	  (fmtlib/fmt#2354).
	  For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)):
		  ```c++
		  #include <fmt/os.h>
		  void write_text(fmt::writer w) {
		    w.print("The answer is {}.", 42);
		  }
		  int main() {
		    // Write to FILE.
		    write_text(stdout);
		    // Write to fmt::ostream.
		    auto f = fmt::output_file("myfile");
		    write_text(f);
		    // Write to std::string.
		    auto sb = fmt::string_buffer();
		    write_text(sb);
		    std::string s = sb.str();
		  }
		  ```
	- Added width and alignment support to the formatter of `std::error_code`.
	- Made `std::expected<void, E>` formattable
	  (fmtlib/fmt#4145,
	  fmtlib/fmt#4148).
	  For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)):
		  ```c++
		  fmt::print("{}", std::expected<void, int>());
		  ```
		  prints
		  ```
		  expected()
		  ```
	  Thanks @phprus.
	- Made `fmt::is_formattable<void>` SFINAE-friendly
	  (fmtlib/fmt#4147).
	- Added support for `_BitInt` formatting when using clang
	  (fmtlib/fmt#4007,
	  fmtlib/fmt#4072,
	  fmtlib/fmt#4140,
	  fmtlib/fmt#4173,
	  fmtlib/fmt#4176).
	  For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)):
		  ```c++
		  using int42 = _BitInt(42);
		  fmt::print("{}", int42(100));
		  ```
	  Thanks @Arghnews.
	- Added the `n` specifier for tuples and pairs
	  (fmtlib/fmt#4107). Thanks @someonewithpc.
	- Added support for tuple-like types to `fmt::join`
	  (fmtlib/fmt#4226,
	  fmtlib/fmt#4230). Thanks @phprus.
	- Made more types formattable at compile time
	  (fmtlib/fmt#4127). Thanks @AnthonyVH.
	- Implemented a more efficient compile-time `fmt::formatted_size`
	  (fmtlib/fmt#4102,
	  fmtlib/fmt#4103). Thanks @phprus.
	- Fixed compile-time formatting of some string types
	  (fmtlib/fmt#4065). Thanks @torshepherd.
	- Made compiled version of `fmt::format_to` work with
	  `std::back_insert_iterator<std::vector<char>>`
	  (fmtlib/fmt#4206,
	  fmtlib/fmt#4211). Thanks @phprus.
	- Added a formatter for `std::reference_wrapper`
	  (fmtlib/fmt#4163,
	  fmtlib/fmt#4164). Thanks @yfeldblum and @phprus.
	- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j`
	  and `%Y` (fmtlib/fmt#4161). Thanks @KKhanhH.
	- Made microseconds formatted as `us` instead of `µs` if the Unicode support is
	  disabled (fmtlib/fmt#4088).
	- Fixed an unreleased regression in transcoding of surrogate pairs
	  (fmtlib/fmt#4094,
	  fmtlib/fmt#4095). Thanks @phprus.
	- Made `fmt::appender` satisfy `std::output_iterator` concept
	  (fmtlib/fmt#4092,
	  fmtlib/fmt#4093). Thanks @phprus.
	- Made `std::iterator_traits<fmt::appender>` standard-conforming
	  (fmtlib/fmt#4185). Thanks @CaseyCarter.
	- Made it easier to reuse `fmt::formatter<std::string_view>` for types with
	  an implicit conversion to `std::string_view`
	  (fmtlib/fmt#4036,
	  fmtlib/fmt#4055). Thanks @Arghnews.
	- Made it possible to disable `<filesystem>` use via `FMT_CPP_LIB_FILESYSTEM`
	  for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK
	  (fmtlib/fmt#4257,
	  fmtlib/fmt#4258,
	  fmtlib/fmt#4259). Thanks @W4RH4WK and @phprus.
	- Fixed compatibility with platforms that use 80-bit `long double`
	  (fmtlib/fmt#4245,
	  fmtlib/fmt#4246). Thanks @jsirpoma.
	- Added support for UTF-32 code units greater than `0xFFFF` in fill
	  (fmtlib/fmt#4201).
	- Fixed handling of legacy encodings on Windows with GCC
	  (fmtlib/fmt#4162).
	- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference
	  (fmtlib/fmt#4261,
	  fmtlib/fmt#4262). Thanks @sascha-devel.
	- Added `fmt::dynamic_format_arg_store::size`
	  (fmtlib/fmt#4270). Thanks @hannes-harnisch.
	- Removed the ability to control locale usage via an undocumented
	  `FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`.
	- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other
	  similar macros.
	- Improved include directory ordering to reduce the chance of including
	  incorrect headers when using multiple versions of {fmt}
	  (fmtlib/fmt#4116). Thanks @cdzhan.
	- Made it possible to compile a subset of {fmt} without the C++ runtime.
	- Improved documentation and README
	  (fmtlib/fmt#4066,
	  fmtlib/fmt#4117,
	  fmtlib/fmt#4203,
	  fmtlib/fmt#4235). Thanks @zyctree and @nikola-sh.
	- Improved the documentation generator (fmtlib/fmt#4110,
	  fmtlib/fmt#4115). Thanks @rturrado.
	- Improved CI (fmtlib/fmt#4155,
	  fmtlib/fmt#4151). Thanks @phprus.
	- Fixed various warnings and compilation issues
	  (fmtlib/fmt#2708,
	  fmtlib/fmt#4091,
	  fmtlib/fmt#4109,
	  fmtlib/fmt#4113,
	  fmtlib/fmt#4125,
	  fmtlib/fmt#4129,
	  fmtlib/fmt#4130,
	  fmtlib/fmt#4131,
	  fmtlib/fmt#4132,
	  fmtlib/fmt#4133,
	  fmtlib/fmt#4144,
	  fmtlib/fmt#4150,
	  fmtlib/fmt#4158,
	  fmtlib/fmt#4159,
	  fmtlib/fmt#4160,
	  fmtlib/fmt#4170,
	  fmtlib/fmt#4177,
	  fmtlib/fmt#4187,
	  fmtlib/fmt#4188,
	  fmtlib/fmt#4194,
	  fmtlib/fmt#4200,
	  fmtlib/fmt#4205,
	  fmtlib/fmt#4207,
	  fmtlib/fmt#4208,
	  fmtlib/fmt#4210,
	  fmtlib/fmt#4220,
	  fmtlib/fmt#4231,
	  fmtlib/fmt#4232,
	  fmtlib/fmt#4233,
	  fmtlib/fmt#4236,
	  fmtlib/fmt#4267,
	  fmtlib/fmt#4271).
	  Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega,
	  @c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao.

Signed-off-by: Adolf Belka <[email protected]>
Signed-off-by: Michael Tremer <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants