diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -97,6 +97,12 @@ the shared library they shipped should turn this on and see `include/__availability` for more details." OFF) option(LIBCXX_ENABLE_CLANG_TIDY "Whether to compile and run clang-tidy checks" OFF) +# TODO MODULES Remove this option and test for the requirements (CMake/Clang) instead. +option(LIBCXX_ENABLE_STD_MODULES + "Whether to enable the building the C++23 `std` module. This feature is + experimental and has additional dependencies. Only enable this when + interested in testing or developing this module. See + https://libcxx.llvm.org/Modules.html for more information." OFF) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(LIBCXX_DEFAULT_TEST_CONFIG "llvm-libc++-shared-gcc.cfg.in") @@ -410,6 +416,7 @@ if(LLVM_ENABLE_PER_TARGET_RUNTIME_DIR AND NOT APPLE) set(LIBCXX_LIBRARY_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR}/${LLVM_DEFAULT_TARGET_TRIPLE}) set(LIBCXX_GENERATED_INCLUDE_DIR "${LLVM_BINARY_DIR}/include/c++/v1") + set(LIBCXX_GENERATED_MODULE_DIR "${LLVM_BINARY_DIR}/modules/c++/v1") set(LIBCXX_GENERATED_INCLUDE_TARGET_DIR "${LLVM_BINARY_DIR}/include/${LLVM_DEFAULT_TARGET_TRIPLE}/c++/v1") set(LIBCXX_INSTALL_LIBRARY_DIR lib${LLVM_LIBDIR_SUFFIX}/${LLVM_DEFAULT_TARGET_TRIPLE} CACHE PATH "Path where built libc++ libraries should be installed.") @@ -423,9 +430,11 @@ if(LLVM_LIBRARY_OUTPUT_INTDIR) set(LIBCXX_LIBRARY_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR}) set(LIBCXX_GENERATED_INCLUDE_DIR "${LLVM_BINARY_DIR}/include/c++/v1") + set(LIBCXX_GENERATED_MODULE_DIR "${LLVM_BINARY_DIR}/modules/c++/v1") else() set(LIBCXX_LIBRARY_DIR ${CMAKE_BINARY_DIR}/lib${LIBCXX_LIBDIR_SUFFIX}) set(LIBCXX_GENERATED_INCLUDE_DIR "${CMAKE_BINARY_DIR}/include/c++/v1") + set(LIBCXX_GENERATED_MODULE_DIR "${CMAKE_BINARY_DIR}/modules/c++/v1") endif() set(LIBCXX_GENERATED_INCLUDE_TARGET_DIR "${LIBCXX_GENERATED_INCLUDE_DIR}") set(LIBCXX_INSTALL_LIBRARY_DIR lib${LIBCXX_LIBDIR_SUFFIX} CACHE PATH @@ -856,6 +865,9 @@ add_subdirectory(include) add_subdirectory(src) add_subdirectory(utils) +if (LIBCXX_ENABLE_STD_MODULES) + add_subdirectory(modules) +endif() set(LIBCXX_TEST_DEPS "cxx_experimental") @@ -867,6 +879,10 @@ list(APPEND LIBCXX_TEST_DEPS cxx-tidy) endif() +if (LIBCXX_ENABLE_STD_MODULES) + list(APPEND LIBCXX_TEST_DEPS generate-cxx-modules generate-test-module-std) +endif() + if (LIBCXX_INCLUDE_BENCHMARKS) add_subdirectory(benchmarks) endif() diff --git a/libcxx/cmake/caches/Generic-module-std-cxx23.cmake b/libcxx/cmake/caches/Generic-module-std-cxx23.cmake new file mode 100644 --- /dev/null +++ b/libcxx/cmake/caches/Generic-module-std-cxx23.cmake @@ -0,0 +1,4 @@ +set(LIBCXX_ENABLE_STD_MODULES ON CACHE BOOL "") +set(LIBCXX_TEST_PARAMS "enable_modules=std;std=c++23" CACHE STRING "") +set(LIBCXXABI_TEST_PARAMS "std=c++23" CACHE STRING "") + diff --git a/libcxx/cmake/caches/Generic-modules-lsv.cmake b/libcxx/cmake/caches/Generic-modules-lsv.cmake --- a/libcxx/cmake/caches/Generic-modules-lsv.cmake +++ b/libcxx/cmake/caches/Generic-modules-lsv.cmake @@ -1,2 +1,2 @@ -set(LIBCXX_TEST_PARAMS "enable_modules=True;enable_modules_lsv=True" CACHE STRING "") +set(LIBCXX_TEST_PARAMS "enable_modules=clang;enable_modules_lsv=True" CACHE STRING "") set(LIBCXXABI_TEST_PARAMS "${LIBCXX_TEST_PARAMS}" CACHE STRING "") diff --git a/libcxx/cmake/caches/Generic-modules.cmake b/libcxx/cmake/caches/Generic-modules.cmake --- a/libcxx/cmake/caches/Generic-modules.cmake +++ b/libcxx/cmake/caches/Generic-modules.cmake @@ -1,2 +1,2 @@ -set(LIBCXX_TEST_PARAMS "enable_modules=True" CACHE STRING "") +set(LIBCXX_TEST_PARAMS "enable_modules=clang" CACHE STRING "") set(LIBCXXABI_TEST_PARAMS "${LIBCXX_TEST_PARAMS}" CACHE STRING "") diff --git a/libcxx/docs/Contributing.rst b/libcxx/docs/Contributing.rst --- a/libcxx/docs/Contributing.rst +++ b/libcxx/docs/Contributing.rst @@ -44,6 +44,7 @@ - Did you update the synopsis of the relevant headers? - Did you update the relevant files to track implementation status (in ``docs/Status/``)? - Did you mark all functions and type declarations with the :ref:`proper visibility macro `? +- Did you add all new named declarations to the ``std`` module? - If you added a header: - Did you add it to ``include/module.modulemap.in``? diff --git a/libcxx/docs/Modules.rst b/libcxx/docs/Modules.rst new file mode 100644 --- /dev/null +++ b/libcxx/docs/Modules.rst @@ -0,0 +1,226 @@ +.. _ModulesInLibcxx: + +================= +Modules in libc++ +================= + +.. warning:: Modules are an experimental feature. It has additional build + requirements and not all libc++ configurations are supported yet. + + The work is still in an early developement state and not + considered stable nor complete + +This page contains information regarding C++23 module support in libc++. +There are two kinds of modules available in Clang + + * `Clang specific modules `_ + * `C++ modules `_ + +This page mainly discusses the C++ modules. In C++20 there are also header units, +these are not part of this document. + +Overview +======== + +The module sources are stored in ``.cppm`` files. Modules need to be available +as BMIs, which are ``.pcm`` files for Clang. BMIs are not portable, they depend +on the compiler used and its compilation flags. Therefore there needs to be a +way to distribute the ``.cppm`` files to the user and offer a way for them to +build and use the ``.pcm`` files. It is expected this will be done by build +systems in the future. To aid early adaptor and build system vendors libc++ +currently ships a CMake project to aid building modules. + +.. note:: This CMake file is intended to be a temporary solution and will + be removed in the future. The timeline for the removal depends + on the availability of build systems with proper module support. + +What works +~~~~~~~~~~ + + * Building BMIs + * Running tests using the ``std`` module + * Using the ``std`` module in external projects + * The following "parts disabled" configuration options are supported + + * ``LIBCXX_ENABLE_LOCALIZATION`` + * ``LIBCXX_ENABLE_WIDE_CHARACTERS`` + +Some of the current limitations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * There is no official build system support, libc++ has experimental CMake support + * Requires CMake 3.26 + * Requires Ninja 1.11 + * Requires a recent Clang 17 + * The path to the compiler may not be a symlink, ``clang-scan-deps`` does + not handle that case properly + * Only C++23 is tested + * Libc++ is not tested with modules instead of headers + * The module ``.cppm`` files are not installed + * The experimental ``PSTL`` library is not supported + * Clang supports modules using GNU extensions, but libc++ does not work using + GNU extensions. + * Clang: + * Including headers after importing the ``std`` module may fail. This is + hard to solve and there is a work-around by first including all headers + `bug report `__. + +Blockers +~~~~~~~~ + + * libc++ + + * Currently the tests only test with modules enabled, but do not import + modules instead of headers. When converting tests to using modules there + are still failures. These are under investigation. + + * It has not been determined how to fully test libc++ with modules instead + of headers. + + * Clang + + * Some concepts do not work properly + `bug report `__. + + +Using in external projects +========================== + +Users need to be able to build their own BMI files. + +.. note:: The requirements for users to build their own BMI files will remain + true for the forseeable future. For now this needs to be done manually. + Once libc++'s implementation is more mature we will reach out to build + system vendors, with the goal that building the BMI files is done by + the build system. + +Currently this requires a local build of libc++ with modules enabled. Since +modules are not part of the installation yet, they are used from the build +directory. First libc++ needs to be build with module support enabled. + +.. code-block:: bash + + $ git clone https://github.com/llvm/llvm-project.git + $ cd llvm-project + $ mkdir build + $ cmake -G Ninja -S runtimes -B build -DLIBCXX_ENABLE_STD_MODULES=ON -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind" + $ ninja -C build + +The above ``build`` directory will be referred to as ```` in the +rest of these instructions. + +This is a small sample program that uses the module ``std``. It consists of a +``CMakeLists.txt`` and a ``main.cpp`` file. + +.. code-block:: cpp + + import std; + + int main() { std::cout << "Hello modular world\n"; } + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.26.0 FATAL_ERROR) + project("module" + LANGUAGES CXX + ) + + # + # Set language version used + # + + # At the moment only C++23 is tested. + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_STANDARD_REQUIRED YES) + # Libc++ doesn't support compiler extensions for modules. + set(CMAKE_CXX_EXTENSIONS OFF) + + # + # Enable modules in CMake + # + + # This is required to write your own modules in your project. + if(CMAKE_VERSION VERSION_LESS "3.27.0") + set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a") + else() + set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7") + endif() + set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1) + + # + # Import the modules from libc++ + # + + include(FetchContent) + FetchContent_Declare( + std + URL "file://${LIBCXX_BUILD}/modules/c++/v1/" + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + FetchContent_GetProperties(std) + if(NOT std_POPULATED) + FetchContent_Populate(std) + add_subdirectory(${std_SOURCE_DIR} ${std_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() + + # + # Adjust project compiler flags + # + + add_compile_options($<$:-fprebuilt-module-path=${CMAKE_BINARY_DIR}/_deps/std-build/CMakeFiles/std.dir/>) + add_compile_options($<$:-nostdinc++>) + # The include path needs to be set to be able to use macros from headers. + # For example from, the headers and . + add_compile_options($<$:-isystem>) + add_compile_options($<$:${LIBCXX_BUILD}/include/c++/v1>) + + # + # Adjust project linker flags + # + + add_link_options($<$:-nostdlib++>) + add_link_options($<$:-L${LIBCXX_BUILD}/lib>) + add_link_options($<$:-Wl,-rpath,${LIBCXX_BUILD}/lib>) + # Linking against std is required for CMake to get the proper dependencies + link_libraries(std c++) + + # + # Add the project + # + + add_executable(main) + target_sources(main + PRIVATE + main.cpp + ) + +Building this project is done with the following steps, assuming the files +``main.cpp`` and ``CMakeLists.txt`` are copied in the current directory. + +.. code-block:: bash + + $ mkdir build + $ cmake -G Ninja -S . -B build -DCMAKE_CXX_COMPILER= -DLIBCXX_BUILD= + $ ninja -C build + $ build/main + +.. warning:: ```` should point point to the real binary and + not to a symlink. + +.. warning:: When using these examples in your own projects make sure the + compilation flags are the same for the ``std`` module and your + project. Some flags will affect the generated code, when these + are different the module cannot be used. For example using + ``-pthread`` in your project and not in the module will give + errors like + + ``error: POSIX thread support was disabled in PCH file but is currently enabled`` + + ``error: module file _deps/std-build/CMakeFiles/std.dir/std.pcm cannot be loaded due to a configuration mismatch with the current compilation [-Wmodule-file-config-mismatch]`` + +If you have questions about modules free free to ask them in the ``#libcxx`` +channel on `LLVM's Discord server `__. + +If you think you've found a bug please it using the `LLVM bug tracker +`_. Please make sure the issue +you found is not one of the known bugs or limitations on this page. diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -35,6 +35,9 @@ What's New in Libc++ 17.0.0? ============================ +There is an experimental implementation of the C++23 ``std`` module. See +:ref:`ModulesInLibcxx` for more information. + Implemented Papers ------------------ - P2520R0 - ``move_iterator`` should be a random access iterator @@ -161,3 +164,9 @@ - ``LIBCXX_ENABLE_FSTREAM`` is not supported anymore, please use ``LIBCXX_ENABLE_FILESYSTEM=OFF`` if your platform does not have support for a filesystem. + +- The lit test parameter ``enable_modules`` changed from a Boolean to an enum. The changes are + + - ``False`` became ``none``. This option does not test with modules enabled. + - ``True`` became ``clang``. This option tests using Clang modules. + - ``std`` is a new optional and tests with the experimental C++23 ``std`` module. diff --git a/libcxx/docs/index.rst b/libcxx/docs/index.rst --- a/libcxx/docs/index.rst +++ b/libcxx/docs/index.rst @@ -39,6 +39,7 @@ BuildingLibcxx TestingLibcxx Contributing + Modules ReleaseProcedure Status/Cxx14 Status/Cxx17 diff --git a/libcxx/modules/.clang-format b/libcxx/modules/.clang-format new file mode 100644 --- /dev/null +++ b/libcxx/modules/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: InheritParentConfig + +NamespaceIndentation: All diff --git a/libcxx/modules/CMakeLists.txt b/libcxx/modules/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libcxx/modules/CMakeLists.txt @@ -0,0 +1,146 @@ +if (CMAKE_VERSION VERSION_LESS 3.26) + message(WARNING "The libc++ modules won't be available because the CMake version is too old. Update to CMake 3.26 or later.") + return() +endif() + +# The headers of Table 24: C++ library headers [tab:headers.cpp] +# and the headers of Table 25: C++ headers for C library facilities [tab:headers.cpp.c] +set(LIBCXX_SOURCES_MODULE_STD + std.cppm + std/algorithm.cppm + std/any.cppm + std/array.cppm + std/atomic.cppm + std/barrier.cppm + std/bit.cppm + std/bitset.cppm + std/cassert.cppm + std/cctype.cppm + std/cerrno.cppm + std/cfenv.cppm + std/cfloat.cppm + std/charconv.cppm + std/chrono.cppm + std/cinttypes.cppm + std/climits.cppm + std/clocale.cppm + std/cmath.cppm + std/codecvt.cppm + std/compare.cppm + std/complex.cppm + std/concepts.cppm + std/condition_variable.cppm + std/coroutine.cppm + std/csetjmp.cppm + std/csignal.cppm + std/cstdarg.cppm + std/cstddef.cppm + std/cstdint.cppm + std/cstdio.cppm + std/cstdlib.cppm + std/cstring.cppm + std/ctime.cppm + std/cuchar.cppm + std/cwchar.cppm + std/cwctype.cppm + std/deque.cppm + std/exception.cppm + std/execution.cppm + std/expected.cppm + std/filesystem.cppm + std/flat_map.cppm + std/flat_set.cppm + std/format.cppm + std/forward_list.cppm + std/fstream.cppm + std/functional.cppm + std/future.cppm + std/generator.cppm + std/initializer_list.cppm + std/iomanip.cppm + std/ios.cppm + std/iosfwd.cppm + std/iostream.cppm + std/iostream.cppm + std/istream.cppm + std/iterator.cppm + std/latch.cppm + std/limits.cppm + std/list.cppm + std/locale.cppm + std/map.cppm + std/mdspan.cppm + std/memory.cppm + std/memory_resource.cppm + std/mutex.cppm + std/new.cppm + std/numbers.cppm + std/numeric.cppm + std/optional.cppm + std/ostream.cppm + std/print.cppm + std/queue.cppm + std/random.cppm + std/ranges.cppm + std/ratio.cppm + std/regex.cppm + std/scoped_allocator.cppm + std/semaphore.cppm + std/set.cppm + std/shared_mutex.cppm + std/source_location.cppm + std/span.cppm + std/spanstream.cppm + std/sstream.cppm + std/stack.cppm + std/stacktrace.cppm + std/stdexcept.cppm + std/stdexcept.cppm + std/stdfloat.cppm + std/stop_token.cppm + std/streambuf.cppm + std/string.cppm + std/string_view.cppm + std/strstream.cppm + std/syncstream.cppm + std/system_error.cppm + std/thread.cppm + std/tuple.cppm + std/type_traits.cppm + std/typeindex.cppm + std/typeinfo.cppm + std/unordered_map.cppm + std/unordered_set.cppm + std/utility.cppm + std/valarray.cppm + std/variant.cppm + std/vector.cppm + std/version.cppm +) + +# TODO MODULES the CMakeLists.txt in the install directory is only temporary +# When that is removed the configured file can use the substitution +# LIBCXX_GENERATED_INCLUDE_TARGET_DIR avoiding this set. +# Also clean up the parts needed to generate the install version. +set(LIBCXX_CONFIGURED_INCLUDE_DIR ${LIBCXX_GENERATED_INCLUDE_TARGET_DIR}) +configure_file( + "CMakeLists.txt.in" + "${LIBCXX_GENERATED_MODULE_DIR}/CMakeLists.txt" + @ONLY +) + +set(_all_modules "${LIBCXX_GENERATED_MODULE_DIR}/CMakeLists.txt") +foreach(file ${LIBCXX_SOURCES_MODULE_STD}) + set(src "${CMAKE_CURRENT_SOURCE_DIR}/${file}") + set(dst "${LIBCXX_GENERATED_MODULE_DIR}/${file}") + add_custom_command(OUTPUT ${dst} + DEPENDS ${src} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dst} + COMMENT "Copying CXX module ${file}") + list(APPEND _all_modules "${dst}") +endforeach() + +add_custom_target(generate-cxx-modules + ALL DEPENDS + ${_all_modules} +) diff --git a/libcxx/modules/CMakeLists.txt.in b/libcxx/modules/CMakeLists.txt.in new file mode 100644 --- /dev/null +++ b/libcxx/modules/CMakeLists.txt.in @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.26) + +project(libc++-modules LANGUAGES CXX) + +# Enable CMake's module support +if(CMAKE_VERSION VERSION_LESS "3.27.0") + set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a") +else() + set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7") +endif() +set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1) + +# Default to C++ extensions being off. Libc++'s modules support have trouble +# with extensions right now. +set(CMAKE_CXX_EXTENSIONS OFF) + +# Propagates the CMake options to the modules. +# +# This uses the std module hard-coded since the std.compat module does not +# depend on these flags. +macro(compile_define_if_not condition def) + if (NOT ${condition}) + target_compile_definitions(std PRIVATE ${def}) + endif() +endmacro() +macro(compile_define_if condition def) + if (${condition}) + target_compile_definitions(std PRIVATE ${def}) + endif() +endmacro() + +if(NOT @LIBCXX_ENABLE_THREADS@ OR NOT @LIBCXXABI_ENABLE_THREADS@ OR NOT @LIBCXX_ENABLE_MONOTONIC_CLOCK@) + message(FATAL_ERROR "Modules without thread support is not yet implemented.") +endif() +if(NOT @LIBCXX_ENABLE_FILESYSTEM@) + message(FATAL_ERROR "Modules without filesystem support is not yet implemented.") +endif() +if(NOT @LIBCXX_ENABLE_RANDOM_DEVICE@) + message(FATAL_ERROR "Modules without randome device support is not yet implemented.") +endif() +if(NOT @LIBCXX_ENABLE_UNICODE@) + message(FATAL_ERROR "Modules without Unicode support is not yet implemented.") +endif() +if(NOT @LIBCXX_ENABLE_EXCEPTIONS@ OR NOT @LIBCXXABI_ENABLE_EXCEPTIONS@) + message(FATAL_ERROR "Modules without exception support is not yet implemented.") +endif() + +add_library(std) +target_sources(std + PUBLIC FILE_SET cxx_modules TYPE CXX_MODULES FILES + @LIBCXX_SOURCES_MODULE_STD@ +) + +target_compile_definitions(std PRIVATE _LIBCPP_ENABLE_EXPERIMENTAL) +target_include_directories(std SYSTEM PRIVATE @LIBCXX_CONFIGURED_INCLUDE_DIR@) + +target_compile_options(std + PUBLIC + -nostdinc++ + -Wno-reserved-module-identifier + -Wno-reserved-user-defined-literal + @LIBCXX_COMPILE_FLAGS@ +) +set_target_properties(std + PROPERTIES + OUTPUT_NAME "c++std" +) diff --git a/libcxx/modules/std/complex.cppm b/libcxx/modules/std/complex.cppm --- a/libcxx/modules/std/complex.cppm +++ b/libcxx/modules/std/complex.cppm @@ -23,9 +23,6 @@ using std::operator/; using std::operator==; -#if 1 // P1614 - using std::operator!=; -#endif using std::operator>>; using std::operator<<; diff --git a/libcxx/modules/std/format.cppm b/libcxx/modules/std/format.cppm --- a/libcxx/modules/std/format.cppm +++ b/libcxx/modules/std/format.cppm @@ -11,28 +11,27 @@ #include export module std:format; -#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) export namespace std { // [format.context], class template basic_format_context using std::basic_format_context; using std::format_context; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wformat_context; -# endif +#endif // [format.args], class template basic_format_args using std::basic_format_args; using std::format_args; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wformat_args; -# endif +#endif // [format.fmt.string], class template basic_format_string using std::basic_format_string; using std::format_string; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wformat_string; -# endif +#endif // [format.functions], formatting functions using std::format; @@ -53,9 +52,9 @@ // [format.parse.ctx], class template basic_format_parse_context using std::basic_format_parse_context; using std::format_parse_context; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wformat_parse_context; -# endif +#endif // [format.range], formatting of ranges // [format.range.fmtkind], variable template format_kind @@ -71,11 +70,10 @@ // [format.arg.store], class template format-arg-store using std::make_format_args; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::make_wformat_args; -# endif +#endif // [format.error], class format_error using std::format_error; } // namespace std -#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT) diff --git a/libcxx/modules/std/functional.cppm b/libcxx/modules/std/functional.cppm --- a/libcxx/modules/std/functional.cppm +++ b/libcxx/modules/std/functional.cppm @@ -94,9 +94,6 @@ using std::swap; using std::operator==; -#if 1 // P1614 - using std::operator!=; -#endif // [func.wrap.move], move only wrapper // using std::move_only_function; diff --git a/libcxx/modules/std/iosfwd.cppm b/libcxx/modules/std/iosfwd.cppm --- a/libcxx/modules/std/iosfwd.cppm +++ b/libcxx/modules/std/iosfwd.cppm @@ -12,5 +12,16 @@ export module std:iosfwd; export namespace std { - // All symbols are exported by other modules. + using std::streampos; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + using std::wstreampos; +#endif + using std::u16streampos; + using std::u32streampos; + using std::u8streampos; + + using std::istreambuf_iterator; + using std::ostreambuf_iterator; + + using std::fpos; } // namespace std diff --git a/libcxx/modules/std/memory.cppm b/libcxx/modules/std/memory.cppm --- a/libcxx/modules/std/memory.cppm +++ b/libcxx/modules/std/memory.cppm @@ -131,7 +131,6 @@ using std::swap; - using std::operator!=; using std::operator<; using std::operator>; using std::operator<=; diff --git a/libcxx/modules/std/memory_resource.cppm b/libcxx/modules/std/memory_resource.cppm --- a/libcxx/modules/std/memory_resource.cppm +++ b/libcxx/modules/std/memory_resource.cppm @@ -16,9 +16,6 @@ using std::pmr::memory_resource; using std::pmr::operator==; -#if 1 // P1614 - using std::operator!=; -#endif // [mem.poly.allocator.class], class template polymorphic_allocator using std::pmr::polymorphic_allocator; diff --git a/libcxx/modules/std/queue.cppm b/libcxx/modules/std/queue.cppm --- a/libcxx/modules/std/queue.cppm +++ b/libcxx/modules/std/queue.cppm @@ -16,15 +16,12 @@ using std::queue; using std::operator==; -#if 0 // P1614 - using std::operator<=>; -#else using std::operator!=; using std::operator<; using std::operator>; using std::operator<=; using std::operator>=; -#endif + using std::operator<=>; using std::swap; using std::uses_allocator; diff --git a/libcxx/modules/std/scoped_allocator.cppm b/libcxx/modules/std/scoped_allocator.cppm --- a/libcxx/modules/std/scoped_allocator.cppm +++ b/libcxx/modules/std/scoped_allocator.cppm @@ -17,8 +17,5 @@ // [scoped.adaptor.operators], scoped allocator operators using std::operator==; -#if 1 // P1614 - using std::operator!=; -#endif } // namespace std diff --git a/libcxx/modules/std/set.cppm b/libcxx/modules/std/set.cppm --- a/libcxx/modules/std/set.cppm +++ b/libcxx/modules/std/set.cppm @@ -16,15 +16,7 @@ using std::set; using std::operator==; -#if 0 // P1614 using std::operator<=>; -#else - using std::operator!=; - using std::operator<; - using std::operator>; - using std::operator<=; - using std::operator>=; -#endif using std::swap; diff --git a/libcxx/modules/std/stack.cppm b/libcxx/modules/std/stack.cppm --- a/libcxx/modules/std/stack.cppm +++ b/libcxx/modules/std/stack.cppm @@ -15,15 +15,14 @@ // [stack], class template stack using std::stack; + using std::operator==; using std::operator==; using std::operator!=; using std::operator<; using std::operator>; using std::operator<=; using std::operator>=; -#if 0 // P1614 using std::operator<=>; -#endif using std::swap; using std::uses_allocator; diff --git a/libcxx/modules/std/stop_token.cppm b/libcxx/modules/std/stop_token.cppm --- a/libcxx/modules/std/stop_token.cppm +++ b/libcxx/modules/std/stop_token.cppm @@ -8,14 +8,10 @@ //===----------------------------------------------------------------------===// module; -#if __has_include() // D145183 contains a patch for this header -# error "include this header unconditionally and uncomment the exported symbols" -# include -#endif +#include export module std:stop_token; export namespace std { -#if 0 // [stoptoken], class stop_­token using std::stop_token; @@ -28,5 +24,4 @@ // [stopcallback], class template stop_­callback using std::stop_callback; -#endif } // namespace std diff --git a/libcxx/modules/std/string.cppm b/libcxx/modules/std/string.cppm --- a/libcxx/modules/std/string.cppm +++ b/libcxx/modules/std/string.cppm @@ -71,10 +71,15 @@ // [basic.string.hash], hash support using std::hash; + // TODO MODULES is this a bug? +#if 1 + using std::operator""s; +#else inline namespace literals { inline namespace string_literals { // [basic.string.literals], suffix for basic_string literals using std::literals::string_literals::operator""s; } // namespace string_literals } // namespace literals +#endif } // namespace std diff --git a/libcxx/modules/std/unordered_map.cppm b/libcxx/modules/std/unordered_map.cppm --- a/libcxx/modules/std/unordered_map.cppm +++ b/libcxx/modules/std/unordered_map.cppm @@ -19,9 +19,6 @@ using std::unordered_multimap; using std::operator==; -#if 1 // P1614 - using std::operator!=; -#endif using std::swap; diff --git a/libcxx/modules/std/unordered_set.cppm b/libcxx/modules/std/unordered_set.cppm --- a/libcxx/modules/std/unordered_set.cppm +++ b/libcxx/modules/std/unordered_set.cppm @@ -19,9 +19,6 @@ using std::unordered_multiset; using std::operator==; -#if 1 // P1614 - using std::operator!=; -#endif using std::swap; diff --git a/libcxx/modules/std/vector.cppm b/libcxx/modules/std/vector.cppm --- a/libcxx/modules/std/vector.cppm +++ b/libcxx/modules/std/vector.cppm @@ -17,16 +17,7 @@ using std::vector; using std::operator==; -#if 0 // P1614 using std::operator<=>; -#else - /* These should be removed after https://reviews.llvm.org/D132268 lands. */ - using std::operator!=; - using std::operator<; - using std::operator>; - using std::operator<=; - using std::operator>=; -#endif using std::swap; diff --git a/libcxx/test/CMakeLists.txt b/libcxx/test/CMakeLists.txt --- a/libcxx/test/CMakeLists.txt +++ b/libcxx/test/CMakeLists.txt @@ -68,6 +68,32 @@ "Running libcxx tests" ${CMAKE_CURRENT_BINARY_DIR} DEPENDS cxx-test-depends) + + if(LIBCXX_ENABLE_STD_MODULES) + # Generates the modules used in the test. + # Note the test will regenerate this with the proper setting + # - the right DCMAKE_CXX_STANDARD + # - the right test compilation flags + # Since modules depend on these flags there currently is no way to + # avoid generating these for the tests. The advantage of the + # pre generation is that less build information needs to be shared + # in the bridge. + add_custom_command( + OUTPUT "${CMAKE_BINARY_DIR}/test/__config_module__/CMakeCache.txt" + COMMAND + ${CMAKE_COMMAND} + "-G${CMAKE_GENERATOR}" + "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}" + "-B${CMAKE_BINARY_DIR}/test/__config_module__" + "-H${LIBCXX_GENERATED_MODULE_DIR}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=23" + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" + ) + add_custom_target(generate-test-module-std + DEPENDS "${CMAKE_BINARY_DIR}/test/__config_module__/CMakeCache.txt" + COMMENT "Builds generic module std.") + endif() endif() if (LIBCXX_GENERATE_COVERAGE) diff --git a/libcxx/test/configs/cmake-bridge.cfg.in b/libcxx/test/configs/cmake-bridge.cfg.in --- a/libcxx/test/configs/cmake-bridge.cfg.in +++ b/libcxx/test/configs/cmake-bridge.cfg.in @@ -29,5 +29,13 @@ config.substitutions.append(('%{include}', '@LIBCXX_GENERATED_INCLUDE_DIR@')) config.substitutions.append(('%{target-include}', '@LIBCXX_GENERATED_INCLUDE_TARGET_DIR@')) config.substitutions.append(('%{lib}', '@LIBCXX_LIBRARY_DIR@')) +config.substitutions.append(('%{module}', '@LIBCXX_GENERATED_MODULE_DIR@')) config.substitutions.append(('%{executor}', '@LIBCXX_EXECUTOR@')) config.substitutions.append(('%{test-tools}', '@LIBCXX_TEST_TOOLS_PATH@')) + +# The test needs to manually rebuild the module. The compiler flags used in the +# test need to be the same as the compiler flags used to generate the module. +# In the future, when CMake can generated modules this may no longer be +# necessary. +# TODO MODULES whether it's possible to remove this substitution. +config.substitutions.append(('%{cmake}', '@CMAKE_COMMAND@')) diff --git a/libcxx/test/libcxx/module_std.gen.py b/libcxx/test/libcxx/module_std.gen.py new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/module_std.gen.py @@ -0,0 +1,227 @@ +# ===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===----------------------------------------------------------------------===## + +# Test that all named declarations with external linkage match the +# exported declarations in their associated module partition. +# Then it tests the sum of the exported declarations in the module +# partitions matches the export of the std module. + +# Note the test of the std module requires all partitions to be tested +# first. Since lit tests have no dependencies, this means the test needs +# to be one monolitic test. Since the test doesn't take very long it's +# not a huge issue. + +# RUN: %{python} %s %{libcxx}/utils + +import sys + +sys.path.append(sys.argv[1]) +from libcxx.test.header_information import toplevel_headers + +BLOCKLIT = ( + "" # block Lit from interpreting a RUN/XFAIL/etc inside the generation script +) + +### Remove the headers that have no module associated with them + +# Note all C-headers using .h are filtered in the loop. + +# These headers are not available in C++23, but in older language Standards. +toplevel_headers.remove("ccomplex") +toplevel_headers.remove("ciso646") +toplevel_headers.remove("cstdbool") +toplevel_headers.remove("ctgmath") + +# Ignore several declarations found in the includes. +# +# Part of these items are bugs other are not yet implemented features. +SkipDeclarations = dict() + +# See comment in the header. +SkipDeclarations["cuchar"] = ["std::mbstate_t", "std::size_t"] + +# Not in the synopsis. +SkipDeclarations["cwchar"] = ["std::FILE"] + +# The operators are added for private types like __iom_t10. +SkipDeclarations["iomanip"] = ["std::operator<<", "std::operator>>"] + +SkipDeclarations["iosfwd"] = ["std::ios_base", "std::vector"] + +# This header also provides declarations in the namespace that might be +# an error. +SkipDeclarations["filesystem"] = [ + "std::filesystem::operator==", + "std::filesystem::operator!=", +] + +# This is a specialization for a private type +SkipDeclarations["iterator"] = ["std::pointer_traits"] + +# TODO MODULES +# This definition is declared in string and defined in istream +# This declaration should be part of string +SkipDeclarations["istream"] = ["std::getline"] + +# P1614 (at many places) and LWG3519 too. +SkipDeclarations["random"] = [ + "std::operator!=", + # LWG3519 makes these hidden friends. + # Note the older versions had the requirement of these operations but not in + # the synopsis. + "std::operator<<", + "std::operator>>", + "std::operator==", +] + +# Declared in the forward header since std::string uses std::allocator +SkipDeclarations["string"] = ["std::allocator"] +# TODO MODULES remove zombie names +# https://libcxx.llvm.org/Status/Cxx20.html#note-p0619 +SkipDeclarations["memory"] = [ + "std::return_temporary_buffer", + "std::get_temporary_buffer", +] + +# TODO MODULES this should be part of ios instead +SkipDeclarations["streambuf"] = ["std::basic_ios"] + +# include/__type_traits/is_swappable.h +SkipDeclarations["type_traits"] = [ + "std::swap", + # TODO MODULES gotten through __functional/unwrap_ref.h + "std::reference_wrapper", +] + +# Add declarations in headers. +# +# Some headers have their defines in a different header, which may have +# additional declarations. +ExtraDeclarations = dict() +# This declaration is in the ostream header. +ExtraDeclarations["system_error"] = ["std::operator<<"] + +# Adds an extra header file to scan +# +# +ExtraHeader = dict() +# locale has a file and not a subdirectory +ExtraHeader["locale"] = "v1/__locale$" +ExtraHeader["thread"] = "v1/__threading_support$" +ExtraHeader["ranges"] = "v1/__fwd/subrange.h$" + +# The extra header is needed since two headers are required to provide the +# same definition. +ExtraHeader["functional"] = "v1/__compare/compare_three_way.h$" + +# Create empty file with all parts. +print( + f"""\ +//--- module_std.sh.cpp +// UNSUPPORTED{BLOCKLIT}: c++03, c++11, c++14, c++17, c++20 + +// REQUIRES{BLOCKLIT}: has-clang-tidy +// REQUIRES{BLOCKLIT}: use_module_std + +// The GCC compiler flags are not always compatible with clang-tidy. +// UNSUPPORTED{BLOCKLIT}: gcc + +// RUN{BLOCKLIT}: echo -n > %t.all_partitions +""" +) + +# Validate all module parts. +for header in toplevel_headers: + if header.endswith(".h"): # Skip C compatibility headers + continue + + # Dump the information as found in the module's cppm file. + print( + f"// RUN{BLOCKLIT}: %{{clang-tidy}} %{{module}}/std/{header}.cppm " + " --checks='-*,libcpp-header-exportable-declarations' " + " -config='{CheckOptions: [ " + " {" + " key: libcpp-header-exportable-declarations.Filename, " + f" value: {header}.cppm" + " }, {" + " key: libcpp-header-exportable-declarations.FileType, " + " value: ModulePartition" + " }, " + " ]}' " + " --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin " + " -- %{flags} %{compile_flags} " + f"| sort > %t.{header}.module" + ) + print(f"// RUN{BLOCKLIT}: cat %t.{header}.module >> %t.all_partitions") + + # Dump the information as found in the module by using the header file(s). + skip_declarations = " ".join(SkipDeclarations.get(header, [])) + if skip_declarations: + skip_declarations = ( + "{" + " key: libcpp-header-exportable-declarations.SkipDeclarations, " + f' value: "{skip_declarations}" ' + "}, " + ) + + extra_declarations = " ".join(ExtraDeclarations.get(header, [])) + if extra_declarations: + extra_declarations = ( + " {" + " key: libcpp-header-exportable-declarations.ExtraDeclarations, " + f' value: "{extra_declarations}" ' + "}, " + ) + + extra_header = ExtraHeader.get(header, "") + if extra_header: + extra_header = ( + "{" + " key: libcpp-header-exportable-declarations.ExtraHeader, " + f' value: "{extra_header}" ' + "}, " + ) + + # Clang-tidy needs a file input + print(f'// RUN{BLOCKLIT}: echo "#include <{header}>" > %t.{header}.cpp') + print( + f"// RUN{BLOCKLIT}: %{{clang-tidy}} %t.{header}.cpp " + " --checks='-*,libcpp-header-exportable-declarations' " + " -config='{CheckOptions: [ " + f" {{key: libcpp-header-exportable-declarations.Filename, value: {header}}}, " + " {key: libcpp-header-exportable-declarations.FileType, value: Header}, " + f" {skip_declarations} {extra_declarations} {extra_header}, " + " ]}' " + " --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin " + " -- %{flags} %{compile_flags} " + f" | sort > %t.{header}.include" + ) + + # Compare the cppm and header file(s) return the same results. + print(f"// RUN{BLOCKLIT}: diff -u %t.{header}.module %t.{header}.include") + + +# Merge the data of the parts +print(f"// RUN{BLOCKLIT}: sort -u -o %t.all_partitions %t.all_partitions") + +# Dump the information as found in std.cppm. +print( + f"// RUN{BLOCKLIT}: %{{clang-tidy}} %{{module}}/std.cppm " + " --checks='-*,libcpp-header-exportable-declarations' " + " -config='{CheckOptions: [ " + " {key: libcpp-header-exportable-declarations.Header, value: std.cppm}, " + " {key: libcpp-header-exportable-declarations.FileType, value: Module}, " + " ]}' " + f" --load=%{{test-tools}}/clang_tidy_checks/libcxx-tidy.plugin " + " -- %{flags} %{compile_flags} " + " | sort > %t.module" +) + + +# Compare the sum of the parts with the main module. +print(f"// RUN{BLOCKLIT}: diff -u %t.all_partitions %t.module") diff --git a/libcxx/test/libcxx/modules_include.gen.py b/libcxx/test/libcxx/modules_include.gen.py --- a/libcxx/test/libcxx/modules_include.gen.py +++ b/libcxx/test/libcxx/modules_include.gen.py @@ -22,6 +22,8 @@ //--- {header}.compile.pass.cpp // RUN{BLOCKLIT}: %{{cxx}} %s %{{flags}} %{{compile_flags}} -fmodules -fcxx-modules -fmodules-cache-path=%t -fsyntax-only +// UNSUPPORTED{BLOCKLIT}: use_module_std + // GCC doesn't support -fcxx-modules // UNSUPPORTED{BLOCKLIT}: gcc diff --git a/libcxx/test/tools/clang_tidy_checks/CMakeLists.txt b/libcxx/test/tools/clang_tidy_checks/CMakeLists.txt --- a/libcxx/test/tools/clang_tidy_checks/CMakeLists.txt +++ b/libcxx/test/tools/clang_tidy_checks/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCES abi_tag_on_virtual.cpp + header_exportable_declarations.cpp hide_from_abi.cpp proper_version_checks.cpp qualify_declval.cpp diff --git a/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.hpp b/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.hpp new file mode 100644 --- /dev/null +++ b/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.hpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang-tidy/ClangTidyCheck.h" + +#include "llvm/ADT/StringRef.h" + +#include +#include +#include + +namespace libcpp { +class header_exportable_declarations : public clang::tidy::ClangTidyCheck { +public: + explicit header_exportable_declarations(llvm::StringRef, clang::tidy::ClangTidyContext*); + void registerMatchers(clang::ast_matchers::MatchFinder*) override; + void check(const clang::ast_matchers::MatchFinder::MatchResult&) override; + + enum class FileType { Header, ModulePartition, Module, Unknown }; + +private: + llvm::StringRef filename_; + FileType file_type_; + llvm::StringRef extra_header_; + std::set decls_; +}; +} // namespace libcpp diff --git a/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.cpp b/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/tools/clang_tidy_checks/header_exportable_declarations.cpp @@ -0,0 +1,240 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang-tidy/ClangTidyCheck.h" +#include "clang-tidy/ClangTidyModuleRegistry.h" + +#include "clang/Basic/Module.h" + +#include "llvm/ADT/ArrayRef.h" + +#include "header_exportable_declarations.hpp" + +#include +#include +#include +#include + +template <> +struct clang::tidy::OptionEnumMapping { + static llvm::ArrayRef> getEnumMapping() { + static constexpr std::pair Mapping[] = { + {libcpp::header_exportable_declarations::FileType::Header, "Header"}, + {libcpp::header_exportable_declarations::FileType::ModulePartition, "ModulePartition"}, + {libcpp::header_exportable_declarations::FileType::Module, "Module"}}; + return ArrayRef(Mapping); + } +}; + +namespace libcpp { +header_exportable_declarations::header_exportable_declarations( + llvm::StringRef name, clang::tidy::ClangTidyContext* context) + : clang::tidy::ClangTidyCheck(name, context), + filename_(Options.get("Filename", "")), + file_type_(Options.get("FileType", header_exportable_declarations::FileType::Unknown)), + extra_header_(Options.get("ExtraHeader", "")) { + if (filename_.empty()) + llvm::errs() << "No filename is provided.\n"; + + switch (file_type_) { + case header_exportable_declarations::FileType::Header: + /* DO NOTHING */ + break; + case header_exportable_declarations::FileType::Module: + case header_exportable_declarations::FileType::ModulePartition: + if (!extra_header_.empty()) + llvm::errs() << "Extra headers are not allowed for modules.\n"; + if (Options.get("SkipDeclarations")) + llvm::errs() << "Modules may not skip declarations.\n"; + if (Options.get("ExtraDeclarations")) + llvm::errs() << "Modules may not have extra declarations.\n"; + break; + case header_exportable_declarations::FileType::Unknown: + llvm::errs() << "No file type is provided.\n"; + break; + } + + std::optional list = Options.get("SkipDeclarations"); + // TODO(LLVM-17) Remove clang 15 work-around. +#if defined(__clang_major__) && __clang_major__ < 16 + if (list) { + std::string_view s = *list; + auto b = s.begin(); + auto e = std::find(b, s.end(), ' '); + while (b != e) { + decls_.emplace(b, e); + if (e == s.end()) + break; + b = e + 1; + e = std::find(b, s.end(), ' '); + } + } +#else // defined(__clang_major__) && __clang_major__ < 16 + if (list) + for (auto decl : std::views::split(*list, ' ')) { + std::string s; + std::ranges::copy(decl, std::back_inserter(s)); // use range based constructor + decls_.emplace(std::move(s)); + } +#endif // defined(__clang_major__) && __clang_major__ < 16 + + list = Options.get("ExtraDeclarations"); + // TODO(LLVM-17) Remove clang 15 work-around. +#if defined(__clang_major__) && __clang_major__ < 16 + if (list) { + std::string_view s = *list; + auto b = s.begin(); + auto e = std::find(b, s.end(), ' '); + while (b != e) { + std::cout << "using " << std::string_view{b, e} << ";\n"; + if (e == s.end()) + break; + b = e + 1; + e = std::find(b, s.end(), ' '); + } + } +#else // defined(__clang_major__) && __clang_major__ < 16 + if (list) + for (auto decl : std::views::split(*list, ' ')) + std::cout << "using " << std::string_view{decl.data(), decl.size()} << ";\n"; +#endif // defined(__clang_major__) && __clang_major__ < 16 +} + +void header_exportable_declarations::registerMatchers(clang::ast_matchers::MatchFinder* finder) { + // there are no public names in the Standard starting with an underscore, so + // no need to check the strict rules. + using namespace clang::ast_matchers; + + switch (file_type_) { + case FileType::Header: + + finder->addMatcher( + namedDecl( + // Looks at the common locations where headers store their data + // * header + // * __header/*.h + // * __fwd/header.h + anyOf(isExpansionInFileMatching(("v1/__" + filename_ + "/").str()), + isExpansionInFileMatching(extra_header_), + isExpansionInFileMatching(("v1/__fwd/" + filename_ + "\\.h$").str()), + isExpansionInFileMatching(("v1/" + filename_ + "$").str())), + unless(hasAncestor(friendDecl()))) + .bind("header_exportable_declarations"), + this); + break; + case FileType::ModulePartition: + finder->addMatcher(namedDecl(isExpansionInFileMatching(filename_)).bind("header_exportable_declarations"), this); + break; + case FileType::Module: + finder->addMatcher(namedDecl().bind("header_exportable_declarations"), this); + break; + case header_exportable_declarations::FileType::Unknown: + llvm::errs() << "This should be unreachable.\n"; + break; + } +} + +/// Returns the qualified name of a declaration. +/// +/// There is a small issue with qualified names. Typically the name returned is +/// in the namespace \c std instead of the namespace \c std::__1. Except when a +/// name is declared both in the namespace \c std and in the namespace +/// \c std::__1. In that case the returned value will adjust the name to use +/// the namespace \c std. +/// +/// The reason this happens is due to some parts of libc++ using +/// \code namespace std \endcode instead of +/// \code _LIBCPP_BEGIN_NAMESPACE_STD \endcode +/// Some examples +/// * cstddef has bitwise operators for the type \c byte +/// * exception has equality operators for the type \c exception_ptr +/// * initializer_list has the functions \c begin and \c end +static std::string get_qualified_name(const clang::NamedDecl& decl) { + std::string result = decl.getQualifiedNameAsString(); + + if (result.starts_with("std::__1::")) + result.erase(5, 5); + + return result; +} + +static bool is_viable_declaration(const clang::NamedDecl* decl) { + // Declarations nested in records are automatically exported with the record itself. + if (!decl->getDeclContext()->isNamespace()) + return false; + + // Declarations that are a subobject of a friend Declaration are automatically exported with the record itself. + if (decl->getFriendObjectKind() != clang::Decl::FOK_None) + return false; + + // *** Function declarations *** + + if (clang::CXXMethodDecl::classof(decl)) + return false; + + if (clang::CXXDeductionGuideDecl::classof(decl)) + return false; + + if (clang::FunctionDecl::classof(decl)) + return true; + + if (clang::CXXConstructorDecl::classof(decl)) + return false; + + // implicit constructors disallowed + if (const auto* r = llvm::dyn_cast_or_null(decl)) + return !r->isLambda() && !r->isImplicit(); + + // *** Unconditionally accepted declarations *** + return llvm::isa(decl); +} + +/// Returns the name is a reserved name. +/// +/// Detected reserved names are names starting with __ or _[A-Z]. +/// These names can be in the namespace std or any namespace inside std. For +/// example std::ranges contains reserved names to implement the Niebloids. +/// +/// This test misses 2 candidates which are not used in libc++ +/// * any identifier with two underscores not at the start +/// * a name with a leading underscore in the global namespace +bool is_reserved_name(const std::string& name) { + std::size_t pos = name.find("::_"); + if (pos == std::string::npos) + return false; + + if (pos + 3 > name.size()) + return false; + + return name[pos + 3] == '_' || std::isupper(name[pos + 3]); +} + +void header_exportable_declarations::check(const clang::ast_matchers::MatchFinder::MatchResult& result) { + if (const auto* decl = result.Nodes.getNodeAs("header_exportable_declarations"); decl != nullptr) { + if (!is_viable_declaration(decl)) + return; + + std::string name = get_qualified_name(*decl); + if (is_reserved_name(name)) + return; + + // For modules (std, std.compat) only take the declarations exported from the partitions. + // Making sure no declatations of headers are compared. + if (file_type_ == FileType::Module) + if (clang::Module* M = decl->getOwningModule(); M && M->Kind != clang::Module::ModulePartitionInterface) + return; + + if (decls_.contains(name)) + return; + + std::cout << "using " << std::string{name} << ";\n"; + decls_.insert(name); + } +} + +} // namespace libcpp diff --git a/libcxx/test/tools/clang_tidy_checks/libcpp_module.cpp b/libcxx/test/tools/clang_tidy_checks/libcpp_module.cpp --- a/libcxx/test/tools/clang_tidy_checks/libcpp_module.cpp +++ b/libcxx/test/tools/clang_tidy_checks/libcpp_module.cpp @@ -10,6 +10,7 @@ #include "clang-tidy/ClangTidyModuleRegistry.h" #include "abi_tag_on_virtual.hpp" +#include "header_exportable_declarations.hpp" #include "hide_from_abi.hpp" #include "proper_version_checks.hpp" #include "qualify_declval.hpp" @@ -21,6 +22,7 @@ public: void addCheckFactories(clang::tidy::ClangTidyCheckFactories& check_factories) override { check_factories.registerCheck("libcpp-avoid-abi-tag-on-virtual"); + check_factories.registerCheck("libcpp-header-exportable-declarations"); check_factories.registerCheck("libcpp-hide-from-abi"); check_factories.registerCheck("libcpp-cpp-version-check"); check_factories.registerCheck("libcpp-robust-against-adl"); diff --git a/libcxx/utils/ci/buildkite-pipeline.yml b/libcxx/utils/ci/buildkite-pipeline.yml --- a/libcxx/utils/ci/buildkite-pipeline.yml +++ b/libcxx/utils/ci/buildkite-pipeline.yml @@ -146,6 +146,27 @@ limit: 2 timeout_in_minutes: 120 + - label: "C++23 Module std" + command: "libcxx/utils/ci/run-buildbot generic-module-std-cxx23" + artifact_paths: + - "**/test-results.xml" + - "**/*.abilist" + env: + # Note modules require and absolute path for clang-scan-deps + # https://github.com/llvm/llvm-project/issues/61006 + CC: "/usr/lib/llvm-${LLVM_HEAD_VERSION}/bin/clang" + CXX: "/usr/lib/llvm-${LLVM_HEAD_VERSION}/bin/clang++" + CMAKE: "/opt/bin/cmake" + ENABLE_CLANG_TIDY: "On" + agents: + queue: "libcxx-builders" + os: "linux" + retry: + automatic: + - exit_status: -1 # Agent was lost + limit: 2 + timeout_in_minutes: 120 + - label: "C++11" command: "libcxx/utils/ci/run-buildbot generic-cxx11" artifact_paths: diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot --- a/libcxx/utils/ci/run-buildbot +++ b/libcxx/utils/ci/run-buildbot @@ -35,9 +35,12 @@ Environment variables CC The C compiler to use, this value is used by CMake. This variable is optional. + CXX The C++ compiler to use, this value is used by CMake. This variable is optional. +CMAKE The CMake binary to use. This variable is optional. + CLANG_FORMAT The clang-format binary to use when generating the format ignore list. @@ -89,8 +92,24 @@ # version will generally work with the Clang shipped in Xcode (e.g. if Clang # knows about -std=c++20, the CMake bundled in Xcode will probably know about # that flag too). -if xcrun --find ninja &>/dev/null; then NINJA="$(xcrun --find ninja)"; else NINJA="ninja"; fi -if xcrun --find cmake &>/dev/null; then CMAKE="$(xcrun --find cmake)"; else CMAKE="cmake"; fi +if xcrun --find ninja &>/dev/null; then + NINJA="$(xcrun --find ninja)" +elif which ninja &>/dev/null; then + # The current implementation of modules needs the absolute path to the ninja + # binary. + # TODO MODULES Is this still needed when CMake has libc++ module support? + NINJA="$(which ninja)" +else + NINJA="ninja" +fi + +if [ -z "${CMAKE}" ]; then + if xcrun --find cmake &>/dev/null; then + CMAKE="$(xcrun --find cmake)" + else + CMAKE="cmake" + fi +fi function clean() { rm -rf "${BUILD_DIR}" @@ -178,8 +197,8 @@ fi ${GIT_CLANG_FORMAT} \ --diff \ - --extensions ',h,hpp,c,cpp,inc,ipp' HEAD~1 \ - -- $(find libcxx/{benchmarks,include,src}/ -type f | grep -vf libcxx/utils/data/ignore_format.txt) \ + --extensions ',h,hpp,c,cpp,cppm,inc,ipp' HEAD~1 \ + -- $(find libcxx/{benchmarks,include,modules,src}/ -type f | grep -vf libcxx/utils/data/ignore_format.txt) \ | tee ${BUILD_DIR}/clang-format.patch # Check if the diff is empty, fail otherwise. ! grep -q '^--- a' ${BUILD_DIR}/clang-format.patch @@ -379,6 +398,9 @@ generate-cmake -DLIBCXXABI_USE_LLVM_UNWINDER=ON check-runtimes ;; +# +# Module builds +# generic-modules) clean generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-modules.cmake" @@ -391,6 +413,12 @@ check-runtimes check-abi-list ;; +generic-module-std-cxx23) + clean + generate-cmake -C "${MONOREPO_ROOT}/libcxx/cmake/caches/Generic-module-std-cxx23.cmake" + check-runtimes + check-abi-list +;; # # Parts removed # diff --git a/libcxx/utils/libcxx/test/dsl.py b/libcxx/utils/libcxx/test/dsl.py --- a/libcxx/utils/libcxx/test/dsl.py +++ b/libcxx/utils/libcxx/test/dsl.py @@ -12,6 +12,7 @@ import platform import re import shutil +import subprocess import tempfile import libcxx.test.format @@ -338,6 +339,12 @@ } +def _getSubstitution(substitution, config): + for (orig, replacement) in config.substitutions: + if orig == substitution: + return replacement + raise ValueError('Substitution {} is not in the config.'.format(substitution)) + def _appendToSubstitution(substitutions, key, value): return [(k, v + " " + value) if k == key else (k, v) for (k, v) in substitutions] @@ -430,6 +437,30 @@ def pretty(self, config, litParams): return "add {} to %{{flags}}".format(self._getFlag(config)) +class BuildStdModule(ConfigAction): + def applyTo(self, config): + build = os.path.join(config.test_exec_root, '__config_module__') + + std = _getSubstitution('%{cxx_std}', config) + if std == 'cxx26': + # This fails to work properly. It might be due to + # CMAKE_CXX_STANDARD 26 + # does not work in CMake 3.26, it requires the upcomming CMake 3.27. + # TODO MODULES test whether this is fixed with CMake 3.27. + std = '17' + elif std == 'cxx23': + std = '23' + else: + std = '17' # Not allowed for modules + + flags = _getSubstitution('%{flags}', config) + cmake = _getSubstitution('%{cmake}', config) + + subprocess.check_call([cmake, "-DCMAKE_CXX_STANDARD=" + std, f"-DCMAKE_CXX_FLAGS={flags}", build], env={}) + subprocess.check_call([cmake, "--build", build], env={}) + + def pretty(self, config, litParams): + return "building std module with flags {}".format(_getSubstitution('%{flags}', config)) class AddFlagIfSupported(ConfigAction): """ diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py --- a/libcxx/utils/libcxx/test/params.py +++ b/libcxx/utils/libcxx/test/params.py @@ -24,6 +24,7 @@ "-Wno-noexcept-type", "-Wno-aligned-allocation-unavailable", "-Wno-atomic-alignment", + "-Wno-reserved-module-identifier", # GCC warns about places where we might want to add sized allocation/deallocation # functions, but we know better what we're doing/testing in the test suite. "-Wno-sized-deallocation", @@ -73,6 +74,15 @@ return None +_allModules = ["none", "clang", "std"] + + +def getModuleFlag(cfg, enable_modules): + if enable_modules in _allModules: + return enable_modules + return None + + DEFAULT_PARAMETERS = [ Parameter( name="target_triple", @@ -104,18 +114,32 @@ ), Parameter( name="enable_modules", - choices=[True, False], - type=bool, - default=False, - help="Whether to build the test suite with Clang modules enabled.", - actions=lambda modules: [ + choices=_allModules, + type=str, + help="Whether to build the test suite with modules enabled. Select " + "`clang` for Clang modules and `std` for C++23 std module", + default=lambda cfg: next(s for s in _allModules if getModuleFlag(cfg, s)), + actions=lambda enable_modules: [ AddFeature("modules-build"), AddCompileFlag("-fmodules"), AddCompileFlag( "-fcxx-modules" ), # AppleClang disregards -fmodules entirely when compiling C++. This enables modules for C++. ] - if modules + if enable_modules == "clang" + else [ + AddFeature("use_module_std"), + AddCompileFlag("-DTEST_USE_MODULE"), + AddCompileFlag("-DTEST_USE_MODULE_STD"), + AddCompileFlag( + lambda cfg: "-fprebuilt-module-path=" + + os.path.join( + cfg.test_exec_root, "__config_module__/CMakeFiles/std.dir" + ) + ), + BuildStdModule(), + ] + if enable_modules == "std" else [], ), Parameter(