[clang] [C++20] [Modules] Implementing Eliding Unreachable Decls of GMF in ASTWriter (PR #76930)

Chuanqi Xu via cfe-commits cfe-commits at lists.llvm.org
Thu Jan 4 01:41:06 PST 2024


https://github.com/ChuanqiXu9 created https://github.com/llvm/llvm-project/pull/76930

This was a patch to try to implement eliding unreachable decls in GMF in ASTWriter. It was developed a half year ago and I just rebased it but I did't fix the failing test. It ran well. 

The core idea of the patch is that we can implement the idea **reachable** in ASTWriter naturally. 

The secret is that we skip writing GMF initially (generally we will write decls from the top to the bottom) and we start to write the declarations from module purview. Then we will only write the declarations in GMF if it is mentioned during the writing process. So the unreachable decls won't be written natually.

The experience in implementing this patch is pretty smooth and the tests from the spec can be passed. I felt this should be the natural way to implement this feature.

The only one and big problem is that we didn't implement the formal semantics in the spec in this way : |

>From b9a03912276d25ff819a755bef4ee72d64ce1480 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <yedeng.yd at linux.alibaba.com>
Date: Thu, 4 Jan 2024 17:24:23 +0800
Subject: [PATCH] [C++20] [Modules] Implementing Eliding Unreachable Decls of
 GMF in ASTWriter

---
 clang/include/clang/AST/DeclBase.h            |  16 +-
 clang/include/clang/Basic/LangOptions.def     |   1 +
 clang/include/clang/Driver/Options.td         |   7 +
 clang/include/clang/Serialization/ASTWriter.h |  24 +++
 clang/lib/Sema/SemaModule.cpp                 |   6 +-
 clang/lib/Serialization/ASTReaderDecl.cpp     |   3 +
 clang/lib/Serialization/ASTWriter.cpp         | 160 +++++++++++++++++-
 clang/lib/Serialization/ASTWriterDecl.cpp     |   2 +
 .../basic.scope/basic.scope.namespace/p2.cpp  |   6 +-
 .../module.glob.frag/cxx20-10-4-ex2.cppm      |  72 ++++++++
 clang/test/CXX/module/module.import/p2.cpp    |   7 +-
 .../test/CodeGenCXX/module-intializer-pmf.cpp |   6 +-
 clang/test/CodeGenCXX/module-intializer.cpp   |  39 ++---
 clang/test/Modules/abi-tag.cppm               |  69 ++++++++
 clang/test/Modules/concept.cppm               |   2 +
 .../explicitly-specialized-template.cpp       |   2 +-
 .../inconsistent-deduction-guide-linkage.cppm |   6 +
 clang/test/Modules/named-modules-adl-2.cppm   |   2 +
 clang/test/Modules/named-modules-adl.cppm     |   2 +
 clang/test/Modules/polluted-operator.cppm     |  11 +-
 clang/test/Modules/pr58716.cppm               |   2 +
 clang/test/Modules/pr60775.cppm               |   4 +
 clang/test/Modules/pr62589.cppm               |   3 +
 clang/test/Modules/preferred_name.cppm        |   2 +
 .../redundant-template-default-arg3.cpp       |   9 +
 .../template-function-specialization.cpp      |   6 +-
 26 files changed, 416 insertions(+), 53 deletions(-)
 create mode 100644 clang/test/CXX/module/module.glob.frag/cxx20-10-4-ex2.cppm
 create mode 100644 clang/test/Modules/abi-tag.cppm

diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 10dcbdb262d84e..523b930d59645a 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -233,9 +233,12 @@ class alignas(8) Decl {
 
     /// This declaration has an owning module, but is only visible to
     /// lookups that occur within that module.
-    /// The discarded declarations in global module fragment belongs
-    /// to this group too.
-    ModulePrivate
+    ModulePrivate,
+
+    /// This declaration is part of a Global Module Fragment, it is permitted
+    /// to discard it and therefore it is not reachable or visible to importers
+    /// of the named module of which the GMF is part.
+    ModuleDiscardable
   };
 
 protected:
@@ -658,9 +661,10 @@ class alignas(8) Decl {
   /// Whether this declaration comes from another module unit.
   bool isInAnotherModuleUnit() const;
 
-  /// FIXME: Implement discarding declarations actually in global module
-  /// fragment. See [module.global.frag]p3,4 for details.
-  bool isDiscardedInGlobalModuleFragment() const { return false; }
+  /// See [module.global.frag]p3,4 for details.
+  bool isDiscardedInGlobalModuleFragment() const {
+    return getModuleOwnershipKind() == ModuleOwnershipKind::ModuleDiscardable;
+  }
 
   /// Return true if this declaration has an attribute which acts as
   /// definition of the entity, such as 'alias' or 'ifunc'.
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 21abc346cf17ac..97ba8147bdfbb5 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -178,6 +178,7 @@ LANGOPT(BuiltinHeadersInSystemModules, 1, 0, "builtin headers belong to system m
 BENIGN_ENUM_LANGOPT(CompilingModule, CompilingModuleKind, 3, CMK_None,
                     "compiling a module interface")
 BENIGN_LANGOPT(CompilingPCH, 1, 0, "building a pch")
+LANGOPT(DiscardGMFDecls   , 1, 1, "Discard unreachable decls in GMF")
 BENIGN_LANGOPT(BuildingPCHWithObjectFile, 1, 0, "building a pch which has a corresponding object file")
 BENIGN_LANGOPT(CacheGeneratedPCH, 1, 0, "cache generated PCH files in memory")
 BENIGN_LANGOPT(PCHInstantiateTemplates, 1, 0, "instantiate templates while building a PCH")
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 2b93ddf033499c..127434550276e0 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2970,6 +2970,13 @@ defm prebuilt_implicit_modules : BoolFOption<"prebuilt-implicit-modules",
   PosFlag<SetTrue, [], [ClangOption], "Look up implicit modules in the prebuilt module path">,
   NegFlag<SetFalse>, BothFlags<[], [ClangOption, CC1Option]>>;
 
+defm discarding_gmf_decls : BoolFOption<"discarding-gmf-decls",
+  LangOpts<"DiscardGMFDecls">, DefaultTrue,
+  PosFlag<SetTrue>,
+  NegFlag<SetFalse, [], [ClangOption],
+          "Disable to discard unreachable decls in global module fragment">,
+  BothFlags<[], [ClangOption, CC1Option]>>;
+
 def fmodule_output_EQ : Joined<["-"], "fmodule-output=">,
   Flags<[NoXarchOption]>, Visibility<[ClangOption, CC1Option]>,
   HelpText<"Save intermediate module file results when compiling a standard C++ module unit.">;
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index de69f99003d827..fdd142ac7e20da 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -467,6 +467,22 @@ class ASTWriter : public ASTDeserializationListener,
   std::vector<SourceRange> NonAffectingRanges;
   std::vector<SourceLocation::UIntTy> NonAffectingOffsetAdjustments;
 
+  /// Mark ModuleDiscardable Decl D and its file scope top level declaration D
+  /// as reachable. This is a no-op if D is not ModuleDiscardable. We'll mark
+  /// the file scope top level declaration D as reachable too. Otherwise, it is
+  /// problematic if some parts of a decl is discarded in some TU and these
+  /// parts are not discarded in other TUs. This is an ODR violation. So if a
+  /// sub-decl is reachable, the top level decl and all of its children should
+  /// be reachable too.
+  void MarkDeclReachable(const Decl *D);
+  /// A helper to IsDeclModuleDiscardable. There are special declarations which
+  /// may not be referenced directly. But they can't be discarded if their
+  /// correspond decls are reachable. e.g., the deduction guides decls.
+  bool IsSpecialDeclNotDiscardable(Decl *D);
+  /// Callbacks to mark special decls as reachable once their corresponding
+  /// decls become reachable.
+  llvm::DenseMap<Decl *, llvm::SmallVector<Decl *, 8>> ReachableMarkerCallbacks;
+
   /// Collects input files that didn't affect compilation of the current module,
   /// and initializes data structures necessary for leaving those files out
   /// during \c SourceManager serialization.
@@ -792,6 +808,14 @@ class ASTWriter : public ASTDeserializationListener,
     return WritingModule && WritingModule->isNamedModule();
   }
 
+  /// Whether or not D is module discardable. Besides the case that D is marked
+  /// not module discardable explicitly, `IsDeclModuleDiscardable` will return
+  /// false if:
+  /// - The file scope top level declaration of D is not module discardable.
+  /// - D is a deduction guide for another template declaration TD and TD is not
+  /// module discardable.
+  bool IsDeclModuleDiscardable(const Decl *D);
+
 private:
   // ASTDeserializationListener implementation
   void ReaderInitialized(ASTReader *Reader) override;
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index ed7f626971f345..ac68d0e5106d7a 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -88,7 +88,11 @@ Sema::ActOnGlobalModuleFragmentDecl(SourceLocation ModuleLoc) {
   // within the module unit.
   //
   // So the declations in the global module shouldn't be visible by default.
-  TU->setModuleOwnershipKind(Decl::ModuleOwnershipKind::ReachableWhenImported);
+  if (getLangOpts().DiscardGMFDecls)
+    TU->setModuleOwnershipKind(Decl::ModuleOwnershipKind::ModuleDiscardable);
+  else
+    TU->setModuleOwnershipKind(
+        Decl::ModuleOwnershipKind::ReachableWhenImported);
   TU->setLocalOwningModule(GlobalModule);
 
   // FIXME: Consider creating an explicit representation of this declaration.
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 547eb77930b4ee..a76740d0898a8f 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -656,6 +656,9 @@ void ASTDeclReader::VisitDecl(Decl *D) {
     case Decl::ModuleOwnershipKind::ReachableWhenImported:
     case Decl::ModuleOwnershipKind::ModulePrivate:
       break;
+
+    case Decl::ModuleOwnershipKind::ModuleDiscardable:
+      llvm_unreachable("We should never read module discardable decls");
     }
 
     D->setModuleOwnershipKind(ModuleOwnership);
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 78939bfd533ffa..4823cbd5dd4f21 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -2994,7 +2994,8 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) {
     // Emit the initializers, if any.
     RecordData Inits;
     for (Decl *D : Context->getModuleInitializers(Mod))
-      Inits.push_back(GetDeclRef(D));
+      if (!IsDeclModuleDiscardable(D))
+        Inits.push_back(GetDeclRef(D));
     if (!Inits.empty())
       Stream.EmitRecord(SUBMODULE_INITIALIZERS, Inits);
 
@@ -3171,6 +3172,13 @@ uint64_t ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   uint64_t Offset = Stream.GetCurrentBitNo();
   SmallVector<uint32_t, 128> KindDeclPairs;
   for (const auto *D : DC->decls()) {
+    if (IsDeclModuleDiscardable(D)) {
+      if (DC->isFileContext())
+        continue;
+      else
+        MarkDeclReachable(D);
+    }
+
     KindDeclPairs.push_back(D->getKind());
     KindDeclPairs.push_back(GetDeclRef(D));
   }
@@ -3819,9 +3827,12 @@ class ASTDeclContextNameLookupTrait {
   template<typename Coll>
   data_type getData(const Coll &Decls) {
     unsigned Start = DeclIDs.size();
-    for (NamedDecl *D : Decls) {
-      DeclIDs.push_back(
-          Writer.GetDeclRef(getDeclForLocalLookup(Writer.getLangOpts(), D)));
+    for (NamedDecl *ND : Decls) {
+      auto *D = getDeclForLocalLookup(Writer.getLangOpts(), ND);
+      if (Writer.IsDeclModuleDiscardable(D))
+        continue;
+
+      DeclIDs.push_back(Writer.GetDeclRef(D));
     }
     return std::make_pair(Start, DeclIDs.size());
   }
@@ -3976,6 +3987,15 @@ ASTWriter::GenerateNameLookupTable(const DeclContext *ConstDC,
         isLookupResultEntirelyExternal(Result, DC))
       continue;
 
+    if (!DC->isFileContext() && !DoneWritingDeclsAndTypes) {
+      for (auto *D : Result.getLookupResult())
+        if (IsDeclModuleDiscardable(D))
+          MarkDeclReachable(D);
+    } else if (llvm::all_of(Result.getLookupResult(), [this](NamedDecl *D) {
+            return IsDeclModuleDiscardable(D);
+          }))
+        continue;
+
     // We also skip empty results. If any of the results could be external and
     // the currently available results are empty, then all of the results are
     // external and we skip it above. So the only way we get here with an empty
@@ -4165,7 +4185,7 @@ uint64_t ASTWriter::WriteDeclContextVisibleBlock(ASTContext &Context,
       }
 
       for (NamedDecl *ND : Result)
-        if (!ND->isFromASTFile())
+        if (!ND->isFromASTFile() && !IsDeclModuleDiscardable(ND))
           GetDeclRef(ND);
     }
 
@@ -4903,7 +4923,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
   const TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
   SmallVector<uint32_t, 128> NewGlobalKindDeclPairs;
   for (const auto *D : TU->noload_decls()) {
-    if (!D->isFromASTFile()) {
+    if (!D->isFromASTFile() && !IsDeclModuleDiscardable(D)) {
       NewGlobalKindDeclPairs.push_back(D->getKind());
       NewGlobalKindDeclPairs.push_back(GetDeclRef(D));
     }
@@ -4955,9 +4975,9 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
   // Make sure visible decls, added to DeclContexts previously loaded from
   // an AST file, are registered for serialization. Likewise for template
   // specializations added to imported templates.
-  for (const auto *I : DeclsToEmitEvenIfUnreferenced) {
-    GetDeclRef(I);
-  }
+  for (const auto *I : DeclsToEmitEvenIfUnreferenced)
+    if (!IsDeclModuleDiscardable(I))
+      GetDeclRef(I);
 
   // Make sure all decls associated with an identifier are registered for
   // serialization, if we're storing decls with identifiers.
@@ -5280,6 +5300,7 @@ void ASTWriter::WriteDeclUpdatesBlocks(RecordDataImpl &OffsetsRecord) {
       case UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION:
       case UPD_CXX_ADDED_ANONYMOUS_NAMESPACE:
         assert(Update.getDecl() && "no decl to add?");
+        MarkDeclReachable(Update.getDecl());
         Record.push_back(GetDeclRef(Update.getDecl()));
         break;
 
@@ -5419,6 +5440,7 @@ void ASTWriter::WriteDeclUpdatesBlocks(RecordDataImpl &OffsetsRecord) {
       Record.AddVarDeclInit(VD);
     }
 
+    MarkDeclReachable(D);
     OffsetsRecord.push_back(GetDeclRef(D));
     OffsetsRecord.push_back(Record.Emit(DECL_UPDATES));
   }
@@ -5679,7 +5701,125 @@ TypeID ASTWriter::getTypeID(QualType T) const {
   });
 }
 
+bool ASTWriter::IsSpecialDeclNotDiscardable(Decl *D) {
+  assert(D->isDiscardedInGlobalModuleFragment());
+
+  /// Currently, the only special decl is the deduction guide.
+  if (auto *ND = dyn_cast<NamedDecl>(D)) {
+    DeclarationName Name = ND->getDeclName();
+    if (TemplateDecl *TD = Name.getCXXDeductionGuideTemplate()) {
+      if (!IsDeclModuleDiscardable(TD)) {
+        MarkDeclReachable(D);
+        return true;
+      }
+
+      ReachableMarkerCallbacks[TD].push_back(D);
+    }
+  }
+
+  // FIXME:
+  //
+  // There is a wide used pattern in libstdc++:
+  //
+  // namespace std
+  // {
+  //   inline namespace __cxx11 __attribute__((__abi_tag__ ("cxx11"))) { }
+  // }
+  // namespace __gnu_cxx
+  // {
+  //   inline namespace __cxx11 __attribute__((__abi_tag__ ("cxx11"))) { }
+  // }
+  // ...
+  // namespace std {
+  //   some declarations for STL.
+  //   ...
+  //   namespace __cxx11 {
+  //     some declarations for STL.
+  //   }
+  // }
+  //
+  // Then in a real project, we observed false-positive ODR violations
+  // since some module units discard `__gnu_cxx::__cxx11` namespace while
+  // other module units don't discard `__gnu_cxx::__cxx11` namespace.
+  //
+  // This may imply that the ODR checking process is context sensitive.
+  // That said, the same type in different module units can be considered to be
+  // different if some module units discard the unused `__gnu_cxx::__cxx11` namespace
+  // while other module units don't. This is incorrect.
+  //
+  // This is a workaround to make things happen but we indeed to fix the ODR checking
+  // process indeed.
+  if (auto *ND = dyn_cast<NamedDecl>(D);
+      ND && ND->getAttr<AbiTagAttr>()) {
+    MarkDeclReachable(D);
+    return true;
+  }
+
+  return false;
+}
+
+bool ASTWriter::IsDeclModuleDiscardable(const Decl *ConstD) {
+  Decl *D = const_cast<Decl *>(ConstD);
+
+  if (!D->isDiscardedInGlobalModuleFragment())
+    return false;
+
+  // The Translation Unit should never be module discardable.
+  if (!D->getDeclContext()) {
+    assert(isa<TranslationUnitDecl>(D));
+    return false;
+  }
+
+  if (IsSpecialDeclNotDiscardable(D))
+    return false;
+
+  const DeclContext *DC = D->getNonTransparentDeclContext();
+  while (DC && DC->getParent() &&
+         !DC->getParent()->getNonTransparentContext()->isFileContext())
+    DC = DC->getParent()->getNonTransparentContext();
+
+  assert(DC && "Why is the decl not covered by file context?");
+  if (!DC->isFileContext() && !cast<Decl>(DC)->isDiscardedInGlobalModuleFragment()) {
+    MarkDeclReachable(D);
+    return false;
+  }
+
+  return true;
+}
+
+void ASTWriter::MarkDeclReachable(const Decl *ConstD) {
+  Decl *D = const_cast<Decl *>(ConstD);
+
+  if (!D || !D->isDiscardedInGlobalModuleFragment())
+    return;
+
+  D->setModuleOwnershipKind(Decl::ModuleOwnershipKind::ReachableWhenImported);
+  if (D->getNonTransparentDeclContext()->isFileContext()) {
+    // Update the decl contexts so that we can still find the decl with name
+    // lookup.
+    UpdatedDeclContexts.insert(D->getNonTransparentDeclContext());
+  }
+
+  auto Iter = ReachableMarkerCallbacks.find(D);
+  if (Iter != ReachableMarkerCallbacks.end()) {
+    for (Decl *ToBeMarked : Iter->second) {
+      MarkDeclReachable(ToBeMarked);
+      GetDeclRef(ToBeMarked);
+    }
+    ReachableMarkerCallbacks.erase(D);
+  }
+
+  DeclContext *DC = D->getNonTransparentDeclContext();
+  while (DC && DC->getParent() &&
+         !DC->getParent()->getNonTransparentContext()->isFileContext())
+    DC = DC->getParent()->getNonTransparentContext();
+
+  if (DC && !DC->isFileContext())
+    MarkDeclReachable(cast<Decl>(DC));
+}
+
 void ASTWriter::AddDeclRef(const Decl *D, RecordDataImpl &Record) {
+  MarkDeclReachable(D);
   Record.push_back(GetDeclRef(D));
 }
 
@@ -5695,6 +5835,8 @@ DeclID ASTWriter::GetDeclRef(const Decl *D) {
   if (D->isFromASTFile())
     return D->getGlobalID();
 
+  assert(!D->isDiscardedInGlobalModuleFragment() && "We shouldn't write discarded decl.\n");
+
   assert(!(reinterpret_cast<uintptr_t>(D) & 0x01) && "Invalid decl pointer");
   DeclID &ID = DeclIDs[D];
   if (ID == 0) {
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 9e3299f0491848..cd605cfbadc11b 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1987,6 +1987,8 @@ void ASTDeclWriter::VisitRedeclarable(Redeclarable<T> *D) {
     //
     // FIXME: This is not correct; when we reach an imported declaration we
     // won't emit its previous declaration.
+    Writer.MarkDeclReachable(D->getPreviousDecl());
+    Writer.MarkDeclReachable(MostRecent);
     (void)Writer.GetDeclRef(D->getPreviousDecl());
     (void)Writer.GetDeclRef(MostRecent);
   } else {
diff --git a/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp b/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp
index d69db40062dae9..5e48300244655e 100644
--- a/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp
+++ b/clang/test/CXX/basic/basic.scope/basic.scope.namespace/p2.cpp
@@ -30,7 +30,6 @@ module;
 
 void test_early() {
   in_header = 1; // expected-error {{use of undeclared identifier 'in_header'}}
-  // expected-note@* {{not visible}}
 
   global_module_fragment = 1; // expected-error {{use of undeclared identifier 'global_module_fragment'}}
 
@@ -53,10 +52,9 @@ import A;
 #endif
 
 void test_late() {
-  in_header = 1; // expected-error {{missing '#include "foo.h"'; 'in_header' must be declared before it is used}}
-  // expected-note@* {{not visible}}
+  in_header = 1; // expected-error {{use of undeclared identifier 'in_header'}}
 
-  global_module_fragment = 1; // expected-error {{missing '#include'; 'global_module_fragment' must be declared before it is used}}
+  global_module_fragment = 1; // expected-error {{use of undeclared identifier 'global_module_fragment'}}
 
   exported = 1;
 
diff --git a/clang/test/CXX/module/module.glob.frag/cxx20-10-4-ex2.cppm b/clang/test/CXX/module/module.glob.frag/cxx20-10-4-ex2.cppm
new file mode 100644
index 00000000000000..592f926e391e96
--- /dev/null
+++ b/clang/test/CXX/module/module.glob.frag/cxx20-10-4-ex2.cppm
@@ -0,0 +1,72 @@
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 %t/std-10-4-ex2-interface.cppm -emit-module-interface \
+// RUN:     -o %t/M.pcm -Wno-unused-value
+// RUN: %clang_cc1 -std=c++20 %t/std-10-4-ex2-implementation.cpp -fmodule-file=M=%t/M.pcm \
+// RUN:     -fsyntax-only -verify
+//
+// RUN: %clang_cc1 -std=c++20 %t/std-10-4-ex2-interface.cppm -emit-module-interface \
+// RUN:     -o %t/M.pcm -Wno-unused-value -fno-discarding-gmf-decls
+// RUN: %clang_cc1 -std=c++20 %t/std-10-4-ex2-implementation.cpp -fmodule-file=M=%t/M.pcm \
+// RUN:     -fsyntax-only -verify -fno-discarding-gmf-decls -DNO_DISCARD
+
+//--- std-10-4-ex2.h
+
+namespace N {
+struct X {};
+int d();
+int e();
+inline int f(X, int = d()) { return e(); }
+int g(X);
+int h(X);
+} // namespace N
+
+//--- std-10-4-ex2-interface.cppm
+
+module;
+
+#include "std-10-4-ex2.h"
+
+export module M;
+
+template <typename T> int use_f() {
+  N::X x;           // N::X, N, and  ::  are decl-reachable from use_f
+  return f(x, 123); // N::f is decl-reachable from use_f,
+                    // N::e is indirectly decl-reachable from use_f
+                    //   because it is decl-reachable from N::f, and
+                    // N::d is decl-reachable from use_f
+                    //   because it is decl-reachable from N::f
+                    //   even though it is not used in this call
+}
+
+template <typename T> int use_g() {
+  N::X x;             // N::X, N, and :: are decl-reachable from use_g
+  return g((T(), x)); // N::g is not decl-reachable from use_g
+}
+
+template <typename T> int use_h() {
+  N::X x;             // N::X, N, and :: are decl-reachable from use_h
+  return h((T(), x)); // N::h is not decl-reachable from use_h, but
+                      // N::h is decl-reachable from use_h<int>
+}
+
+int k = use_h<int>();
+// use_h<int> is decl-reachable from k, so
+// N::h is decl-reachable from k
+
+//--- std-10-4-ex2-implementation.cpp
+#ifdef NO_DISCARD
+// expected-no-diagnostics
+#endif
+
+module M;
+
+int a = use_f<int>();
+int b = use_g<int>();
+#ifndef NO_DISCARD
+// expected-error at std-10-4-ex2-interface.cppm:20 {{use of undeclared identifier 'g'}}
+// expected-note at -3 {{in instantiation of function template specialization 'use_g<int>' requested here}}
+#endif
+int c = use_h<int>();
diff --git a/clang/test/CXX/module/module.import/p2.cpp b/clang/test/CXX/module/module.import/p2.cpp
index ef6006811e7763..21068a0fa30d5d 100644
--- a/clang/test/CXX/module/module.import/p2.cpp
+++ b/clang/test/CXX/module/module.import/p2.cpp
@@ -67,13 +67,14 @@ void test() {
 module;
 class A{};
 export module C;
-void test() {
+export void C() {
   A a;
 }
 
 //--- UseGlobal.cpp
 import C;
 void test() {
-  A a; // expected-error {{'A' must be declared before it is used}}
-       // expected-note at Global.cppm:2 {{declaration here is not visible}}
+  A a; // expected-error {{missing '#include'; 'A' must be declared before it is used}}
+       // expected-note@* {{declaration here is not visible}}
+  C();
 }
diff --git a/clang/test/CodeGenCXX/module-intializer-pmf.cpp b/clang/test/CodeGenCXX/module-intializer-pmf.cpp
index 7ab4a2e2bd78a1..8352c49fc0e319 100644
--- a/clang/test/CodeGenCXX/module-intializer-pmf.cpp
+++ b/clang/test/CodeGenCXX/module-intializer-pmf.cpp
@@ -20,6 +20,8 @@ export struct InMod {
 
 export InMod IM;
 
+export using ::G;
+
 module :private;
 
 struct InPMF {
@@ -28,12 +30,12 @@ struct InPMF {
 
 InPMF P;
 
-// CHECK: define internal void @__cxx_global_var_init
-// CHECK: call {{.*}} @_ZN4GlobC1Ev
 // CHECK: define internal void @__cxx_global_var_init
 // CHECK: call {{.*}} @_ZNW6HasPMF5InPMFC1Ev
 // CHECK: define internal void @__cxx_global_var_init
 // CHECK: call {{.*}} @_ZNW6HasPMF5InModC1Ev
+// CHECK: define internal void @__cxx_global_var_init
+// CHECK: call {{.*}} @_ZN4GlobC1Ev
 // CHECK: define void @_ZGIW6HasPMF
 // CHECK: store i8 1, ptr @_ZGIW6HasPMF__in_chrg
 // CHECK: call void @__cxx_global_var_init
diff --git a/clang/test/CodeGenCXX/module-intializer.cpp b/clang/test/CodeGenCXX/module-intializer.cpp
index d365d180ac59d1..6baf9670da23c0 100644
--- a/clang/test/CodeGenCXX/module-intializer.cpp
+++ b/clang/test/CodeGenCXX/module-intializer.cpp
@@ -31,19 +31,6 @@
 // RUN: -fmodule-file=M=M.pcm -S -emit-llvm  -o - \
 // RUN: | FileCheck %s --check-prefix=CHECK-IMPL
 
-// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 N.cpp -S -emit-llvm \
-// RUN:   -o - | FileCheck %s --check-prefix=CHECK-N
-
-// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 O.cpp -S -emit-llvm \
-// RUN:   -o - | FileCheck %s --check-prefix=CHECK-O
-
-// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 M-part.cpp -S -emit-llvm \
-// RUN:   -o - | FileCheck %s --check-prefix=CHECK-P
-
-// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 M.cpp \
-// RUN:   -fmodule-file=N.pcm -fmodule-file=O=O.pcm -fmodule-file=M:Part=M-part.pcm \
-// RUN:   -S -emit-llvm -o - | FileCheck %s --check-prefix=CHECK-M
-
 //--- N-h.h
 
 struct Oink {
@@ -59,16 +46,18 @@ module;
 
 export module N;
 
+export using ::Hog;
+
 export struct Quack {
   Quack(){};
 };
 
 export Quack Duck;
 
-// CHECK-N: define internal void @__cxx_global_var_init
-// CHECK-N: call {{.*}} @_ZN4OinkC1Ev
 // CHECK-N: define internal void @__cxx_global_var_init
 // CHECK-N: call {{.*}} @_ZNW1N5QuackC1Ev
+// CHECK-N: define internal void @__cxx_global_var_init
+// CHECK-N: call {{.*}} @_ZN4OinkC1Ev
 // CHECK-N: define void @_ZGIW1N
 // CHECK-N: store i8 1, ptr @_ZGIW1N__in_chrg
 // CHECK-N: call void @__cxx_global_var_init
@@ -89,16 +78,19 @@ module;
 
 export module O;
 
+export using ::Cat;
+
 export struct Bark {
   Bark(){};
 };
 
 export Bark Dog;
 
-// CHECK-O: define internal void @__cxx_global_var_init
-// CHECK-O: call {{.*}} @_ZN4MeowC2Ev
+
 // CHECK-O: define internal void @__cxx_global_var_init
 // CHECK-O: call {{.*}} @_ZNW1O4BarkC1Ev
+// CHECK-O: define internal void @__cxx_global_var_init
+// CHECK-O: call {{.*}} @_ZN4MeowC2Ev
 // CHECK-O: define void @_ZGIW1O
 // CHECK-O: store i8 1, ptr @_ZGIW1O__in_chrg
 // CHECK-O: call void @__cxx_global_var_init
@@ -119,16 +111,18 @@ module;
 
 module M:Part;
 
+using ::Frog;
+
 struct Squawk {
   Squawk(){};
 };
 
 Squawk parrot;
 
-// CHECK-P: define internal void @__cxx_global_var_init
-// CHECK-P: call {{.*}} @_ZN5CroakC1Ev
 // CHECK-P: define internal void @__cxx_global_var_init
 // CHECK-P: call {{.*}} @_ZNW1M6SquawkC1Ev
+// CHECK-P: define internal void @__cxx_global_var_init
+// CHECK-P: call {{.*}} @_ZN5CroakC1Ev
 // CHECK-P: define void @_ZGIW1MWP4Part
 // CHECK-P: store i8 1, ptr @_ZGIW1MWP4Part__in_chrg
 // CHECK-P: call void @__cxx_global_var_init
@@ -152,6 +146,8 @@ import N;
 export import O;
 import :Part;
 
+using ::Cow;
+
 export struct Baa {
   int x;
   Baa(){};
@@ -161,10 +157,11 @@ export struct Baa {
 
 export Baa Sheep(10);
 
-// CHECK-M: define internal void @__cxx_global_var_init
-// CHECK-M: call {{.*}} @_ZN3MooC1Ev
+
 // CHECK-M: define internal void @__cxx_global_var_init
 // CHECK-M: call {{.*}} @_ZNW1M3BaaC1Ei
+// CHECK-M: define internal void @__cxx_global_var_init
+// CHECK-M: call {{.*}} @_ZN3MooC1Ev
 // CHECK-M: declare void @_ZGIW1O()
 // CHECK-M: declare void @_ZGIW1N()
 // CHECK-M: declare void @_ZGIW1MWP4Part()
diff --git a/clang/test/Modules/abi-tag.cppm b/clang/test/Modules/abi-tag.cppm
new file mode 100644
index 00000000000000..078ed2e395a310
--- /dev/null
+++ b/clang/test/Modules/abi-tag.cppm
@@ -0,0 +1,69 @@
+// Tests that the namespace with abi tag attribute won't get discarded.
+// The pattern is widely used in libstdc++.
+//
+// RUN: rm -rf %t
+// RUN: mkdir %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 %t/m.cppm -emit-module-interface -o %t/m.pcm
+// RUNX: %clang_cc1 -std=c++20 %t/m.pcm -S -emit-llvm -o - | FileCheck %t/m.cppm
+
+//--- foo.h
+#pragma GCC system_header
+
+namespace std {
+    inline namespace __cxx11 __attribute__((__abi_tag__ ("tag_name"))) { }
+}
+
+namespace __gnu_cxx {
+    inline namespace __cxx11 __attribute__((__abi_tag__ ("tag_name"))) { }
+}
+
+namespace std {
+    namespace __cxx11 {
+        struct C { int x; };
+    }
+}
+
+std::C foo() { return {3}; }
+
+//--- m.cppm
+module;
+#include "foo.h"
+export module m;
+export using ::foo;
+
+// CHECK: define{{.*}}@_Z3fooB8tag_namev
+
+//--- bar.h
+#pragma GCC system_header
+
+namespace std {
+    inline namespace __cxx11 __attribute__((__abi_tag__ ("tag_name"))) { }
+}
+
+namespace __gnu_cxx {
+    inline namespace __cxx11 __attribute__((__abi_tag__ ("tag_name"))) { }
+}
+
+namespace __gnu_cxx {
+    namespace __cxx11 {
+        template <class C>
+        struct traits {
+            typedef C type;
+        };
+    }
+}
+
+namespace std {
+    template <class C>
+    struct vec {
+        typedef traits<C>::type type;
+    };
+}
+
+//--- n.cppm
+module;
+#include "bar.h"
+export module n;
+export using ::foo;
diff --git a/clang/test/Modules/concept.cppm b/clang/test/Modules/concept.cppm
index 0e85a46411a544..ec17a50b87dc6b 100644
--- a/clang/test/Modules/concept.cppm
+++ b/clang/test/Modules/concept.cppm
@@ -63,12 +63,14 @@ struct __fn {
 module;
 #include "foo.h"
 export module A;
+export using ::__fn;
 
 //--- B.cppm
 module;
 #include "foo.h"
 export module B;
 import A;
+export using ::__fn;
 
 #ifdef DIFFERENT
 // expected-error at foo.h:41 {{'__fn::operator()' from module 'A.<global>' is not present in definition of '__fn' provided earlier}}
diff --git a/clang/test/Modules/explicitly-specialized-template.cpp b/clang/test/Modules/explicitly-specialized-template.cpp
index 89677254ea739a..c3aa5dd15ca7bc 100644
--- a/clang/test/Modules/explicitly-specialized-template.cpp
+++ b/clang/test/Modules/explicitly-specialized-template.cpp
@@ -39,7 +39,7 @@ class X {
 
 //--- Use.cpp
 import X;
-foo<int> f; // expected-error {{'foo' must be declared before it is used}}
+foo<int> f; // expected-error {{missing '#include "foo.h"'; 'foo' must be declared before it is used}}
             // expected-note@* {{declaration here is not visible}}
 int bar() {
   X<int> x;
diff --git a/clang/test/Modules/inconsistent-deduction-guide-linkage.cppm b/clang/test/Modules/inconsistent-deduction-guide-linkage.cppm
index abcbec07f97de0..6150fe09c2f6db 100644
--- a/clang/test/Modules/inconsistent-deduction-guide-linkage.cppm
+++ b/clang/test/Modules/inconsistent-deduction-guide-linkage.cppm
@@ -19,6 +19,9 @@ module;
 
 #include "C.h"
 export module B;
+export namespace foo {
+  using foo::bar;
+}
 
 //--- C.h
 namespace foo {
@@ -33,6 +36,9 @@ namespace foo {
 module;
 #include "E.h"
 export module D;
+export namespace foo {
+  using foo::bar;
+}
 
 //--- D-part.cppm
 export module D:part;
diff --git a/clang/test/Modules/named-modules-adl-2.cppm b/clang/test/Modules/named-modules-adl-2.cppm
index 655acfcd93f69a..dcacb8f4b153d2 100644
--- a/clang/test/Modules/named-modules-adl-2.cppm
+++ b/clang/test/Modules/named-modules-adl-2.cppm
@@ -31,6 +31,8 @@ void b() {
 	a(s());
 }
 
+export using ::operator+;
+
 //--- c.cppm
 // expected-no-diagnostics
 export module c;
diff --git a/clang/test/Modules/named-modules-adl.cppm b/clang/test/Modules/named-modules-adl.cppm
index d5133ef367265a..6ad3b35aeff9f2 100644
--- a/clang/test/Modules/named-modules-adl.cppm
+++ b/clang/test/Modules/named-modules-adl.cppm
@@ -25,6 +25,8 @@ void a(T x) {
 	n::s() + x;
 }
 
+export using n::operator+;
+
 //--- b.cppm
 // expected-no-diagnostics
 export module b;
diff --git a/clang/test/Modules/polluted-operator.cppm b/clang/test/Modules/polluted-operator.cppm
index b24464aa6ad21e..83f6cdfc1b42d3 100644
--- a/clang/test/Modules/polluted-operator.cppm
+++ b/clang/test/Modules/polluted-operator.cppm
@@ -44,6 +44,10 @@ module;
 #include "foo.h"
 #include "bar.h"
 export module a;
+export namespace std {
+  using std::variant;
+  using std::_Traits;
+}
 
 //--- b.cppm
 module;
@@ -51,7 +55,10 @@ module;
 export module b;
 import a;
 
+export namespace std {
+  using std::variant;
+  using std::_Traits;
+}
+
 // expected-error@* {{has different definitions in different modules; first difference is defined here found data member '_S_copy_ctor' with an initializer}}
 // expected-note@* {{but in 'a.<global>' found data member '_S_copy_ctor' with a different initializer}}
-// expected-error@* {{from module 'a.<global>' is not present in definition of 'variant<_Types...>' provided earlier}}
-// expected-note@* {{declaration of 'swap' does not match}}
diff --git a/clang/test/Modules/pr58716.cppm b/clang/test/Modules/pr58716.cppm
index 3f97fca7d5e8a3..00e131fac94a39 100644
--- a/clang/test/Modules/pr58716.cppm
+++ b/clang/test/Modules/pr58716.cppm
@@ -14,6 +14,8 @@ module;
 #include "fail.h"
 export module mymodule;
 
+export using ::this_fails;
+
 // CHECK: @.str = {{.*}}"{}\00"
 // CHECK: store{{.*}}ptr @.str
 
diff --git a/clang/test/Modules/pr60775.cppm b/clang/test/Modules/pr60775.cppm
index 4db027ba3600a9..38752e2d5d9435 100644
--- a/clang/test/Modules/pr60775.cppm
+++ b/clang/test/Modules/pr60775.cppm
@@ -32,6 +32,10 @@ void a() {
 	}
 }
 
+export namespace std {
+    using std::initializer_list;
+}
+
 //--- b.cpp
 // expected-no-diagnostics
 import a;
diff --git a/clang/test/Modules/pr62589.cppm b/clang/test/Modules/pr62589.cppm
index 4164c3405ac0e3..6aeafffbc5a0f7 100644
--- a/clang/test/Modules/pr62589.cppm
+++ b/clang/test/Modules/pr62589.cppm
@@ -70,6 +70,9 @@ export module a;
 export using ::a;
 export using ::a_view;
 
+// Otherwise the operator== would be discarded.
+using ::operator==;
+
 //--- b.cpp
 // expected-no-diagnostics
 import a;
diff --git a/clang/test/Modules/preferred_name.cppm b/clang/test/Modules/preferred_name.cppm
index 46ad96cb1abc33..273791ee59f516 100644
--- a/clang/test/Modules/preferred_name.cppm
+++ b/clang/test/Modules/preferred_name.cppm
@@ -32,6 +32,8 @@ inline foo_templ<char> bar()
 module;
 #include "foo.h"
 export module A;
+export using ::foo_templ;
+export using ::bar;
 
 //--- Use.cppm
 // expected-no-diagnostics
diff --git a/clang/test/Modules/redundant-template-default-arg3.cpp b/clang/test/Modules/redundant-template-default-arg3.cpp
index 8bb222ac91ffce..ee8a68e1a5cbcd 100644
--- a/clang/test/Modules/redundant-template-default-arg3.cpp
+++ b/clang/test/Modules/redundant-template-default-arg3.cpp
@@ -88,6 +88,15 @@ int v9;
 module;
 #include "foo.h"
 export module foo;
+export using ::v;
+export using ::v2;
+export using ::v3;
+export using ::v4;
+export using ::v5;
+export using ::v6;
+export using ::v7;
+export using ::v8;
+export using ::v9;
 
 //--- use.cpp
 import foo;
diff --git a/clang/test/Modules/template-function-specialization.cpp b/clang/test/Modules/template-function-specialization.cpp
index 3eac92e7edb94c..3ceb43c7ceee7b 100644
--- a/clang/test/Modules/template-function-specialization.cpp
+++ b/clang/test/Modules/template-function-specialization.cpp
@@ -48,10 +48,8 @@ import foo;
 void use() {
   foo<short>();
   foo<int>();
-  foo2<short>(); // expected-error {{missing '#include'; 'foo2' must be declared before it is used}}
-                 // expected-note@* {{declaration here is not visible}}
-  foo2<int>();   // expected-error {{missing '#include'; 'foo2' must be declared before it is used}}
-                 // expected-note@* {{declaration here is not visible}}
+  foo2<short>(); // expected-error {{use of undeclared identifier 'foo2'}}
+  foo2<int>();   // expected-error {{use of undeclared identifier 'foo2'}}
   foo3<short>();
   foo3<int>();
 



More information about the cfe-commits mailing list