-
Notifications
You must be signed in to change notification settings - Fork 1.6k
P2465R3 Standard Library Modules std And std.compat
#3108
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
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_STDafter 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_conceptsis 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.ixxandstd.compat.ixx, in amodulessubdirectory that's a sibling of theincorincludesubdirectory 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.ixxbuild 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 toexportwhen 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_iteratoror<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++latestsetting 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_STDand__cpp_lib_modules._EXPORT_STDis activated bydefined(_BUILD_STD_MODULE)(which is defined bystd.ixx, not by users directly) and further guarded by_HAS_CXX23because 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.cppcsetjmpsetjmpis a macro, so I'm not exporting it. See [csetjmp.syn] and [headers]/6.cstddefnullptr_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, itsoperators, andto_integer().cmathhypotis special:std.compatwill need to provide only 2-arg in the global namespace. Therefore, I need aninline namespace _Binary_hypot. (This_Uglynamespace 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_Uglyin order to make Standard code work (as this will be found through ADL).complexstrstreamswap()overloads.exceptionexceptionandbad_exception. (This is necessary because we get them from VCRuntime.)_HAS_UNEXPECTEDis blocked in C++23 mode, so nothing within is exported.typeinfotype_info,bad_cast,bad_typeid. (VCRuntime again.)newextern "C++"note below._BUILD_STD_MODULEcontrol macro to avoid disrupting the internal compiler objsize test.<exception> exports bad_alloc and bad_array_new_lengthfor_HAS_EXCEPTIONS=0, as this was non-obvious._BITMASK_OPSmacro 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_fstypes and the__std_tzdbtypes, 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_OPSoverloads.) 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_PUREetc. - 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_GLOBALand_CRTDATA2_IMPORTemit__declspecs (sometimes), but previouslyexternoccurred 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.ifcbuild, 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/modulesas C++.tools/format/CMakeLists.txt, clang-formatstl/modules/*.ixx.stl/CMakeLists.txthow 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):
chronoformatting 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
usingthe 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 thestdmodule.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_PARAMETERtoP1502R1_standard_library_header_units/env.lst. This was missed by Even more various cleanups #2656.__cpp_lib_source_locationguards.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 emittingvectorin 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.modulesdirectory is.test.cpp(test Standard headers and the<cmeow>wrappers),test2.cpp(teststd.compat), andclassic.cpp(isolateforce_include.hpp)..ixxfiles first. Real usage will use more elaborate command lines to compile the.ixxfiles 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.