https://github.com/makslevental created 
https://github.com/llvm/llvm-project/pull/146104

(Unity building here called "jumbo" because it is called that in the context of 
the Chromium project.)

This adds an example plugin named `JumboSupport` that adds a pragma named 
`jumbo`, which signals that the main source file is a jumbo compilation unit 
that will include other source files. After each include into the main source 
file, the contents of all anonymous namespace declarations in the included 
source file are hidden, so that the anonymous namespace remains local to source 
files. It also undefines all macros defined in the included source file.

Some (intentionally) minimal changes to clang is implemented to make it 
possible to implement this functionality in the plugin.

>From 6e4090dd5e83d28ef3655e2b11746d0a6da0b8d0 Mon Sep 17 00:00:00 2001
From: Maksim Levental <maksim.leven...@gmail.com>
Date: Fri, 27 Jun 2025 12:20:07 -0400
Subject: [PATCH] Add some basic extra support for C++ unity building

(Unity building here called "jumbo" because it is called that in the
context of the Chromium project.)

This adds an example plugin named `JumboSupport` that adds a pragma
named `jumbo`, which signals that the main source file is a jumbo
compilation unit that will include other source files. After each
include into the main source file, the contents of all anonymous
namespace declarations in the included source file are hidden, so
that the anonymous namespace remains local to source files. It also
undefines all macros defined in the included source file.

Some (intentionally) minimal changes to clang is implemented to
make it possible to implement this functionality in the plugin.
---
 clang/examples/CMakeLists.txt                |   1 +
 clang/examples/JumboSupport/CMakeLists.txt   |  14 ++
 clang/examples/JumboSupport/JumboSupport.cpp | 165 +++++++++++++++++++
 clang/include/clang/AST/Decl.h               |   7 +
 clang/lib/AST/ItaniumMangle.cpp              |  16 ++
 clang/lib/Sema/SemaLookup.cpp                |   6 +
 6 files changed, 209 insertions(+)
 create mode 100644 clang/examples/JumboSupport/CMakeLists.txt
 create mode 100644 clang/examples/JumboSupport/JumboSupport.cpp

diff --git a/clang/examples/CMakeLists.txt b/clang/examples/CMakeLists.txt
index 2396ecac16b2d..f46fe762a2d71 100644
--- a/clang/examples/CMakeLists.txt
+++ b/clang/examples/CMakeLists.txt
@@ -11,3 +11,4 @@ if(CLANG_PLUGIN_SUPPORT)
   add_subdirectory(CallSuperAttribute)
   add_subdirectory(PluginsOrder)
 endif()
