[C++20] [Modules] Support to export declarations in language linkage

Close https://github.com/llvm/llvm-project/issues/60405

See the discussion in the above link for the background.

What the patch does:
- Rename `Module::ModuleKind::GlobalModuleFragment` to
  `Module::ModuleKind::ExplicitGlobalModuleFragment`.
- Add another module kind `ImplicitGlobalModuleFragment` to
  `ModuleKind`.
- Create an implicit global module fragment for the language linkage
  declarations inside a module purview.
    - If the language linkage lives inside the scope of an export decl,
      the created modules is marked as exported to outer modules.
- In fact, Sema will only create at most 2 implicit global module
  fragments to avoid creating a lot of unnecessary modules in the edging
case.

Reviewed By: iains

Differential Revision: https://reviews.llvm.org/D144367
This commit is contained in:
Chuanqi Xu
2023-03-03 10:31:48 +08:00
parent 87cf39aa34
commit bf52ead24c
13 changed files with 167 additions and 39 deletions

View File

@@ -176,6 +176,8 @@ Bug Fixes in This Version
(`#60268 <https://github.com/llvm/llvm-project/issues/60268>`_)
- Fix crash when taking the address of a consteval lambda call operator.
(`#57682 <https://github.com/llvm/llvm-project/issues/57682>`_)
- Clang now support export declarations in the language linkage.
(`#60405 <https://github.com/llvm/llvm-project/issues/60405>`_)
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -120,11 +120,17 @@ public:
/// This is a C++ 20 module partition implementation.
ModulePartitionImplementation,
/// This is a fragment of the global module within some C++ module.
GlobalModuleFragment,
/// This is the explicit Global Module Fragment of a modular TU.
/// As per C++ [module.global.frag].
ExplicitGlobalModuleFragment,
/// This is the private module fragment within some C++ module.
PrivateModuleFragment,
/// This is an implicit fragment of the global module which contains
/// only language linkage declarations (made in the purview of the
/// named module).
ImplicitGlobalModuleFragment,
};
/// The kind of this module.
@@ -170,7 +176,15 @@ public:
/// Does this Module scope describe a fragment of the global module within
/// some C++ module.
bool isGlobalModule() const { return Kind == GlobalModuleFragment; }
bool isGlobalModule() const {
return isExplicitGlobalModule() || isImplicitGlobalModule();
}
bool isExplicitGlobalModule() const {
return Kind == ExplicitGlobalModuleFragment;
}
bool isImplicitGlobalModule() const {
return Kind == ImplicitGlobalModuleFragment;
}
bool isPrivateModule() const { return Kind == PrivateModuleFragment; }

View File

@@ -553,6 +553,8 @@ public:
/// parent.
Module *createGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
Module *Parent = nullptr);
Module *createImplicitGlobalModuleFragmentForModuleUnit(
SourceLocation Loc, bool IsExported, Module *Parent = nullptr);
/// Create a global module fragment for a C++ module interface unit.
Module *createPrivateModuleFragmentForInterfaceUnit(Module *Parent,

View File

@@ -2276,8 +2276,20 @@ private:
};
/// The modules we're currently parsing.
llvm::SmallVector<ModuleScope, 16> ModuleScopes;
/// The global module fragment of the current translation unit.
clang::Module *GlobalModuleFragment = nullptr;
/// The explicit global module fragment of the current translation unit.
/// The explicit Global Module Fragment, as specified in C++
/// [module.global.frag].
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.
clang::Module *TheImplicitGlobalModuleFragment = nullptr;
clang::Module *TheExportedImplicitGlobalModuleFragment = nullptr;
/// Namespace definitions that we will export when they finish.
llvm::SmallPtrSet<const NamespaceDecl*, 8> DeferredExportedNamespaces;
@@ -2293,11 +2305,17 @@ private:
return getCurrentModule() ? getCurrentModule()->isModulePurview() : false;
}
/// Enter the scope of the global module.
/// Enter the scope of the explicit global module fragment.
Module *PushGlobalModuleFragment(SourceLocation BeginLoc);
/// Leave the scope of the global module.
/// Leave the scope of the explicit global module fragment.
void PopGlobalModuleFragment();
/// Enter the scope of an implicit global module fragment.
Module *PushImplicitGlobalModuleFragment(SourceLocation BeginLoc,
bool IsExported);
/// Leave the scope of an implicit global module fragment.
void PopImplicitGlobalModuleFragment();
VisibleModuleSet VisibleModules;
/// Cache for module units which is usable for current module.

View File

