-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
P2465R3 Standard Library Modules std
And std.compat
#3108
Conversation
_EXPORT_STD is activated by defined(_BUILD_STD_MODULE) and further guarded by _HAS_CXX23 because we don't want to allow Time-Traveling Standard Library Modules. However, this isn't further guarded for compilers. If someone (including us) wants to build Standard Library Modules with an untested compiler, they can try. Only the feature-test macro is guarded to declare support for the compilers we've tested.
Changelog:
@cdacamar could confirm whether this was a compiler bug or by design. At the time (and a lot has changed in the compiler since I dealt with this), if an Even if this was a compiler bug that has been fixed, I still think it is more desirable and consistent for both the types and the functions to be |
I should have tagged this comment "No change requested". I'm perfectly happy with the change even if it's no longer necessary, I just wanted to understand why it is/was necessary. |
I'm mirroring this to the MSVC-internal repo - please notify me if any further changes are pushed. |
I've pushed a 2-line commit to fix the internal compiler objsize test. I don't fully understand the root cause, but something about repeating
As the intention is to have zero effect on non-modules builds, I've guarded these function declarations with the |
Fixes #2930.
Overview
I've added over 3,750 occurrences of
_EXPORT_STD
after auditing over 148,000 lines of headers. This was a manual process, checking against the Standard to verify that all user-visible machinery is being exported - not an automated search-and-replace.The tests are passing with VS 2022 17.4 Preview 2 and a number of workarounds in product code. There are some known compiler bugs with no available workarounds (e.g. various ICEs), and @cdacamar is continuing to investigate those. The tracking issue for compiler bugs is #1694. As previously seen with Standard Library Header Units, adding test coverage for Standard Library Modules will help in two ways: (1) preventing compiler changes from introducing regressions, and (2) immediately identifying when library changes or additions have encountered previously-unknown bugs, so we can report and work around them.
Currently, only the MSVC compiler front-end is supported. The EDG front-end that's used for IntelliSense is still working on modules support. As usual, we'll support Clang as a first-class citizen as soon as compiler support is available. I am assuming that
__cpp_lib_concepts
is defined (i.e. not attempting to mark the non-concepts fallbacks as exported). This can be revisited if EDG gains support for modules significantly before concepts.This adds two files,
std.ixx
andstd.compat.ixx
, in amodules
subdirectory that's a sibling of theinc
orinclude
subdirectory for headers. These will be used to build IFC and OBJ files (by the user's build system). Notably, we will never ship prebuilt IFCs/OBJs. This allows the Standard Library Modules to handle all of the user's various compiler options and macro settings (e.g. release/debug, static/dynamic,_ITERATOR_DEBUG_LEVEL
, warning suppression, restoring removed machinery, disabling Standard machinery, etc.). Building the modules is quite fast, and thestd.compat.ixx
build is lightweight.The implementation strategy that I've chosen is minimally intrusive and should be easy to maintain going forward. Top-level Standard classes/functions/variables/etc. need to be marked with
_EXPORT_STD
, which will expand toexport
when building the Standard Library Modules, and will expand to nothing otherwise. Due to how modules work, we don't need to mark the contents of classes (e.g. member functions, nested classes, hidden friends), nor do we need to mark partial/explicit specializations. We also don't need to mark namespaces (they will be exported implicitly when anything within is exported). I've merged a bunch of previous cleanup PRs, increasing the STL's use of hidden friends, reducing the number of changes that are necessary here. I'll mention the rare occurrences where we need to export things that don't appear in the Standard, in order to make Standard code work.As @CaseyCarter noted in a comment, we also don't need to export deduction guides. See [temp.deduct.guide]/1 "Deduction guides are not found by name lookup. Instead, when performing class template argument deduction ([over.match.class.deduct]), all reachable deduction guides declared for the class template are considered." and [module.reach]'s definition of "reachable".
Regarding operators, here's an example showing how we don't need to export hidden friends, but we do need to export ordinary non-members in order for them to be found through ADL:
Click to expand example:
The other thing that we need, because we have a "classic" separately compiled build with ABI constraints, is the addition of
extern "C++"
in various places. This has no effect on non-modules code, so it's being added unconditionally, but in the modules world it has a special meaning.Going forward, all PRs that are adding or refactoring features will need to mark things as
_EXPORT_STD
.I've followed a strict policy of exporting only Standard machinery, with no exceptions for Microsoft extensions (like
stdext::checked_array_iterator
or<cvt/meow>
) or TS machinery<experimental/meow>
. Note that deprecated components are still Standard and must be exported. The quasi-exception to this policy is that I'm also exporting previously-Standard-but-removed components, following the philosophy that Standard Library Modules respect all compiler options/settings. (This has no effect for users who are using the default/std:c++latest
setting of removing that old machinery. It's just that if users choose to restore it, then Modules won't stand in their way.)I have chosen to consistently mark all declarations and definitions as exported, even though (I believe) only the first declaration is necessary to mark.
I've updated the wiki's Files To Edit When Adding Or Removing Files.
Changes
yvals_core.h
_EXPORT_STD
and__cpp_lib_modules
._EXPORT_STD
is activated bydefined(_BUILD_STD_MODULE)
(which is defined bystd.ixx
, not by users directly) and further guarded by_HAS_CXX23
because we don't want to allow Time-Traveling Standard Library Modules at this time. However, this isn't further guarded for compilers. If someone (including us) wants to build Standard Library Modules with an untested compiler, they can try. Only the feature-test macro is guarded to declare support for the compilers we've tested.VSO_0157762_feature_test_macros/test.compile.pass.cpp
csetjmp
setjmp
is a macro, so I'm not exporting it. See [csetjmp.syn] and [headers]/6.cstddef
nullptr_t
. This is special because it was neither defined in the STL nor in VCRuntime. Instead, it was predefined by the compiler (internalsrc/vctools/Compiler/CxxFE/sl/p1/c/typesrc.c
). Now we need to define the alias again in order to export it.byte
, itsoperator
s, andto_integer()
.cmath
hypot
is special:std.compat
will need to provide only 2-arg in the global namespace. Therefore, I need aninline namespace _Binary_hypot
. (This_Ugly
namespace is implicitly exported, which is unavoidable, but also unobservable.)hypot
,lerp
, and Special Math.ranges
_Pipe::operator|
. This is a special case, where we must export something_Ugly
in order to make Standard code work (as this will be found through ADL).complex
strstream
swap()
overloads.exception
exception
andbad_exception
. (This is necessary because we get them from VCRuntime.)_HAS_UNEXPECTED
is blocked in C++23 mode, so nothing within is exported.typeinfo
type_info
,bad_cast
,bad_typeid
. (VCRuntime again.)new
extern "C++"
note below._BUILD_STD_MODULE
control macro to avoid disrupting the internal compiler objsize test.<exception> exports bad_alloc and bad_array_new_length
for_HAS_EXCEPTIONS=0
, as this was non-obvious._BITMASK_OPS
macro so we can conditionally export the operators that it defines.extern "C"
andextern "C++"
The surprising thing is how much of the STL's separately compiled code works without any additional changes. The compiler knows that
extern "C"
machinery is attached to the global module. That's why all UCRT headers work, and much of the STL's modern code where we've moved to flatextern "C"
interfaces. However, for the older code, we need to addextern "C++"
. This has always been part of C++'s grammar but was previously near-useless. Now, it attaches declarations to the global module, see N4917 [module.unit]/7.In two cases, the
__std_fs
types and the__std_tzdb
types, we were defining types outside ofextern "C"
but using them in the signatures ofextern "C"
functions. We need to be more disciplined now, so I'm marking these types asextern "C"
. (However, we need to exclude the_BITMASK_OPS
overloads.) This doesn't affect ABI or name mangling for classic code.Then, we need to mark separately compiled functions/classes/objects as
extern "C++"
. (I found these by searching for occurrences of_CRTIMP2_PURE
etc. - the only changes that were semi-automated - and by fixing errors that occurred during testing. For example, this is why various facets are marked.)For
<iostream>
, I am making one additional change/cleanup -__PURE_APPDOMAIN_GLOBAL
and_CRTDATA2_IMPORT
emit__declspec
s (sometimes), but previouslyextern
occurred in the middle. It's more consistent to keep them together, and to keep_EXPORT_STD extern "C++"
together. This should have no effect on non-modules code.The Modules Themselves
This adds
stl/modules/std.ixx
, which is (surprisingly?) simple after all of the above changes. It defines_BUILD_STD_MODULE
(with a comment that it assumes that it's going to use classic headers - trying to use header units to build this named module would be weird). It has a global module fragment (themodule;
section) where the UCRT headers are included - this is not strictly necessary, but recommended by @cdacamar. Then weexport module std;
, silence a warning because we know what we're doing, and include all Standard and<cmeow>
headers.Then this adds
stl/modules/std.compat.ixx
, a lightweight module. It's super fast to build because it saysexport import std;
to reuse thestd.ifc
build, then exports additional names in the global namespace. I looked at each C wrapper header, extracted its exported names, and filtered out names that should only be instd
(e.g.byte
,lerp
, and Special Math arestd
-only). The names appear in Standard order here, but I'm not reordering the<cmeow>
implementations.I've commented duplicates for clarity (so that comparing
<cmeow>
to these lists doesn't involve mysteriously missing names).As previously mentioned,
<cmath>
's binaryhypot()
is special.For
<cstddef>
, I'm exportingmax_align_t
(C11) andnullptr_t
(C23) in the global namespace.Then, we need a few infrastructure changes:
.gitattributes
, markstl/modules
as C++.tools/format/CMakeLists.txt
, clang-formatstl/modules/*.ixx
.stl/CMakeLists.txt
how to copy the modules.Compiler Bug Workarounds
I currently have 3 workarounds which involve marking internal machinery as exported (very targeted and easy to remove later):
chrono
formatting emits bogus error C3861:'_Fill_tm'
: identifier not found"sort()
emits bogus error C2065:'_Atomic_counter_t'
: undeclared identifier".One simple workaround for an EDG bug:
extern "C++" enum class Cats : int;
".There's one additional workaround for strange code in the UCRT:
time()
function using modules andstd.core
".This workaround is much more elaborate, as it involves defining our own functions instead of just
using
the UCRT's. (Scenarios that have bothimport std;
and#include <ctime>
would experience ambiguity, I believe.) Currently, this is the best known way to get the<ctime>
machinery into thestd
module.Testing
I'm substantially unifying the Header Units and Modules test coverage. The directories and their scripts remain separate, but the code that exercises each Standard header is shared.
/D_STL_CALL_ABORT_INSTEAD_OF_INVALID_PARAMETER
toP1502R1_standard_library_header_units/env.lst
. This was missed by Even more various cleanups #2656.__cpp_lib_source_location
guards.impl_test_source_location()
.<memory>
before<memory_resource>
,<string>
before<string_view>
.test_stacktrace()
.TEST_HEADER_UNITS
.using namespace std;
into each function.test_header_units_and_modules.hpp
, we definitely don't want a file-scopeusing
-directive. However, for the Standard headers, we don't really have to worry about Header Units or named Modules emittingvector
in the global namespace, so we can justusing namespace std;
. (We'll be more disciplined about testingstd.compat
.)test_header_units_and_modules.hpp
.<version>
.P2465R3_standard_library_modules
.modules
directory is.test.cpp
(test Standard headers and the<cmeow>
wrappers),test2.cpp
(teststd.compat
), andclassic.cpp
(isolateforce_include.hpp
)..ixx
files first. Real usage will use more elaborate command lines to compile the.ixx
files separately./analyze:only /analyze:autolog-
isn't taking too long (unlike Header Units) so I am adding configurations here.Internal Changes, Next Steps
I've prepared internal changes (for MSVC MSBuild, the Perl test harness, and Setup). The remaining things to do are:
In followup internal work, we'll need to integrate this with the VS IDE, MSBuild for users, and CMake/Ninja builds.