+add_subdirectory(JumboSupport)
diff --git a/clang/examples/JumboSupport/CMakeLists.txt 
b/clang/examples/JumboSupport/CMakeLists.txt
new file mode 100644
index 0000000000000..9630ec1eeeee1
--- /dev/null
+++ b/clang/examples/JumboSupport/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_llvm_library(JumboSupport MODULE JumboSupport.cpp PLUGIN_TOOL clang)
+
+if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
+  set(LLVM_LINK_COMPONENTS
+    Support
+  )
+  clang_target_link_libraries(JumboSupport PRIVATE
+    clangAST
+    clangBasic
+    clangFrontend
+    clangLex
+    LLVMSupport
+    )
+endif()
diff --git a/clang/examples/JumboSupport/JumboSupport.cpp 
b/clang/examples/JumboSupport/JumboSupport.cpp
new file mode 100644
index 0000000000000..b0fbbbb4c83c2
--- /dev/null
+++ b/clang/examples/JumboSupport/JumboSupport.cpp
@@ -0,0 +1,165 @@
+//===- ResetAnonymousNamespace.cpp 
----------------------------------------===//
+//
+// Clang plugin that adds
+//
+//   #pragma reset_anonymous_namespace
+//
+// which resets the anonymous namespace, as if a new translation unit was being
+// processed.
+//
+//===----------------------------------------------------------------------===//
+
+#include <set>
+#include <string>
+
+#include "clang/AST/AST.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Frontend/FrontendPluginRegistry.h"
+#include "clang/Lex/LexDiagnostic.h"
+#include "clang/Lex/Preprocessor.h"
+
+namespace {
+
+bool IsLocationInFile(clang::SourceManager &SM, clang::SourceLocation Loc,
+                      const clang::FileEntry *File) {
+  clang::FileID FID = SM.getFileID(SM.getSpellingLoc(Loc));
+  return FID.isValid() && SM.getFileEntryForID(FID) == File;
+}
+
+class DisableTaggedNamespaceDecls
+    : public clang::RecursiveASTVisitor<DisableTaggedNamespaceDecls> {
+private:
+  clang::SourceManager &SM;
+  const clang::FileEntry *File;
+
+public:
+  DisableTaggedNamespaceDecls(clang::SourceManager &SM,
+                              const clang::FileEntry *File)
+      : SM(SM), File(File) {}
+
+  bool VisitNamespaceDecl(clang::NamespaceDecl *NS) {
+    if (NS->isAnonymousNamespace())
+      if (IsLocationInFile(SM, NS->getBeginLoc(), File))
+        NS->setDisabled();
+    return true;
+  }
+};
+
+class ASTConsumer : public clang::ASTConsumer {
+  clang::ASTContext *Context;
+
+public:
+  static ASTConsumer *Instance;
+
+  ASTConsumer() { Instance = this; }
+
+  void Initialize(clang::ASTContext &Ctx) override { Context = &Ctx; }
+
+  clang::ASTContext *getASTContext() { return Context; }
+};
+
+ASTConsumer *ASTConsumer::Instance = nullptr;
+
+class PPCallbacks : public clang::PPCallbacks {
+  clang::Preprocessor &PP;
+  clang::SourceManager &SM;
+
+  const clang::FileEntry *CurrentFile;
+
+  std::set<std::string> DefinedMacros;
+
+public:
+  PPCallbacks(clang::Preprocessor &PP) : PP(PP), SM(PP.getSourceManager()) {}
+
+  static void Register(clang::Preprocessor &PP) {
+    PP.addPPCallbacks(std::make_unique<PPCallbacks>(PP));
+  }
+
+  void
+  InclusionDirective(clang::SourceLocation HashLoc,
+                     const clang::Token &IncludeTok, llvm::StringRef FileName,
+                     bool IsAngled, clang::CharSourceRange FilenameRange,
+                     clang::OptionalFileEntryRef File,
+                     llvm::StringRef SearchPath, llvm::StringRef RelativePath,
+                     const clang::Module *SuggestedModule, bool ModuleImported,
+                     clang::SrcMgr::CharacteristicKind FileType) override {
+    if (SM.isInMainFile(HashLoc))
+      CurrentFile = &File->getFileEntry();
+  }
+
+  void FileChanged(clang::SourceLocation Loc, FileChangeReason Reason,
+                   clang::SrcMgr::CharacteristicKind,
+                   clang::FileID PrevFID) override {
+    if (SM.isInMainFile(Loc)) {
+      auto Context = ASTConsumer::Instance->getASTContext();
+
+      DisableTaggedNamespaceDecls Visitor(SM, CurrentFile);
+      Visitor.TraverseDecl(Context->getTranslationUnitDecl());
+
+      llvm::BumpPtrAllocator &Allocator = PP.getPreprocessorAllocator();
+      for (auto Name : DefinedMacros) {
+        clang::IdentifierInfo *II = PP.getIdentifierInfo(Name);
+        PP.appendMacroDirective(II, new (Allocator)
+                                        clang::UndefMacroDirective(Loc));
+      }
+
+      CurrentFile = nullptr;
+      DefinedMacros.clear();
+    }
+  }
+
+  void MacroDefined(const clang::Token &Name,
+                    const clang::MacroDirective *MD) override {
+    if (CurrentFile && IsLocationInFile(SM, Name.getLocation(), CurrentFile))
+      DefinedMacros.emplace(Name.getIdentifierInfo()->getName().str());
+  }
+
+  void MacroUndefined(const clang::Token &Name,
+                      const clang::MacroDefinition &MD,
+                      const clang::MacroDirective *Undef) override {
+    if (CurrentFile && IsLocationInFile(SM, Name.getLocation(), CurrentFile))
+      DefinedMacros.erase(Name.getIdentifierInfo()->getName().str());
+  }
+};
+
+class JumboFrontendAction : public clang::PluginASTAction {
+public:
+  std::unique_ptr<clang::ASTConsumer>
+  CreateASTConsumer(clang::CompilerInstance &CI, llvm::StringRef) override {
+    return std::make_unique<ASTConsumer>();
+  }
+
+  bool ParseArgs(const clang::CompilerInstance &CI,
+                 const std::vector<std::string> &args) override {
+    return true;
+  }
+};
+
+class PragmaJumbo : public clang::PragmaHandler {
+public:
+  PragmaJumbo() : clang::PragmaHandler("jumbo") {}
+
+  void HandlePragma(clang::Preprocessor &PP, clang::PragmaIntroducer 
Introducer,
+                    clang::Token &PragmaTok) override {
+    clang::Token Tok;
+    PP.LexUnexpandedToken(Tok);
+    if (Tok.isNot(clang::tok::eod))
+      PP.Diag(Tok, clang::diag::ext_pp_extra_tokens_at_eol) << "pragma unity";
+
+    if (!ASTConsumer::Instance) {
+      // Plugin not enabled.
+      return;
+    }
+
+    PPCallbacks::Register(PP);
+  }
+};
+
+} // namespace
+
+static clang::FrontendPluginRegistry::Add<JumboFrontendAction>
+    X("jumbo-support", "jumbo compilation support tools");
+
+static clang::PragmaHandlerRegistry::Add<PragmaJumbo>
+    P1("jumbo", "begin treating top-level includes as jumbo includes");
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index c4202f1f3d07e..8aed80a5d3414 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -579,6 +579,10 @@ class NamespaceDecl : public NamedDecl,
   /// The unnamed namespace that inhabits this namespace, if any.
   NamespaceDecl *AnonymousNamespace = nullptr;
 
