Skip to content

Commit

Permalink
[C++20] [Modules] Support to export declarations in language linkage
Browse files Browse the repository at this point in the history
Close #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
  • Loading branch information
ChuanqiXu9 committed Mar 3, 2023
1 parent 87cf39a commit bf52ead
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 39 deletions.
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
20 changes: 17 additions & 3 deletions clang/include/clang/Basic/Module.h
Expand Up @@ -120,11 +120,17 @@ class alignas(8) Module {
/// 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.
Expand Down Expand Up @@ -170,7 +176,15 @@ class alignas(8) Module {

/// 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; }

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Lex/ModuleMap.h
Expand Up @@ -553,6 +553,8 @@ class ModuleMap {
/// 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,
Expand Down
26 changes: 22 additions & 4 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -2276,8 +2276,20 @@ class Sema final {
};
/// 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;
Expand All @@ -2293,11 +2305,17 @@ class Sema final {
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.
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/AST/Decl.cpp
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Frontend/FrontendActions.cpp
Expand Up @@ -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";
}
Expand Down
17 changes: 16 additions & 1 deletion clang/lib/Lex/ModuleMap.cpp
Expand Up @@ -855,14 +855,29 @@ 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)
PendingSubmodules.emplace_back(Result);
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) {
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Sema/Sema.cpp
Expand Up @@ -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;
Expand Down
12 changes: 3 additions & 9 deletions clang/lib/Sema/SemaDeclCXX.cpp
Expand Up @@ -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);
}

Expand All @@ -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;
Expand Down
12 changes: 6 additions & 6 deletions clang/lib/Sema/SemaExprCXX.cpp
Expand Up @@ -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) {
Expand All @@ -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());
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaLookup.cpp
Expand Up @@ -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
Expand Down
50 changes: 39 additions & 11 deletions clang/lib/Sema/SemaModule.cpp
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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();
}
Expand Down
52 changes: 52 additions & 0 deletions clang/test/Modules/export-language-linkage.cppm
@@ -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}}

0 comments on commit bf52ead

Please sign in to comment.