https://github.com/mpark updated https://github.com/llvm/llvm-project/pull/171769
>From 1ddc0fe26c4ba83ef8b825d481f4104270490fa2 Mon Sep 17 00:00:00 2001 From: Michael Park <[email protected]> Date: Mon, 15 Dec 2025 21:18:02 -0800 Subject: [PATCH 1/3] [C++20][Modules] Add a test for namespace lookup optimization. --- clang/unittests/Serialization/CMakeLists.txt | 1 + .../Serialization/NamespaceLookupTest.cpp | 248 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 clang/unittests/Serialization/NamespaceLookupTest.cpp diff --git a/clang/unittests/Serialization/CMakeLists.txt b/clang/unittests/Serialization/CMakeLists.txt index 6782e6b4d7330..a5cc1ed83af49 100644 --- a/clang/unittests/Serialization/CMakeLists.txt +++ b/clang/unittests/Serialization/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_unittest(SerializationTests ForceCheckFileInputTest.cpp InMemoryModuleCacheTest.cpp ModuleCacheTest.cpp + NamespaceLookupTest.cpp NoCommentsTest.cpp PreambleInNamedModulesTest.cpp LoadSpecLazilyTest.cpp diff --git a/clang/unittests/Serialization/NamespaceLookupTest.cpp b/clang/unittests/Serialization/NamespaceLookupTest.cpp new file mode 100644 index 0000000000000..f11c6218dac32 --- /dev/null +++ b/clang/unittests/Serialization/NamespaceLookupTest.cpp @@ -0,0 +1,248 @@ +//== unittests/Serialization/NamespaceLookupOptimizationTest.cpp =======// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Driver/CreateInvocationFromArgs.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Parse/ParseAST.h" +#include "clang/Serialization/ASTReader.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang; +using namespace clang::tooling; + +namespace { + +class NamespaceLookupTest : public ::testing::Test { + void SetUp() override { + ASSERT_FALSE( + sys::fs::createUniqueDirectory("namespace-lookup-test", TestDir)); + } + + void TearDown() override { sys::fs::remove_directories(TestDir); } + +public: + SmallString<256> TestDir; + + void addFile(StringRef Path, StringRef Contents) { + ASSERT_FALSE(sys::path::is_absolute(Path)); + + SmallString<256> AbsPath(TestDir); + sys::path::append(AbsPath, Path); + + ASSERT_FALSE( + sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath))); + + std::error_code EC; + llvm::raw_fd_ostream OS(AbsPath, EC); + ASSERT_FALSE(EC); + OS << Contents; + } + + std::string GenerateModuleInterface(StringRef ModuleName, + StringRef Contents) { + std::string FileName = llvm::Twine(ModuleName + ".cppm").str(); + addFile(FileName, Contents); + + IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = + llvm::vfs::createPhysicalFileSystem(); + DiagnosticOptions DiagOpts; + IntrusiveRefCntPtr<DiagnosticsEngine> Diags = + CompilerInstance::createDiagnostics(*VFS, DiagOpts); + CreateInvocationOptions CIOpts; + CIOpts.Diags = Diags; + CIOpts.VFS = VFS; + + std::string CacheBMIPath = + llvm::Twine(TestDir + "/" + ModuleName + ".pcm").str(); + std::string PrebuiltModulePath = + "-fprebuilt-module-path=" + TestDir.str().str(); + const char *Args[] = {"clang++", + "-std=c++20", + "--precompile", + PrebuiltModulePath.c_str(), + "-working-directory", + TestDir.c_str(), + "-I", + TestDir.c_str(), + FileName.c_str(), + "-o", + CacheBMIPath.c_str()}; + std::shared_ptr<CompilerInvocation> Invocation = + createInvocation(Args, CIOpts); + EXPECT_TRUE(Invocation); + + CompilerInstance Instance(std::move(Invocation)); + Instance.setDiagnostics(Diags); + Instance.getFrontendOpts().OutputFile = CacheBMIPath; + // Avoid memory leaks. + Instance.getFrontendOpts().DisableFree = false; + GenerateModuleInterfaceAction Action; + EXPECT_TRUE(Instance.ExecuteAction(Action)); + EXPECT_FALSE(Diags->hasErrorOccurred()); + + return CacheBMIPath; + } +}; + +struct NamespaceLookupResult { + int NumLocalNamespaces = 0; + int NumExternalNamespaces = 0; +}; + +class NamespaceLookupConsumer : public ASTConsumer { + NamespaceLookupResult &Result; + +public: + + explicit NamespaceLookupConsumer(NamespaceLookupResult &Result) + : Result(Result) {} + + void HandleTranslationUnit(ASTContext &Context) override { + TranslationUnitDecl *TU = Context.getTranslationUnitDecl(); + ASSERT_TRUE(TU); + ASTReader *Chain = dyn_cast_or_null<ASTReader>(Context.getExternalSource()); + ASSERT_TRUE(Chain); + for (const Decl *D : + TU->lookup(DeclarationName(&Context.Idents.get("N")))) { + if (!isa<NamespaceDecl>(D)) + continue; + if (!D->isFromASTFile()) { + ++Result.NumLocalNamespaces; + } else { + ++Result.NumExternalNamespaces; + EXPECT_EQ(D, Chain->getKeyDeclaration(D)); + } + } + } +}; + +class NamespaceLookupAction : public ASTFrontendAction { + NamespaceLookupResult &Result; + +public: + explicit NamespaceLookupAction(NamespaceLookupResult &Result) + : Result(Result) {} + + std::unique_ptr<ASTConsumer> + CreateASTConsumer(CompilerInstance &CI, StringRef /*Unused*/) override { + return std::make_unique<NamespaceLookupConsumer>(Result); + } +}; + +TEST_F(NamespaceLookupTest, ExternalNamespacesOnly) { + GenerateModuleInterface("M1", R"cpp( +export module M1; +namespace N {} + )cpp"); + GenerateModuleInterface("M2", R"cpp( +export module M2; +namespace N {} + )cpp"); + GenerateModuleInterface("M3", R"cpp( +export module M3; +namespace N {} + )cpp"); + const char *test_file_contents = R"cpp( +import M1; +import M2; +import M3; + )cpp"; + std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); + NamespaceLookupResult Result; + EXPECT_TRUE(runToolOnCodeWithArgs( + std::make_unique<NamespaceLookupAction>(Result), test_file_contents, + { + "-std=c++20", + DepArg.c_str(), + "-I", + TestDir.c_str(), + }, + "main.cpp")); + + EXPECT_EQ(0, Result.NumLocalNamespaces); + EXPECT_EQ(1, Result.NumExternalNamespaces); +} + +TEST_F(NamespaceLookupTest, ExternalReplacedByLocal) { + GenerateModuleInterface("M1", R"cpp( +export module M1; +namespace N {} + )cpp"); + GenerateModuleInterface("M2", R"cpp( +export module M2; +namespace N {} + )cpp"); + GenerateModuleInterface("M3", R"cpp( +export module M3; +namespace N {} + )cpp"); + const char *test_file_contents = R"cpp( +import M1; +import M2; +import M3; + +namespace N {} + )cpp"; + std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); + NamespaceLookupResult Result; + EXPECT_TRUE(runToolOnCodeWithArgs( + std::make_unique<NamespaceLookupAction>(Result), test_file_contents, + { + "-std=c++20", + DepArg.c_str(), + "-I", + TestDir.c_str(), + }, + "main.cpp")); + + EXPECT_EQ(1, Result.NumLocalNamespaces); + EXPECT_EQ(0, Result.NumExternalNamespaces); +} + +TEST_F(NamespaceLookupTest, LocalAndExternalInterleaved) { + GenerateModuleInterface("M1", R"cpp( +export module M1; +namespace N {} + )cpp"); + GenerateModuleInterface("M2", R"cpp( +export module M2; +namespace N {} + )cpp"); + GenerateModuleInterface("M3", R"cpp( +export module M3; +namespace N {} + )cpp"); + const char *test_file_contents = R"cpp( +import M1; + +namespace N {} + +import M2; +import M3; + )cpp"; + std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); + NamespaceLookupResult Result; + EXPECT_TRUE(runToolOnCodeWithArgs( + std::make_unique<NamespaceLookupAction>(Result), test_file_contents, + { + "-std=c++20", + DepArg.c_str(), + "-I", + TestDir.c_str(), + }, + "main.cpp")); + + EXPECT_EQ(1, Result.NumLocalNamespaces); + EXPECT_EQ(1, Result.NumExternalNamespaces); +} + +} // namespace >From b416fb51f3770558177d622efd98a0f18c78cc95 Mon Sep 17 00:00:00 2001 From: Michael Park <[email protected]> Date: Wed, 10 Dec 2025 17:58:04 -0800 Subject: [PATCH 2/3] [C++20][Modules] Improve namespace handling performance for modules. --- clang/lib/Serialization/ASTReader.cpp | 58 ++++++++++++++++++++++----- clang/lib/Serialization/ASTWriter.cpp | 31 +++++++++++--- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index aec61322fb8be..e8600d1e914cd 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -555,7 +555,25 @@ namespace { using MacroDefinitionsMap = llvm::StringMap<std::pair<StringRef, bool /*IsUndef*/>>; -using DeclsMap = llvm::DenseMap<DeclarationName, SmallVector<NamedDecl *, 8>>; + +class DeclsSet { + SmallVector<NamedDecl *, 64> Decls; + llvm::SmallPtrSet<NamedDecl *, 8> Found; + +public: + operator ArrayRef<NamedDecl *>() const { return Decls; } + + bool empty() const { return Decls.empty(); } + + bool insert(NamedDecl *ND) { + auto [_, Inserted] = Found.insert(ND); + if (Inserted) + Decls.push_back(ND); + return Inserted; + } +}; + +using DeclsMap = llvm::DenseMap<DeclarationName, DeclsSet>; } // namespace @@ -8702,14 +8720,23 @@ bool ASTReader::FindExternalVisibleDeclsByName(const DeclContext *DC, return false; // Load the list of declarations. - SmallVector<NamedDecl *, 64> Decls; - llvm::SmallPtrSet<NamedDecl *, 8> Found; + DeclsSet DS; auto Find = [&, this](auto &&Table, auto &&Key) { for (GlobalDeclID ID : Table.find(Key)) { NamedDecl *ND = cast<NamedDecl>(GetDecl(ID)); - if (ND->getDeclName() == Name && Found.insert(ND).second) - Decls.push_back(ND); + if (ND->getDeclName() != Name) + continue; + // Special case for namespaces: There can be a lot of redeclarations of + // some namespaces, and we import a "key declaration" per imported module. + // Since all declarations of a namespace are essentially interchangeable, + // we can optimize namespace look-up by only storing the key declaration + // of the current TU, rather than storing N key declarations where N is + // the # of imported modules that declare that namespace. + // TODO: Try to generalize this optimization to other redeclarable decls. + if (isa<NamespaceDecl>(ND)) + ND = cast<NamedDecl>(getKeyDeclaration(ND)); + DS.insert(ND); } }; @@ -8744,8 +8771,8 @@ bool ASTReader::FindExternalVisibleDeclsByName(const DeclContext *DC, Find(It->second.Table, Name); } - SetExternalVisibleDeclsForName(DC, Name, Decls); - return !Decls.empty(); + SetExternalVisibleDeclsForName(DC, Name, DS); + return !DS.empty(); } void ASTReader::completeVisibleDeclsMap(const DeclContext *DC) { @@ -8763,7 +8790,16 @@ void ASTReader::completeVisibleDeclsMap(const DeclContext *DC) { for (GlobalDeclID ID : It->second.Table.findAll()) { NamedDecl *ND = cast<NamedDecl>(GetDecl(ID)); - Decls[ND->getDeclName()].push_back(ND); + // Special case for namespaces: There can be a lot of redeclarations of + // some namespaces, and we import a "key declaration" per imported module. + // Since all declarations of a namespace are essentially interchangeable, + // we can optimize namespace look-up by only storing the key declaration + // of the current TU, rather than storing N key declarations where N is + // the # of imported modules that declare that namespace. + // TODO: Try to generalize this optimization to other redeclarable decls. + if (isa<NamespaceDecl>(ND)) + ND = cast<NamedDecl>(getKeyDeclaration(ND)); + Decls[ND->getDeclName()].insert(ND); } // FIXME: Why a PCH test is failing if we remove the iterator after findAll? @@ -8773,9 +8809,9 @@ void ASTReader::completeVisibleDeclsMap(const DeclContext *DC) { findAll(ModuleLocalLookups, NumModuleLocalVisibleDeclContexts); findAll(TULocalLookups, NumTULocalVisibleDeclContexts); - for (DeclsMap::iterator I = Decls.begin(), E = Decls.end(); I != E; ++I) { - SetExternalVisibleDeclsForName(DC, I->first, I->second); - } + for (auto &[Name, DS] : Decls) + SetExternalVisibleDeclsForName(DC, Name, DS); + const_cast<DeclContext *>(DC)->setHasExternalVisibleStorage(false); } diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index dc7f93e0483e4..6a4b788066476 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -4396,20 +4396,20 @@ class ASTDeclContextNameLookupTrait template <typename Coll> data_type getData(const Coll &Decls) { unsigned Start = DeclIDs.size(); - for (NamedDecl *D : Decls) { + auto AddDecl = [this](NamedDecl *D) { NamedDecl *DeclForLocalLookup = getDeclForLocalLookup(Writer.getLangOpts(), D); if (Writer.getDoneWritingDeclsAndTypes() && !Writer.wasDeclEmitted(DeclForLocalLookup)) - continue; + return; // Try to avoid writing internal decls to reduced BMI. // See comments in ASTWriter::WriteDeclContextLexicalBlock for details. if (Writer.isGeneratingReducedBMI() && !DeclForLocalLookup->isFromExplicitGlobalModule() && IsInternalDeclFromFileContext(DeclForLocalLookup)) - continue; + return; auto ID = Writer.GetDeclRef(DeclForLocalLookup); @@ -4423,7 +4423,7 @@ class ASTDeclContextNameLookupTrait ModuleLocalDeclsMap.insert({Key, DeclIDsTy{ID}}); else Iter->second.push_back(ID); - continue; + return; } break; case LookupVisibility::TULocal: { @@ -4432,7 +4432,7 @@ class ASTDeclContextNameLookupTrait TULocalDeclsMap.insert({D->getDeclName(), DeclIDsTy{ID}}); else Iter->second.push_back(ID); - continue; + return; } case LookupVisibility::GenerallyVisibile: // Generally visible decls go into the general lookup table. @@ -4440,6 +4440,27 @@ class ASTDeclContextNameLookupTrait } DeclIDs.push_back(ID); + }; + for (NamedDecl *D : Decls) { + if (isa<NamespaceDecl>(D) && D->isFromASTFile()) { + // In ASTReader, we stored only the key declaration of a namespace decl + // for this TU rather than storing all of the key declarations from each + // imported module. If we have an external namespace decl, this is that + // key declaration and we need to re-expand it to write out all of the + // key declarations from each imported module again. + // + // See comment 'ASTReader::FindExternalVisibleDeclsByName' for details. + ASTReader *Chain = Writer.getChain(); + assert(Chain && "An external namespace decl without an ASTReader"); + assert(D == Chain->getKeyDeclaration(D) && + "An external namespace decl that is not " + "the key declaration of this TU"); + Chain->forEachImportedKeyDecl(D, [&AddDecl](const Decl *D) { + AddDecl(cast<NamedDecl>(const_cast<Decl *>(D))); + }); + } else { + AddDecl(D); + } } return std::make_pair(Start, DeclIDs.size()); } >From 8b117abcba907ea646a64257e15717265066da10 Mon Sep 17 00:00:00 2001 From: Michael Park <[email protected]> Date: Mon, 15 Dec 2025 08:10:02 -0800 Subject: [PATCH 3/3] [C++20][Modules] Refine condition for key decl check. --- clang/lib/Serialization/ASTWriter.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index 6a4b788066476..899fd69c2045e 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -4442,7 +4442,9 @@ class ASTDeclContextNameLookupTrait DeclIDs.push_back(ID); }; for (NamedDecl *D : Decls) { - if (isa<NamespaceDecl>(D) && D->isFromASTFile()) { + if (ASTReader *Chain = Writer.getChain(); + Chain && isa<NamespaceDecl>(D) && D->isFromASTFile() && + D == Chain->getKeyDeclaration(D)) { // In ASTReader, we stored only the key declaration of a namespace decl // for this TU rather than storing all of the key declarations from each // imported module. If we have an external namespace decl, this is that @@ -4450,11 +4452,6 @@ class ASTDeclContextNameLookupTrait // key declarations from each imported module again. // // See comment 'ASTReader::FindExternalVisibleDeclsByName' for details. - ASTReader *Chain = Writer.getChain(); - assert(Chain && "An external namespace decl without an ASTReader"); - assert(D == Chain->getKeyDeclaration(D) && - "An external namespace decl that is not " - "the key declaration of this TU"); Chain->forEachImportedKeyDecl(D, [&AddDecl](const Decl *D) { AddDecl(cast<NamedDecl>(const_cast<Decl *>(D))); }); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
