Author: Chuanqi Xu Date: 2023-11-09T17:44:41+08:00 New Revision: 0f7aaeb3241c3803489a45753190e82dbc7fd5fa
URL: https://github.com/llvm/llvm-project/commit/0f7aaeb3241c3803489a45753190e82dbc7fd5fa DIFF: https://github.com/llvm/llvm-project/commit/0f7aaeb3241c3803489a45753190e82dbc7fd5fa.diff LOG: [C++20] [Modules] Allow export from language linkage Close https://github.com/llvm/llvm-project/issues/71347 Previously I misread the concept of module purview. I thought if a declaration attached to a unnamed module, it can't be part of the module purview. But after the issue report, I recognized that module purview is more of a concept about locations instead of semantics. Concretely, the things in the language linkage after module declarations can be exported. This patch refactors `Module::isModulePurview()` and introduces some possible code cleanups. Added: Modified: clang/include/clang/Basic/Module.h clang/include/clang/Lex/ModuleMap.h clang/include/clang/Sema/Sema.h clang/include/clang/Serialization/ASTWriter.h clang/lib/AST/ASTContext.cpp clang/lib/AST/Decl.cpp clang/lib/AST/DeclBase.cpp clang/lib/CodeGen/CodeGenModule.cpp clang/lib/Frontend/ASTUnit.cpp clang/lib/Lex/ModuleMap.cpp clang/lib/Sema/Sema.cpp clang/lib/Sema/SemaDecl.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaLookup.cpp clang/lib/Sema/SemaModule.cpp clang/lib/Serialization/ASTWriterDecl.cpp clang/test/Modules/export-language-linkage.cppm clang/test/SemaCXX/modules.cppm Removed: ################################################################################ diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index 239eb5a637f3ecf..08b153e8c1c9d33 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -178,9 +178,8 @@ class alignas(8) Module { /// eventually be exposed, for use in "private" modules. std::string ExportAsModule; - /// Does this Module scope describe part of the purview of a standard named - /// C++ module? - bool isModulePurview() const { + /// Does this Module is a named module of a standard named module? + bool isNamedModule() const { switch (Kind) { case ModuleInterfaceUnit: case ModuleImplementationUnit: diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h index d5824713970ea7b..32e7e8f899e502c 100644 --- a/clang/include/clang/Lex/ModuleMap.h +++ b/clang/include/clang/Lex/ModuleMap.h @@ -556,8 +556,8 @@ class ModuleMap { /// parent. Module *createGlobalModuleFragmentForModuleUnit(SourceLocation Loc, Module *Parent = nullptr); - Module *createImplicitGlobalModuleFragmentForModuleUnit( - SourceLocation Loc, bool IsExported, Module *Parent = nullptr); + Module *createImplicitGlobalModuleFragmentForModuleUnit(SourceLocation Loc, + Module *Parent); /// Create a global module fragment for a C++ module interface unit. Module *createPrivateModuleFragmentForInterfaceUnit(Module *Parent, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fe8b387f198c56e..63d548c30da7f6e 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2317,14 +2317,9 @@ class Sema final { clang::Module *TheGlobalModuleFragment = nullptr; /// The implicit global module fragments of the current translation unit. - /// We would only create at most two implicit global module fragments to - /// avoid performance penalties when there are many language linkage - /// exports. /// - /// The contents in the implicit global module fragment can't be discarded - /// no matter if it is exported or not. + /// The contents in the implicit global module fragment can't be discarded. clang::Module *TheImplicitGlobalModuleFragment = nullptr; - clang::Module *TheExportedImplicitGlobalModuleFragment = nullptr; /// Namespace definitions that we will export when they finish. llvm::SmallPtrSet<const NamespaceDecl*, 8> DeferredExportedNamespaces; @@ -2336,9 +2331,7 @@ class Sema final { /// Helper function to judge if we are in module purview. /// Return false if we are not in a module. - bool isCurrentModulePurview() const { - return getCurrentModule() ? getCurrentModule()->isModulePurview() : false; - } + bool isCurrentModulePurview() const; /// Enter the scope of the explicit global module fragment. Module *PushGlobalModuleFragment(SourceLocation BeginLoc); @@ -2346,8 +2339,7 @@ class Sema final { void PopGlobalModuleFragment(); /// Enter the scope of an implicit global module fragment. - Module *PushImplicitGlobalModuleFragment(SourceLocation BeginLoc, - bool IsExported); + Module *PushImplicitGlobalModuleFragment(SourceLocation BeginLoc); /// Leave the scope of an implicit global module fragment. void PopImplicitGlobalModuleFragment(); diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h index 3019bbc2ddc9cc7..a56929ef0245ee9 100644 --- a/clang/include/clang/Serialization/ASTWriter.h +++ b/clang/include/clang/Serialization/ASTWriter.h @@ -746,7 +746,7 @@ class ASTWriter : public ASTDeserializationListener, ASTReader *getChain() const { return Chain; } bool isWritingStdCXXNamedModules() const { - return WritingModule && WritingModule->isModulePurview(); + return WritingModule && WritingModule->isNamedModule(); } private: diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 014ba4913581f1a..4f54791b4c1e5ce 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -1096,7 +1096,7 @@ ArrayRef<Decl *> ASTContext::getModuleInitializers(Module *M) { } void ASTContext::setCurrentNamedModule(Module *M) { - assert(M->isModulePurview()); + assert(M->isNamedModule()); assert(!CurrentCXXNamedModule && "We should set named module for ASTContext for only once"); CurrentCXXNamedModule = M; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index ed848e574233468..c5c2edf1bfe3aba 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -579,27 +579,6 @@ static bool isSingleLineLanguageLinkage(const Decl &D) { return false; } -/// Determine whether D is declared in the purview of a named module. -static bool isInModulePurview(const NamedDecl *D) { - if (auto *M = D->getOwningModule()) - return M->isModulePurview(); - return false; -} - -static bool isExportedFromModuleInterfaceUnit(const NamedDecl *D) { - // FIXME: Handle isModulePrivate. - switch (D->getModuleOwnershipKind()) { - case Decl::ModuleOwnershipKind::Unowned: - case Decl::ModuleOwnershipKind::ReachableWhenImported: - case Decl::ModuleOwnershipKind::ModulePrivate: - return false; - case Decl::ModuleOwnershipKind::Visible: - case Decl::ModuleOwnershipKind::VisibleWhenImported: - return isInModulePurview(D); - } - llvm_unreachable("unexpected module ownership kind"); -} - static bool isDeclaredInModuleInterfaceOrPartition(const NamedDecl *D) { if (auto *M = D->getOwningModule()) return M->isInterfaceOrPartition(); @@ -1191,6 +1170,27 @@ Linkage NamedDecl::getLinkageInternal() const { .getLinkage(); } +/// Determine whether D is attached to a named module. +static bool isInNamedModule(const NamedDecl *D) { + if (auto *M = D->getOwningModule()) + return M->isNamedModule(); + return false; +} + +static bool isExportedFromModuleInterfaceUnit(const NamedDecl *D) { + // FIXME: Handle isModulePrivate. + switch (D->getModuleOwnershipKind()) { + case Decl::ModuleOwnershipKind::Unowned: + case Decl::ModuleOwnershipKind::ReachableWhenImported: + case Decl::ModuleOwnershipKind::ModulePrivate: + return false; + case Decl::ModuleOwnershipKind::Visible: + case Decl::ModuleOwnershipKind::VisibleWhenImported: + return isInNamedModule(D); + } + llvm_unreachable("unexpected module ownership kind"); +} + /// Get the linkage from a semantic point of view. Entities in /// anonymous namespaces are external (in c++98). Linkage NamedDecl::getFormalLinkage() const { @@ -1204,7 +1204,7 @@ Linkage NamedDecl::getFormalLinkage() const { // [basic.namespace.general]/p2 // A namespace is never attached to a named module and never has a name with // module linkage. - if (isInModulePurview(this) && InternalLinkage == Linkage::External && + if (isInNamedModule(this) && InternalLinkage == Linkage::External && !isExportedFromModuleInterfaceUnit( cast<NamedDecl>(this->getCanonicalDecl())) && !isa<NamespaceDecl>(this)) diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp index 3fd4751d6d1f31d..4fcc2e7302c034c 100644 --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -1113,7 +1113,7 @@ bool Decl::isInAnotherModuleUnit() const { if (M->isGlobalModule()) return false; - assert(M->isModulePurview() && "New module kind?"); + assert(M->isNamedModule() && "New module kind?"); return M != getASTContext().getCurrentNamedModule(); } diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 2e96fff808113ba..75355282878f2f3 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3859,7 +3859,7 @@ bool CodeGenModule::shouldEmitFunction(GlobalDecl GD) { // We don't import function bodies from other named module units since that // behavior may break ABI compatibility of the current unit. if (const Module *M = F->getOwningModule(); - M && M->isModulePurview() && + M && M->getTopLevelModule()->isNamedModule() && getContext().getCurrentNamedModule() != M->getTopLevelModule() && !F->hasAttr<AlwaysInlineAttr>()) return false; diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp index 85157c3b21b6648..c4badcd00c477e0 100644 --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/clang/lib/Frontend/ASTUnit.cpp @@ -883,7 +883,7 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromASTFile( PP.setCounterValue(Counter); Module *M = HeaderInfo.lookupModule(AST->getLangOpts().CurrentModule); - if (M && AST->getLangOpts().isCompilingModule() && M->isModulePurview()) + if (M && AST->getLangOpts().isCompilingModule() && M->isNamedModule()) AST->Ctx->setCurrentNamedModule(M); // Create an AST consumer, even though it isn't used. diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp index e4f3cdd3b163f50..00e13c9be4a7d73 100644 --- a/clang/lib/Lex/ModuleMap.cpp +++ b/clang/lib/Lex/ModuleMap.cpp @@ -881,17 +881,17 @@ Module *ModuleMap::createGlobalModuleFragmentForModuleUnit(SourceLocation Loc, return Result; } -Module *ModuleMap::createImplicitGlobalModuleFragmentForModuleUnit( - SourceLocation Loc, bool IsExported, Module *Parent) { +Module * +ModuleMap::createImplicitGlobalModuleFragmentForModuleUnit(SourceLocation Loc, + Module *Parent) { assert(Parent && "We should only create an implicit global module fragment " "in a module purview"); // Note: Here the `IsExplicit` parameter refers to the semantics in clang // modules. All the non-explicit submodules in clang modules will be exported // too. Here we simplify the implementation by using the concept. - auto *Result = new Module(IsExported ? "<exported implicit global>" - : "<implicit global>", - Loc, Parent, /*IsFramework*/ false, - /*IsExplicit*/ !IsExported, NumCreatedModules++); + auto *Result = + new Module("<implicit global>", Loc, Parent, /*IsFramework=*/false, + /*IsExplicit=*/false, NumCreatedModules++); Result->Kind = Module::ImplicitGlobalModuleFragment; return Result; } diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index d25edd470334f63..d7d8d2eaa37e1d6 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1286,8 +1286,6 @@ void Sema::ActOnEndOfTranslationUnit() { if (auto *FD = dyn_cast<FunctionDecl>(D)) { bool DefInPMF = false; if (auto *FDD = FD->getDefinition()) { - assert(FDD->getOwningModule() && - FDD->getOwningModule()->isModulePurview()); DefInPMF = FDD->getOwningModule()->isPrivateModule(); if (!DefInPMF) continue; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 7a424eaa5fe7d8e..3876eb501083acb 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -1684,8 +1684,8 @@ bool Sema::CheckRedeclarationModuleOwnership(NamedDecl *New, NamedDecl *Old) { return false; } - bool NewIsModuleInterface = NewM && NewM->isModulePurview(); - bool OldIsModuleInterface = OldM && OldM->isModulePurview(); + bool NewIsModuleInterface = NewM && NewM->isNamedModule(); + bool OldIsModuleInterface = OldM && OldM->isNamedModule(); if (NewIsModuleInterface || OldIsModuleInterface) { // C++ Modules TS [basic.def.odr] 6.2/6.7 [sic]: // if a declaration of D [...] appears in the purview of a module, all @@ -1819,7 +1819,7 @@ bool Sema::IsRedefinitionInModule(const NamedDecl *New, // [basic.def.odr]p14.3 // Each such definition shall not be attached to a named module // ([module.unit]). - if ((NewM && NewM->isModulePurview()) || (OldM && OldM->isModulePurview())) + if ((NewM && NewM->isNamedModule()) || (OldM && OldM->isNamedModule())) return true; // Then New and Old lives in the same TU if their share one same module unit. @@ -10165,8 +10165,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, // check at the end of the TU (or when the PMF starts) to see that we // have a definition at that point. if (isInline && !D.isFunctionDefinition() && getLangOpts().CPlusPlus20 && - NewFD->hasOwningModule() && - NewFD->getOwningModule()->isModulePurview()) { + NewFD->hasOwningModule() && NewFD->getOwningModule()->isNamedModule()) { PendingInlineFuncDecls.insert(NewFD); } } diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 60786a880b9d3fd..3688192e6cbe5c5 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -16798,8 +16798,7 @@ Decl *Sema::ActOnStartLinkageSpecification(Scope *S, SourceLocation ExternLoc, /// If the declaration is already in global module fragment, we don't /// need to attach it again. if (getLangOpts().CPlusPlusModules && isCurrentModulePurview()) { - Module *GlobalModule = PushImplicitGlobalModuleFragment( - ExternLoc, /*IsExported=*/D->isInExportDeclContext()); + Module *GlobalModule = PushImplicitGlobalModuleFragment(ExternLoc); D->setLocalOwningModule(GlobalModule); } diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp index 324a57095560b79..be011b9f4368d2c 100644 --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -1593,7 +1593,6 @@ bool Sema::isUsableModule(const Module *M) { // The global module fragment can be used to provide declarations that are // attached to the global module and usable within the module unit. if (M == TheGlobalModuleFragment || M == TheImplicitGlobalModuleFragment || - M == TheExportedImplicitGlobalModuleFragment || // If M is the module we're parsing, it should be usable. This covers the // private module fragment. The private module fragment is usable only if // it is within the current module unit. And it must be the current @@ -3911,7 +3910,7 @@ void Sema::ArgumentDependentLookup(DeclarationName Name, SourceLocation Loc, // exports are only valid in module purview and outside of any // PMF (although a PMF should not even be present in a module // with an import). - assert(FM && FM->isModulePurview() && !FM->isPrivateModule() && + assert(FM && FM->isNamedModule() && !FM->isPrivateModule() && "bad export context"); // .. are attached to a named module M, do not appear in the // translation unit containing the point of the lookup.. diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp index 99b4d7d0fe40ee3..9282ceb8dee03de 100644 --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -939,22 +939,20 @@ void Sema::PopGlobalModuleFragment() { ModuleScopes.pop_back(); } -Module *Sema::PushImplicitGlobalModuleFragment(SourceLocation BeginLoc, - bool IsExported) { - Module **M = IsExported ? &TheExportedImplicitGlobalModuleFragment - : &TheImplicitGlobalModuleFragment; - if (!*M) { +Module *Sema::PushImplicitGlobalModuleFragment(SourceLocation BeginLoc) { + if (!TheImplicitGlobalModuleFragment) { ModuleMap &Map = PP.getHeaderSearchInfo().getModuleMap(); - *M = Map.createImplicitGlobalModuleFragmentForModuleUnit( - BeginLoc, IsExported, getCurrentModule()); + TheImplicitGlobalModuleFragment = + Map.createImplicitGlobalModuleFragmentForModuleUnit(BeginLoc, + getCurrentModule()); } - assert(*M && "module creation should not fail"); + assert(TheImplicitGlobalModuleFragment && "module creation should not fail"); // Enter the scope of the global module. - ModuleScopes.push_back({BeginLoc, *M, + ModuleScopes.push_back({BeginLoc, TheImplicitGlobalModuleFragment, /*OuterVisibleModules=*/{}}); - VisibleModules.setVisible(*M, BeginLoc); - return *M; + VisibleModules.setVisible(TheImplicitGlobalModuleFragment, BeginLoc); + return TheImplicitGlobalModuleFragment; } void Sema::PopImplicitGlobalModuleFragment() { @@ -963,3 +961,22 @@ void Sema::PopImplicitGlobalModuleFragment() { "left the wrong module scope, which is not global module fragment"); ModuleScopes.pop_back(); } + +bool Sema::isCurrentModulePurview() const { + if (!getCurrentModule()) + return false; + + /// Does this Module scope describe part of the purview of a standard named + /// C++ module? + switch (getCurrentModule()->Kind) { + case Module::ModuleInterfaceUnit: + case Module::ModuleImplementationUnit: + case Module::ModulePartitionInterface: + case Module::ModulePartitionImplementation: + case Module::PrivateModuleFragment: + case Module::ImplicitGlobalModuleFragment: + return true; + default: + return false; + } +} diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index 763b610288f1182..bf082e5b8eac61a 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -2443,7 +2443,7 @@ static bool isRequiredDecl(const Decl *D, ASTContext &Context, // Named modules have diff erent semantics than header modules. Every named // module units owns a translation unit. So the importer of named modules // doesn't need to deserilize everything ahead of time. - if (WritingModule && WritingModule->isModulePurview()) { + if (WritingModule && WritingModule->isNamedModule()) { // The PragmaCommentDecl and PragmaDetectMismatchDecl are MSVC's extension. // And the behavior of MSVC for such cases will leak this to the module // users. Given pragma is not a standard thing, the compiler has the space diff --git a/clang/test/Modules/export-language-linkage.cppm b/clang/test/Modules/export-language-linkage.cppm index 331a620aaad0613..420e4be0ce45b63 100644 --- a/clang/test/Modules/export-language-linkage.cppm +++ b/clang/test/Modules/export-language-linkage.cppm @@ -3,9 +3,10 @@ // RUN: cd %t // // RUN: %clang_cc1 -std=c++20 %t/a.cppm -emit-module-interface -o %t/a.pcm -// RUN: %clang_cc1 -std=c++20 %t/b.cpp -fmodule-file=a=%t/a.pcm -fsyntax-only -verify -// RUN: %clang_cc1 -std=c++20 %t/c.cppm -fsyntax-only -verify // RUN: %clang_cc1 -module-file-info %t/a.pcm | FileCheck %t/a.cppm +// RUN: %clang_cc1 -std=c++20 %t/b.cpp -fmodule-file=a=%t/a.pcm -fsyntax-only -verify +// RUN: %clang_cc1 -std=c++20 %t/c.cppm -emit-module-interface -o %t/c.pcm +// RUN: %clang_cc1 -std=c++20 %t/d.cpp -fsyntax-only -verify -fmodule-file=c=%t/c.pcm //--- a.cppm export module a; @@ -25,7 +26,6 @@ export { extern "C++" void unexported(); // CHECK: Sub Modules: -// CHECK-NEXT: Implicit Module Fragment '<exported implicit global>' // CHECK-NEXT: Implicit Module Fragment '<implicit global>' //--- b.cpp @@ -45,8 +45,19 @@ int use() { //--- c.cppm export module c; extern "C++" { - // We can't use `export` in an unnamed module. - export int f(); // expected-error {{export declaration can only be used within a module purview}} + export int f(); + int h(); } -extern "C++" export int g(); // expected-error {{export declaration can only be used within a module purview}} +extern "C++" export int g(); + +//--- d.cpp +import c; +int use() { + return f() + g(); +} + +int use_of_nonexported() { + return h(); // expected-error {{missing '#include'; 'h' must be declared before it is used}} + // expected-n...@c.cppm:4 {{declaration here is not visible}} +} diff --git a/clang/test/SemaCXX/modules.cppm b/clang/test/SemaCXX/modules.cppm index 409a6b2f1661e46..41204be76eafa19 100644 --- a/clang/test/SemaCXX/modules.cppm +++ b/clang/test/SemaCXX/modules.cppm @@ -62,10 +62,10 @@ struct S { // language rules right now, but (per personal correspondence between zygoloid // and gdr) is the intent. #if TEST == 1 -export { +export { // expected-note {{export block begins here}} extern "C++" { namespace NestedExport { - export { // expected-error {{export declaration can only be used within a module purview}} + export { // expected-error {{export declaration appears within another export declaration}} int q; } } // namespace NestedExport _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits