[clang] [Clang] Handle C++20 export declarations in -dump-minimization-hints (PR #151666)

Ilya Biryukov via cfe-commits cfe-commits at lists.llvm.org
Fri Aug 1 05:30:55 PDT 2025


https://github.com/ilya-biryukov updated https://github.com/llvm/llvm-project/pull/151666

>From d1c95d8babda9665310773acc66ead6ba5797649 Mon Sep 17 00:00:00 2001
From: Ilya Biryukov <ibiryukov at google.com>
Date: Fri, 1 Aug 2025 09:14:46 +0200
Subject: [PATCH 1/2] [Clang] Handle C++20 export declarations in
 -dump-minimization-hints

Thanks to Justin Stitt for bringing this up, providing test cases and a
direction for the fix!
---
 clang/lib/Frontend/FrontendAction.cpp         |  82 ++++++++----
 .../dump-minimization-hints-cpp20-modules.cpp | 117 ++++++++++++++++++
 2 files changed, 172 insertions(+), 27 deletions(-)
 create mode 100644 clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp

diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index 87cc2fc0cdd17..6c2f6f13a1fce 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -84,29 +84,28 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
     if (!IsCollectingDecls)
       return;
     if (!D || isa<TranslationUnitDecl>(D) || isa<LinkageSpecDecl>(D) ||
-        isa<NamespaceDecl>(D)) {
+        isa<NamespaceDecl>(D) || isa<ExportDecl>(D)) {
       // These decls cover a lot of nested declarations that might not be used,
       // reducing the granularity and making the output less useful.
       return;
     }
-    auto *DC = D->getLexicalDeclContext();
-    if (!DC || !DC->isFileContext()) {
-      // We choose to work at namespace level to reduce complexity and the
-      // number of cases we care about.
+    if (isa<ParmVarDecl>(D)) {
+      // Parameters are covered by their functions.
       return;
     }
+    auto *DC = D->getLexicalDeclContext();
+    if (!DC || !shouldIncludeDeclsIn(DC))
+      return;
 
     PendingDecls.push_back(D);
-    if (auto *NS = dyn_cast<NamespaceDecl>(DC)) {
-      // Add any namespaces we have not seen before.
-      // Note that we filter out namespaces from DeclRead as it includes too
-      // all redeclarations and we only want the ones that had other used
-      // declarations.
-      while (NS && ProcessedNamespaces.insert(NS).second) {
-        PendingDecls.push_back(NS);
-
-        NS = dyn_cast<NamespaceDecl>(NS->getLexicalParent());
-      }
+    for (; (isa<ExportDecl>(DC) || isa<NamespaceDecl>(DC)) &&
+           ProcessedDeclContexts.insert(DC).second;
+         DC = DC->getLexicalParent()) {
+      // Add any interesting decl contexts that we have not seen before.
+      // Note that we filter them out from DeclRead as that would include all
+      // redeclarations of namespaces, potentially those that do not have any
+      // imported declarations.
+      PendingDecls.push_back(cast<Decl>(DC));
     }
   }
 
@@ -205,12 +204,37 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
 
 private:
   std::vector<const Decl *> PendingDecls;
-  llvm::SmallPtrSet<const NamespaceDecl *, 0> ProcessedNamespaces;
+  llvm::SmallPtrSet<const DeclContext *, 0> ProcessedDeclContexts;
   bool IsCollectingDecls = true;
   const SourceManager &SM;
   std::unique_ptr<llvm::raw_ostream> OS;
 
+  static bool shouldIncludeDeclsIn(const DeclContext *DC) {
+    assert(DC && "DC is null");
+    // We choose to work at namespace level to reduce complexity and the number
+    // of cases we care about.
+    // We still need to carefully handle composite declarations like
+    // `ExportDecl`.
+    for (; DC; DC = DC->getLexicalParent()) {
+      if (DC->isFileContext())
+        return true;
+      if (isa<ExportDecl>(DC))
+        continue; // Depends on the parent.
+      return false;
+    }
+    llvm_unreachable("DeclConext chain must end with a translation unit");
+  }
   llvm::SmallVector<CharSourceRange, 2> getRangesToMark(const Decl *D) {
+    if (auto *ED = dyn_cast<ExportDecl>(D)) {
+      if (!ED->hasBraces())
+        return {SM.getExpansionRange(ED->getExportLoc())};
+
+      return {SM.getExpansionRange(SourceRange(
+                  ED->getExportLoc(),
+                  lexForLBrace(ED->getExportLoc(), D->getLangOpts()))),
+              SM.getExpansionRange(ED->getRBraceLoc())};
+    }
+
     auto *NS = dyn_cast<NamespaceDecl>(D);
     if (!NS)
       return {SM.getExpansionRange(D->getSourceRange())};
@@ -232,17 +256,7 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
           }
         }
       }