+  /// Names in this specific namespace declaration should not be included in
+  /// lookups.
+  bool IsDisabled = false;
+
   NamespaceDecl(ASTContext &C, DeclContext *DC, bool Inline,
                 SourceLocation StartLoc, SourceLocation IdLoc,
                 IdentifierInfo *Id, NamespaceDecl *PrevDecl, bool Nested);
@@ -681,6 +685,9 @@ class NamespaceDecl : public NamedDecl,
   static NamespaceDecl *castFromDeclContext(const DeclContext *DC) {
     return static_cast<NamespaceDecl *>(const_cast<DeclContext*>(DC));
   }
+
+  bool isDisabled() const { return IsDisabled; }
+  void setDisabled() { IsDisabled = true; }
 };
 
 class VarDecl;
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 84936b72bb4fa..17552c23d2948 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -24,6 +24,7 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprConcepts.h"
+#include "clang/Basic/SourceManager.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/Mangle.h"
 #include "clang/AST/TypeLoc.h"
@@ -1578,8 +1579,23 @@ void CXXNameMangler::mangleUnqualifiedName(
 
     if (const NamespaceDecl *NS = dyn_cast<NamespaceDecl>(ND)) {
       if (NS->isAnonymousNamespace()) {
+#if 0
         // This is how gcc mangles these names.
         Out << "12_GLOBAL__N_1";
+#endif
+
+#if 1
+        // Add a per-file unique key for the source file containing
+        // the declaration.
+        // FIXME: This should be controlled by a flag.
+        SourceManager &SM = Context.getASTContext().getSourceManager();
+        SourceLocation Loc = SM.getSpellingLoc(NS->getBeginLoc());
+
+        std::string Name("__anonymous_");
+        Name.append(std::to_string(SM.getFileID(Loc).getHashValue()));
+
+        Out << Name.size() << Name;
+#endif
         break;
       }
     }
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index aa7191d2814fe..09647c3f4ca32 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -1137,6 +1137,12 @@ static bool LookupDirect(Sema &S, LookupResult &R, const 
DeclContext *DC) {
   // Perform lookup into this declaration context.
   DeclContext::lookup_result DR = DC->lookup(R.getLookupName());
   for (NamedDecl *D : DR) {
+    DeclContext *DCtx = D->getDeclContext();
+    if (EnumDecl *ED = dyn_cast<EnumDecl>(DCtx))
+      DCtx = ED->getDeclContext();
+    if (NamespaceDecl *NS = dyn_cast<NamespaceDecl>(DCtx))
+      if (NS->isDisabled())
+        continue;
     if ((D = R.getAcceptableDecl(D))) {
       R.addDecl(D);
       Found = true;

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to