diff --git a/libcxx/docs/TestingLibcxx.rst b/libcxx/docs/TestingLibcxx.rst index e7645cb5885f1..50ee9d4ee400b 100644 --- a/libcxx/docs/TestingLibcxx.rst +++ b/libcxx/docs/TestingLibcxx.rst @@ -394,7 +394,7 @@ Custom Directives ~~~~~~~~~~~~~~~~~ Lit has many directives built in (e.g., ``DEFINE``, ``UNSUPPORTED``). In addition to those directives, libc++ adds two additional libc++-specific directives that makes -writing tests easier. See `libc++-specific Lit Directives`_ for more information about the ``FILE_DEPENDENCIES`` and ``ADDITIONAL_COMPILE_FLAGS`` libc++-specific directives. +writing tests easier. See `libc++-specific Lit Directives`_ for more information about the ``FILE_DEPENDENCIES``, ``ADDITIONAL_COMPILE_FLAGS``, and ``MODULE_DEPENDENCIES`` libc++-specific directives. .. _libc++-specific Lit Directives: .. list-table:: libc++-specific Lit Directives @@ -417,6 +417,13 @@ writing tests easier. See `libc++-specific Lit Directives`_ for more information - The additional compiler flags specified by a space-separated list to the ``ADDITIONAL_COMPILE_FLAGS`` libc++-specific Lit directive will be added to the end of the ``%{compile_flags}`` substitution for the test that contains it. This libc++-specific Lit directive makes it possible to add special compilation flags without having to resort to writing a ``.sh.cpp`` test (see `Lit Meaning of libc++ Test Filenames`_), more powerful but perhaps overkill. + * - ``MODULE_DEPENDENCIES`` + - ``// MODULE_DEPENDENCIES: std std.compat`` + - This directive will build the required C++23 standard library + modules and add the additional compiler flags in + %{compile_flags}. (Libc++ offers these modules in C++20 as an + extension.) + Benchmarks ========== diff --git a/libcxx/test/libcxx/module_std.gen.py b/libcxx/test/libcxx/module_std.gen.py index 8e03d6e5b5b52..a9a05a0cd74e6 100644 --- a/libcxx/test/libcxx/module_std.gen.py +++ b/libcxx/test/libcxx/module_std.gen.py @@ -30,6 +30,7 @@ "%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin", "%{cxx}", "%{flags} %{compile_flags}", + "std", ) diff --git a/libcxx/test/libcxx/module_std_compat.gen.py b/libcxx/test/libcxx/module_std_compat.gen.py index c4792db3d71e6..2866066ccedc8 100644 --- a/libcxx/test/libcxx/module_std_compat.gen.py +++ b/libcxx/test/libcxx/module_std_compat.gen.py @@ -30,6 +30,7 @@ "%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin", "%{cxx}", "%{flags} %{compile_flags}", + "std.compat", ) diff --git a/libcxx/test/libcxx/selftest/modules/no-modules.sh.cpp b/libcxx/test/libcxx/selftest/modules/no-modules.sh.cpp new file mode 100644 index 0000000000000..74e65a9072f7b --- /dev/null +++ b/libcxx/test/libcxx/selftest/modules/no-modules.sh.cpp @@ -0,0 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Make sure that the compile flags contain no module information. + +// MODULE_DEPENDENCIES: + +// RUN: echo "%{compile_flags}" | grep -v "std.pcm" +// RUN: echo "%{compile_flags}" | grep -v "std.compat.pcm" diff --git a/libcxx/test/libcxx/selftest/modules/std-and-std.compat-module.sh.cpp b/libcxx/test/libcxx/selftest/modules/std-and-std.compat-module.sh.cpp new file mode 100644 index 0000000000000..d56ebb2961d4a --- /dev/null +++ b/libcxx/test/libcxx/selftest/modules/std-and-std.compat-module.sh.cpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: clang-modules-build +// UNSUPPORTED: gcc + +// XFAIL: has-no-cxx-module-support + +// Make sure that the compile flags contain the expected elements. +// The tests only look for the expected components and not the exact flags. +// Otherwise changing the location of the module would break this test. + +// MODULE_DEPENDENCIES: std std.compat + +// RUN: echo "%{compile_flags}" | grep -- "-fmodule-file=std=.*/std.pcm .*/std.pcm" +// RUN: echo "%{compile_flags}" | grep -- "-fmodule-file=std.compat=.*/std.compat.pcm .*/std.compat.pcm" diff --git a/libcxx/test/libcxx/selftest/modules/std-module.sh.cpp b/libcxx/test/libcxx/selftest/modules/std-module.sh.cpp new file mode 100644 index 0000000000000..ec43994fa1ef9 --- /dev/null +++ b/libcxx/test/libcxx/selftest/modules/std-module.sh.cpp @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: clang-modules-build +// UNSUPPORTED: gcc + +// XFAIL: has-no-cxx-module-support + +// Make sure that the compile flags contain the expected elements. +// The tests only look for the expected components and not the exact flags. +// Otherwise changing the location of the module would break this test. + +// MODULE_DEPENDENCIES: std + +// RUN: echo "%{compile_flags}" | grep -- "-fmodule-file=std=.*/std.pcm .*/std.pcm" + +// The std module should not provide the std.compat module +// RUN: echo "%{compile_flags}" | grep -v "std.compat.pcm" diff --git a/libcxx/test/libcxx/selftest/modules/std.compat-module.sh.cpp b/libcxx/test/libcxx/selftest/modules/std.compat-module.sh.cpp new file mode 100644 index 0000000000000..e84709853fbca --- /dev/null +++ b/libcxx/test/libcxx/selftest/modules/std.compat-module.sh.cpp @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 +// UNSUPPORTED: clang-modules-build +// UNSUPPORTED: gcc + +// XFAIL: has-no-cxx-module-support + +// Make sure that the compile flags contain the expected elements. +// The tests only look for the expected components and not the exact flags. +// Otherwise changing the location of the module would break this test. + +// MODULE_DEPENDENCIES: std.compat + +// RUN: echo "%{compile_flags}" | grep -- "-fmodule-file=std.compat=.*/std.compat.pcm .*/std.compat.pcm" + +// It's unspecified whether std.compat is built on the std module. +// Therefore don't test its presence diff --git a/libcxx/test/std/modules/std.compat.pass.cpp b/libcxx/test/std/modules/std.compat.pass.cpp index ba75f8e401002..e840f3c6b629c 100644 --- a/libcxx/test/std/modules/std.compat.pass.cpp +++ b/libcxx/test/std/modules/std.compat.pass.cpp @@ -8,11 +8,14 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: clang-modules-build +// UNSUPPORTED: gcc -// XFAIL: * +// XFAIL: has-no-cxx-module-support // A minimal test to validate import works. +// MODULE_DEPENDENCIES: std.compat + import std.compat; int main(int, char**) { return !(::strlen("Hello modular world") == 19); } diff --git a/libcxx/test/std/modules/std.pass.cpp b/libcxx/test/std/modules/std.pass.cpp index a018e42a26589..ca05825b3a186 100644 --- a/libcxx/test/std/modules/std.pass.cpp +++ b/libcxx/test/std/modules/std.pass.cpp @@ -8,11 +8,14 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 // UNSUPPORTED: clang-modules-build +// UNSUPPORTED: gcc -// XFAIL: * +// XFAIL: has-no-cxx-module-support // A minimal test to validate import works. +// MODULE_DEPENDENCIES: std + import std; int main(int, char**) { diff --git a/libcxx/utils/libcxx/test/config.py b/libcxx/utils/libcxx/test/config.py index 63cfa1b677819..9a71c494030d7 100644 --- a/libcxx/utils/libcxx/test/config.py +++ b/libcxx/utils/libcxx/test/config.py @@ -16,6 +16,10 @@ def _getSubstitution(substitution, config): 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] + + def configure(parameters, features, config, lit_config): note = lambda s: lit_config.note("({}) {}".format(config.name, s)) config.environment = dict(os.environ) diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py index 1570a6b516739..83b4cebf75411 100644 --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -317,6 +317,18 @@ def _getAndroidDeviceApi(cfg): AddSubstitution("%{clang-tidy}", lambda cfg: _getSuitableClangTidy(cfg)) ], ), + # Whether module support for the platform is available. + Feature( + name="has-no-cxx-module-support", + # The libc of these platforms have functions with internal linkage. + # This is not allowed per C11 7.1.2 Standard headers/6 + # Any declaration of a library function shall have external linkage. + when=lambda cfg: "__ANDROID__" in compilerMacros(cfg) + or "__PICOLIBC__" in compilerMacros(cfg) + or platform.system().lower().startswith("aix") + # Avoid building on platforms that don't support modules properly. + or not hasCompileFlag(cfg, "-Wno-reserved-module-identifier"), + ), ] # Deduce and add the test features that that are implied by the #defines in diff --git a/libcxx/utils/libcxx/test/format.py b/libcxx/utils/libcxx/test/format.py index 5d84711bf5d28..c605e8796ac54 100644 --- a/libcxx/utils/libcxx/test/format.py +++ b/libcxx/utils/libcxx/test/format.py @@ -7,6 +7,7 @@ # ===----------------------------------------------------------------------===## import lit +import libcxx.test.config as config import lit.formats import os import re @@ -52,6 +53,14 @@ def _executeScriptInternal(test, litConfig, commands): return (out, err, exitCode, timeoutInfo, parsedCommands) +def _validateModuleDependencies(modules): + for m in modules: + if m not in ("std", "std.compat"): + raise RuntimeError( + f"Invalid module dependency '{m}', only 'std' and 'std.compat' are valid" + ) + + def parseScript(test, preamble): """ Extract the script from a test, with substitutions applied. @@ -91,6 +100,8 @@ def parseScript(test, preamble): # Parse the test file, including custom directives additionalCompileFlags = [] fileDependencies = [] + modules = [] # The enabled modules + moduleCompileFlags = [] # The compilation flags to use modules parsers = [ lit.TestRunner.IntegratedTestKeywordParser( "FILE_DEPENDENCIES:", @@ -102,6 +113,11 @@ def parseScript(test, preamble): lit.TestRunner.ParserKind.SPACE_LIST, initial_value=additionalCompileFlags, ), + lit.TestRunner.IntegratedTestKeywordParser( + "MODULE_DEPENDENCIES:", + lit.TestRunner.ParserKind.SPACE_LIST, + initial_value=modules, + ), ] # Add conditional parsers for ADDITIONAL_COMPILE_FLAGS. This should be replaced by first @@ -132,12 +148,53 @@ def parseScript(test, preamble): script += scriptInTest # Add compile flags specified with ADDITIONAL_COMPILE_FLAGS. - substitutions = [ - (s, x + " " + " ".join(additionalCompileFlags)) - if s == "%{compile_flags}" - else (s, x) - for (s, x) in substitutions - ] + # Modules need to be built with the same compilation flags as the + # test. So add these flags before adding the modules. + substitutions = config._appendToSubstitution( + substitutions, "%{compile_flags}", " ".join(additionalCompileFlags) + ) + + if modules: + _validateModuleDependencies(modules) + + # The moduleCompileFlags are added to the %{compile_flags}, but + # the modules need to be built without these flags. So expand the + # %{compile_flags} eagerly and hardcode them in the build script. + compileFlags = config._getSubstitution("%{compile_flags}", test.config) + + # Building the modules needs to happen before the other script + # commands are executed. Therefore the commands are added to the + # front of the list. + if "std.compat" in modules: + script.insert( + 0, + "%dbg(MODULE std.compat) %{cxx} %{flags} " + f"{compileFlags} " + "-Wno-reserved-module-identifier -Wno-reserved-user-defined-literal " + "--precompile -o %T/std.compat.pcm -c %{module}/std.compat.cppm", + ) + moduleCompileFlags.extend( + ["-fmodule-file=std.compat=%T/std.compat.pcm", "%T/std.compat.pcm"] + ) + + # Make sure the std module is built before std.compat. Libc++'s + # std.compat module depends on the std module. It is not + # known whether the compiler expects the modules in the order of + # their dependencies. However it's trivial to provide them in + # that order. + script.insert( + 0, + "%dbg(MODULE std) %{cxx} %{flags} " + f"{compileFlags} " + "-Wno-reserved-module-identifier -Wno-reserved-user-defined-literal " + "--precompile -o %T/std.pcm -c %{module}/std.cppm", + ) + moduleCompileFlags.extend(["-fmodule-file=std=%T/std.pcm", "%T/std.pcm"]) + + # Add compile flags required for the modules. + substitutions = config._appendToSubstitution( + substitutions, "%{compile_flags}", " ".join(moduleCompileFlags) + ) # Perform substitutions in the script itself. script = lit.TestRunner.applySubstitutions( diff --git a/libcxx/utils/libcxx/test/modules.py b/libcxx/utils/libcxx/test/modules.py index 9362d52cb72d2..3e9fcae4c5389 100644 --- a/libcxx/utils/libcxx/test/modules.py +++ b/libcxx/utils/libcxx/test/modules.py @@ -113,6 +113,7 @@ class module_test_generator: clang_tidy_plugin: str compiler: str compiler_flags: str + module: str def write_lit_configuration(self): print( @@ -120,13 +121,13 @@ def write_lit_configuration(self): // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: clang-modules-build -// XFAIL: * - // REQUIRES: has-clang-tidy // The GCC compiler flags are not always compatible with clang-tidy. // UNSUPPORTED: gcc +// MODULE_DEPENDENCIES: {self.module} + // RUN: echo -n > {self.tmp_prefix}.all_partitions """ )