@@ -1590,7 +1590,8 @@ Module *Decl::getOwningModuleForLinkage(bool IgnoreLinkage) const {
return M;
case Module::ModuleHeaderUnit:
case Module::GlobalModuleFragment: {
case Module::ExplicitGlobalModuleFragment:
case Module::ImplicitGlobalModuleFragment: {
// External linkage declarations in the global module have no owning module
// for linkage purposes. But internal linkage declarations in the global
// module fragment of a particular module are owned by that module for

View File

@@ -765,8 +765,10 @@ static StringRef ModuleKindName(Module::ModuleKind MK) {
return "Partition Implementation";
case Module::ModuleHeaderUnit:
return "Header Unit";
case Module::GlobalModuleFragment:
case Module::ExplicitGlobalModuleFragment:
return "Global Module Fragment";
case Module::ImplicitGlobalModuleFragment:
return "Implicit Module Fragment";
case Module::PrivateModuleFragment:
return "Private Module Fragment";
}

View File

@@ -855,7 +855,7 @@ Module *ModuleMap::createGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
Module *Parent) {
auto *Result = new Module("<global>", Loc, Parent, /*IsFramework*/ false,
/*IsExplicit*/ true, NumCreatedModules++);
Result->Kind = Module::GlobalModuleFragment;
Result->Kind = Module::ExplicitGlobalModuleFragment;
// If the created module isn't owned by a parent, send it to PendingSubmodules
// to wait for its parent.
if (!Result->Parent)
@@ -863,6 +863,21 @@ Module *ModuleMap::createGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
return Result;
}
Module *ModuleMap::createImplicitGlobalModuleFragmentForModuleUnit(
SourceLocation Loc, bool IsExported, 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++);
Result->Kind = Module::ImplicitGlobalModuleFragment;
return Result;
}
Module *
ModuleMap::createPrivateModuleFragmentForInterfaceUnit(Module *Parent,
SourceLocation Loc) {

View File

@@ -1202,8 +1202,8 @@ void Sema::ActOnEndOfTranslationUnit() {
// A global-module-fragment is only permitted within a module unit.
bool DiagnosedMissingModuleDeclaration = false;
if (!ModuleScopes.empty() &&
ModuleScopes.back().Module->Kind == Module::GlobalModuleFragment) {
if (!ModuleScopes.empty() && ModuleScopes.back().Module->Kind ==
Module::ExplicitGlobalModuleFragment) {
Diag(ModuleScopes.back().BeginLoc,
diag::err_module_declaration_missing_after_global_module_introducer);
DiagnosedMissingModuleDeclaration = true;

View File

@@ -16362,14 +16362,8 @@ 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 =
PushGlobalModuleFragment(ExternLoc);
/// According to [module.reach]p3.2,
/// The declaration in global module fragment is reachable if it is not
/// discarded. And the discarded declaration should be deleted. So it
/// doesn't matter mark the declaration in global module fragment as
/// reachable here.
D->setModuleOwnershipKind(Decl::ModuleOwnershipKind::ReachableWhenImported);
Module *GlobalModule = PushImplicitGlobalModuleFragment(
ExternLoc, /*IsExported=*/D->isInExportDeclContext());
D->setLocalOwningModule(GlobalModule);
}
@@ -16395,7 +16389,7 @@ Decl *Sema::ActOnFinishLinkageSpecification(Scope *S,
// need to pop it.
if (getLangOpts().CPlusPlusModules && getCurrentModule() &&
getCurrentModule()->isGlobalModule() && getCurrentModule()->Parent)
PopGlobalModuleFragment();
PopImplicitGlobalModuleFragment();
PopDeclContext();
return LinkageSpec;

View File

@@ -3023,10 +3023,10 @@ void Sema::DeclareGlobalNewDelete() {
// The implicitly declared "std::bad_alloc" should live in global module
// fragment.
if (GlobalModuleFragment) {
if (TheGlobalModuleFragment) {
getStdBadAlloc()->setModuleOwnershipKind(
Decl::ModuleOwnershipKind::ReachableWhenImported);
getStdBadAlloc()->setLocalOwningModule(GlobalModuleFragment);
getStdBadAlloc()->setLocalOwningModule(TheGlobalModuleFragment);
}
}
if (!StdAlignValT && getLangOpts().AlignedAllocation) {
@@ -3038,10 +3038,10 @@ void Sema::DeclareGlobalNewDelete() {
// The implicitly declared "std::align_val_t" should live in global module
// fragment.
if (GlobalModuleFragment) {
if (TheGlobalModuleFragment) {
AlignValT->setModuleOwnershipKind(
Decl::ModuleOwnershipKind::ReachableWhenImported);
AlignValT->setLocalOwningModule(GlobalModuleFragment);
AlignValT->setLocalOwningModule(TheGlobalModuleFragment);
}
AlignValT->setIntegerType(Context.getSizeType());
@@ -3170,10 +3170,10 @@ void Sema::DeclareGlobalAllocationFunction(DeclarationName Name,
// module all the time. But in the implementation, the global module
// is only meaningful when we're in a module unit. So here we attach
// these allocation functions to global module conditionally.
if (GlobalModuleFragment) {
if (TheGlobalModuleFragment) {
Alloc->setModuleOwnershipKind(
Decl::ModuleOwnershipKind::ReachableWhenImported);
Alloc->setLocalOwningModule(GlobalModuleFragment);
Alloc->setLocalOwningModule(TheGlobalModuleFragment);
}
Alloc->addAttr(VisibilityAttr::CreateImplicit(

View File

@@ -1579,7 +1579,7 @@ bool Sema::isUsableModule(const Module *M) {
// [module.global.frag]p1:
// 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 == GlobalModuleFragment ||
if (M == TheGlobalModuleFragment ||
// 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

View File

@@ -233,7 +233,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc,
}
assert((!getLangOpts().CPlusPlusModules ||
SeenGMF == (bool)this->GlobalModuleFragment) &&
SeenGMF == (bool)this->TheGlobalModuleFragment) &&
"mismatched global module state");
// In C++20, the module-declaration must be the first declaration if there
@@ -358,7 +358,7 @@ Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc,
break;
}
if (!this->GlobalModuleFragment) {
if (!this->TheGlobalModuleFragment) {
ModuleScopes.push_back({});
if (getLangOpts().ModulesLocalVisibility)
ModuleScopes.back().OuterVisibleModules = std::move(VisibleModules);
@@ -408,10 +408,11 @@ Sema::ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc,
// C++20 [basic.link]/2:
// A private-module-fragment shall appear only in a primary module
// interface unit.
switch (ModuleScopes.empty() ? Module::GlobalModuleFragment
switch (ModuleScopes.empty() ? Module::ExplicitGlobalModuleFragment
: ModuleScopes.back().Module->Kind) {
case Module::ModuleMapModule:
case Module::GlobalModuleFragment:
case Module::ExplicitGlobalModuleFragment:
case Module::ImplicitGlobalModuleFragment:
case Module::ModulePartitionImplementation:
case Module::ModulePartitionInterface:
case Module::ModuleHeaderUnit:
@@ -958,25 +959,52 @@ Decl *Sema::ActOnFinishExportDecl(Scope *S, Decl *D, SourceLocation RBraceLoc) {
Module *Sema::PushGlobalModuleFragment(SourceLocation BeginLoc) {
// We shouldn't create new global module fragment if there is already
// one.
if (!GlobalModuleFragment) {
if (!TheGlobalModuleFragment) {
ModuleMap &Map = PP.getHeaderSearchInfo().getModuleMap();
GlobalModuleFragment = Map.createGlobalModuleFragmentForModuleUnit(
TheGlobalModuleFragment = Map.createGlobalModuleFragmentForModuleUnit(
BeginLoc, getCurrentModule());
}
assert(GlobalModuleFragment && "module creation should not fail");
assert(TheGlobalModuleFragment && "module creation should not fail");
// Enter the scope of the global module.
ModuleScopes.push_back({BeginLoc, GlobalModuleFragment,
ModuleScopes.push_back({BeginLoc, TheGlobalModuleFragment,
/*ModuleInterface=*/false,
/*OuterVisibleModules=*/{}});
VisibleModules.setVisible(GlobalModuleFragment, BeginLoc);
VisibleModules.setVisible(TheGlobalModuleFragment, BeginLoc);
return GlobalModuleFragment;
return TheGlobalModuleFragment;
}
void Sema::PopGlobalModuleFragment() {
assert(!ModuleScopes.empty() && getCurrentModule()->isGlobalModule() &&
assert(!ModuleScopes.empty() &&
getCurrentModule()->isExplicitGlobalModule() &&
"left the wrong module scope, which is not global module fragment");
ModuleScopes.pop_back();
}
Module *Sema::PushImplicitGlobalModuleFragment(SourceLocation BeginLoc,
bool IsExported) {
Module **M = IsExported ? &TheExportedImplicitGlobalModuleFragment
: &TheImplicitGlobalModuleFragment;
if (!*M) {
ModuleMap &Map = PP.getHeaderSearchInfo().getModuleMap();
*M = Map.createImplicitGlobalModuleFragmentForModuleUnit(
BeginLoc, IsExported, getCurrentModule());
}
assert(*M && "module creation should not fail");
// Enter the scope of the global module.
ModuleScopes.push_back({BeginLoc, *M,
/*ModuleInterface=*/false,
/*OuterVisibleModules=*/{}});
VisibleModules.setVisible(*M, BeginLoc);
return *M;
}
void Sema::PopImplicitGlobalModuleFragment() {
assert(!ModuleScopes.empty() &&
getCurrentModule()->isImplicitGlobalModule() &&
"left the wrong module scope, which is not global module fragment");
ModuleScopes.pop_back();
}

View File

@@ -0,0 +1,52 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// 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
//--- a.cppm
export module a;
export extern "C++" int foo() { return 43; }
export extern "C++" {
int a();
int b();
int c();
}
export {
extern "C++" void f1();
extern "C++" void f2();
extern "C++" void f3();
}
extern "C++" void unexported();
// CHECK: Sub Modules:
// CHECK-NEXT: Implicit Module Fragment '<exported implicit global>'
// CHECK-NEXT: Implicit Module Fragment '<implicit global>'
//--- b.cpp
import a;
int use() {
a();
b();
c();
f1();
f2();
f3();
unexported(); // expected-error {{missing '#include'; 'unexported' must be declared before it is used}}
// expected-note@a.cppm:15 {{declaration here is not visible}}
return foo();
}
//--- 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}}
}
extern "C++" export int g(); // expected-error {{export declaration can only be used within a module purview}}