-      auto &LangOpts = D->getLangOpts();
-      // Now skip one token, the next should be the lbrace.
-      Token Tok;
-      if (Lexer::getRawToken(TokenBeforeLBrace, Tok, SM, LangOpts, true) ||
-          Lexer::getRawToken(Tok.getEndLoc(), Tok, SM, LangOpts, true) ||
-          Tok.getKind() != tok::l_brace) {
-        // On error or if we did not find the token we expected, avoid marking
-        // everything inside the namespace as used.
-        return {};
-      }
-      LBraceLoc = Tok.getLocation();
+      LBraceLoc = lexForLBrace(TokenBeforeLBrace, D->getLangOpts());
     }
     return {SM.getExpansionRange(SourceRange(NS->getBeginLoc(), LBraceLoc)),
             SM.getExpansionRange(NS->getRBraceLoc())};
@@ -285,6 +299,20 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
 
     OS->flush();
   }
+
+  SourceLocation lexForLBrace(SourceLocation TokenBeforeLBrace,
+                              const LangOptions &LangOpts) {
+    // Now skip one token, the next should be the lbrace.
+    Token Tok;
+    if (Lexer::getRawToken(TokenBeforeLBrace, Tok, SM, LangOpts, true) ||
+        Lexer::getRawToken(Tok.getEndLoc(), Tok, SM, LangOpts, true) ||
+        Tok.getKind() != tok::l_brace) {
+      // On error or if we did not find the token we expected, avoid marking
+      // everything inside the namespace as used.
+      return SourceLocation();
+    }
+    return Tok.getLocation();
+  }
 };
 
 /// Dumps deserialized declarations.
diff --git a/clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp b/clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp
new file mode 100644
index 0000000000000..d0750e0485b8a
--- /dev/null
+++ b/clang/test/Frontend/dump-minimization-hints-cpp20-modules.cpp
@@ -0,0 +1,117 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: %clang_cc1 -std=c++20 %t/foo.cppm -emit-module-interface -o %t/foo.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -dump-minimization-hints=%t/decls
+// RUN: cat %t/decls
+// RUN: cat %t/decls | FileCheck -check-prefix=RANGE %s
+// RANGE:{
+// RANGE-NEXT:"required_ranges": [
+// RANGE-NEXT:  {
+// RANGE-NEXT:    "file": "{{.+}}/foo.cppm",
+// RANGE-NEXT:    "range": [
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 3,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 3,
+// RANGE-NEXT:          "column": 22
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 3
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 9
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 10
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 4,
+// RANGE-NEXT:          "column": 43
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 6,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 6,
+// RANGE-NEXT:          "column": 2
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 7
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 8
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 8,
+// RANGE-NEXT:          "column": 25
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 9,
+// RANGE-NEXT:          "column": 3
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 9,
+// RANGE-NEXT:          "column": 36
+// RANGE-NEXT:        }
+// RANGE-NEXT:      },
+// RANGE-NEXT:      {
+// RANGE-NEXT:        "from": {
+// RANGE-NEXT:          "line": 11,
+// RANGE-NEXT:          "column": 1
+// RANGE-NEXT:        },
+// RANGE-NEXT:        "to": {
+// RANGE-NEXT:          "line": 11,
+// RANGE-NEXT:          "column": 2
+// RANGE-NEXT:        }
+// RANGE-NEXT:      }
+// RANGE-NEXT:    ]
+// RANGE-NEXT:  }
+// RANGE-NEXT:]
+// RANGE-NEXT:}
+
+//--- foo.cppm
+export module foo;
+
+namespace piecemeal { // line 3
+  export int used(int n) { return n + 1; }
+  export int unused(int n) { return n + 2; }
+}
+
+export namespace whole { // line 8
+  int used(int n) { return n + 1; }
+  int unused(int n) { return n + 3; }
+} // line 11
+
+//--- use.cpp
+import foo;
+
+int main() {
+  piecemeal::used(4);  // only of the functions used from each namespace.
+  whole::used(4);
+}

>From bbf9c932e8443b62a23b66b21f03c40abcd9ff9c Mon Sep 17 00:00:00 2001
From: Ilya Biryukov <ibiryukov at google.com>
Date: Fri, 1 Aug 2025 14:30:14 +0200
Subject: [PATCH 2/2] Fix a typo, add a newline between functions

---
 clang/lib/Frontend/FrontendAction.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index 6c2f6f13a1fce..e913f49c34803 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -222,8 +222,9 @@ class DeserializedDeclsSourceRangePrinter : public ASTConsumer,
         continue; // Depends on the parent.
       return false;
     }
-    llvm_unreachable("DeclConext chain must end with a translation unit");
+    llvm_unreachable("DeclContext chain must end with a translation unit");
   }
+
   llvm::SmallVector<CharSourceRange, 2> getRangesToMark(const Decl *D) {
     if (auto *ED = dyn_cast<ExportDecl>(D)) {
       if (!ED->hasBraces())



More information about the cfe-commits mailing list