https://github.com/Sirraide updated 
https://github.com/llvm/llvm-project/pull/210802

>From e8d94c9d5cf208d94823f6da44685ab3825ed647 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Mon, 20 Jul 2026 21:57:59 +0200
Subject: [PATCH 1/5] [Clang] Support libstdc++ hack in preprocessed input

---
 clang/docs/ReleaseNotes.md                        |  4 ++++
 clang/lib/Sema/SemaTemplate.cpp                   | 14 ++++----------
 clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp |  3 +++
 3 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 5a07e77076d17..87b447165e970 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -323,6 +323,10 @@ latest release, please see the [Clang Web 
Site](https://clang.llvm.org) or the
 - Fixed a crash when a using-declaration naming an unresolvable member of a
   dependent base was shadowed by an invalid using-declaration. (#GH209427)
 
+- A workaround that was introduced to fix an issue with the `<format>` header 
present in some versions of
+  libstdc++15 has been extended to handle the case of the input being 
preprocessed first (via `-E`) and
+  only then compiled; previously, this would result in the fix not being 
applied. (#GH160314)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 643392833759d..7268a9aa5b4ef 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4586,16 +4586,10 @@ static bool IsLibstdcxxStdFormatKind(Preprocessor &PP, 
VarDecl *Var) {
       !Var->getDeclContext()->isStdNamespace())
     return false;
 
-  // Checking old versions of libstdc++ is not needed because 15.1 is the first
-  // release in which users can access std::format_kind.
-  // We can use 20250520 as the final date, see the following commits.
-  // GCC releases/gcc-15 branch:
-  // https://gcc.gnu.org/g:fedf81ef7b98e5c9ac899b8641bb670746c51205
-  // https://gcc.gnu.org/g:53680c1aa92d9f78e8255fbf696c0ed36f160650
-  // GCC master branch:
-  // https://gcc.gnu.org/g:9361966d80f625c5accc25cbb439f0278dd8b278
-  // https://gcc.gnu.org/g:c65725eccbabf3b9b5965f27fff2d3b9f6c75930
-  return PP.NeedsStdLibCxxWorkaroundBefore(2025'05'20);
+  // We don't check the libstdc++ version for this one since that doesn't work
+  // if a user preprocesses the input as a separate step (because __GLIBCXX__
+  // will not be defined).
+  return true;
 }
 } // end anonymous namespace
 
diff --git a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp 
b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
index 35611c870b8d1..698b6ccf697e6 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,4 +1,7 @@
 // RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii
+// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
 
 // expected-no-diagnostics
 

>From 2758463b208e51a04576227d45f406bc5db742bb Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Mon, 20 Jul 2026 21:59:31 +0200
Subject: [PATCH 2/5] amend release note

---
 clang/docs/ReleaseNotes.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 87b447165e970..045aac959140d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -324,8 +324,8 @@ latest release, please see the [Clang Web 
Site](https://clang.llvm.org) or the
   dependent base was shadowed by an invalid using-declaration. (#GH209427)
 
 - A workaround that was introduced to fix an issue with the `<format>` header 
present in some versions of
-  libstdc++15 has been extended to handle the case of the input being 
preprocessed first (via `-E`) and
-  only then compiled; previously, this would result in the fix not being 
applied. (#GH160314)
+  libstdc++15 has been extended to support preprocessed input. Previously, 
splitting the preprocessing and
+  compilation step would result in the fix not being applied. (#GH160314)
 
 #### Bug Fixes to AST Handling
 

>From 6bdc1c9d794d1d2f5fec3ef7cbf77d6c32309f03 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 22 Jul 2026 21:01:42 +0200
Subject: [PATCH 3/5] use a pragma instead

---
 .../include/clang/Basic/DiagnosticLexKinds.td |  3 ++
 clang/include/clang/Lex/PPCallbacks.h         |  8 ++++
 clang/include/clang/Lex/Preprocessor.h        |  1 +
 .../lib/Frontend/PrintPreprocessedOutput.cpp  | 47 ++++++++++++++-----
 clang/lib/Lex/PPExpressions.cpp               |  7 +++
 clang/lib/Lex/Pragma.cpp                      | 25 ++++++++++
 clang/lib/Sema/SemaTemplate.cpp               | 14 ++++--
 .../pragma_clang_glibcxx_version_invalid.cpp  |  5 ++
 .../SemaCXX/libstdcxx_format_kind_hack.cpp    | 22 ++++++++-
 9 files changed, 116 insertions(+), 16 deletions(-)
 create mode 100644 
clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp

diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td 
b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 79e74a846e3ea..fd4e843601e3d 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -864,6 +864,9 @@ def err_pp_include_in_arc_cf_code_audited : Error<
 def err_pp_eof_in_arc_cf_code_audited : Error<
   "'#pragma clang arc_cf_code_audited' was not ended within this file">;
 
+def err_pp_pragma_glibcxx_version_requires_integer : Error<
+  "expected integer after '#pragma clang glibcxx_version'">;
+
 def warn_pp_date_time : Warning<
   "expansion of date or time macro is not reproducible">,
   ShowInSystemHeader, DefaultIgnore, InGroup<DiagGroup<"date-time">>;
diff --git a/clang/include/clang/Lex/PPCallbacks.h 
b/clang/include/clang/Lex/PPCallbacks.h
index 2f62fa8d52b1a..706f08f7c0735 100644
--- a/clang/include/clang/Lex/PPCallbacks.h
+++ b/clang/include/clang/Lex/PPCallbacks.h
@@ -342,6 +342,9 @@ class PPCallbacks {
   /// is read.
   virtual void PragmaAssumeNonNullEnd(SourceLocation Loc) {}
 
+  /// Callback invoked when a \#pragma clang glibcxx_version directive is read.
+  virtual void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) {}
+
   /// Called by Preprocessor::HandleMacroExpandedIdentifier when a
   /// macro invocation is found.
   virtual void MacroExpands(const Token &MacroNameTok,
@@ -700,6 +703,11 @@ class PPChainedCallbacks : public PPCallbacks {
     Second->PragmaAssumeNonNullEnd(Loc);
   }
 
+  void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override {
+    First->PragmaGLIBCXXVersion(Loc, Value);
+    Second->PragmaGLIBCXXVersion(Loc, Value);
+  }
+
   void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
                     SourceRange Range, const MacroArgs *Args) override {
     First->MacroExpands(MacroNameTok, MD, Range, Args);
diff --git a/clang/include/clang/Lex/Preprocessor.h 
b/clang/include/clang/Lex/Preprocessor.h
index 1c917dcfe7b7e..5038bc87b210c 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2818,6 +2818,7 @@ class Preprocessor {
 
 public:
   std::optional<std::uint64_t> getStdLibCxxVersion();
+  void setStdLibCxxVersion(std::uint64_t Version);
   bool NeedsStdLibCxxWorkaroundBefore(std::uint64_t FixedVersion);
 
 private:
diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp 
b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
index 02266882c4c4a..87b6ded660507 100644
--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -29,10 +29,12 @@
 using namespace clang;
 
 /// PrintMacroDefinition - Print a macro definition in a form that will be
-/// properly accepted back as a definition.
-static void PrintMacroDefinition(const IdentifierInfo &II, const MacroInfo &MI,
+/// properly accepted back as a definition. If 'II' is nullptr, only the
+/// expansion will be printed.
+static void PrintMacroDefinition(const IdentifierInfo *II, const MacroInfo &MI,
                                  Preprocessor &PP, raw_ostream *OS) {
-  *OS << "#define " << II.getName();
+  if (II)
+    *OS << "#define " << II->getName();
 
   if (MI.isFunctionLike()) {
     *OS << '(';
@@ -182,6 +184,7 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
   void PragmaExecCharsetPop(SourceLocation Loc) override;
   void PragmaAssumeNonNullBegin(SourceLocation Loc) override;
   void PragmaAssumeNonNullEnd(SourceLocation Loc) override;
+  void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override;
 
   /// Insert whitespace before emitting the next token.
   ///
@@ -559,22 +562,37 @@ void PrintPPOutputPPCallbacks::Ident(SourceLocation Loc, 
StringRef S) {
 /// MacroDefined - This hook is called whenever a macro definition is seen.
 void PrintPPOutputPPCallbacks::MacroDefined(const Token &MacroNameTok,
                                             const MacroDirective *MD) {
+  bool ShouldEmitDefine = true;
   const MacroInfo *MI = MD->getMacroInfo();
+  SourceLocation DefLoc = MI->getDefinitionLoc();
+
   // Print out macro definitions in -dD mode and when we have -fdirectives-only
   // for C++20 header units.
   if ((!DumpDefines && !DirectivesOnly) ||
       // Ignore __FILE__ etc.
-      MI->isBuiltinMacro())
-    return;
-
-  SourceLocation DefLoc = MI->getDefinitionLoc();
-  if (DirectivesOnly && !MI->isUsed()) {
+      MI->isBuiltinMacro()) {
+    ShouldEmitDefine = false;
+  } else if (DirectivesOnly && !MI->isUsed()) {
     SourceManager &SM = PP.getSourceManager();
     if (SM.isInPredefinedFile(DefLoc))
-      return;
+      ShouldEmitDefine = false;
   }
+
+  if (!ShouldEmitDefine) {
+    // Preserve '__GLIBCXX__' as a pragma if we shouldn't print '#define's; 
this
+    // is required for a number of workarounds in Sema (which are enabled
+    // depending on the value of this macro).
+    if (MacroNameTok.getIdentifierInfo()->getName() == "__GLIBCXX__") {
+      MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
+      *OS << "#pragma clang glibcxx_version";
+      PrintMacroDefinition(/*II=*/nullptr, *MI, PP, OS);
+      setEmittedDirectiveOnThisLine();
+    }
+    return;
+  }
+
   MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
-  PrintMacroDefinition(*MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
+  PrintMacroDefinition(MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
   setEmittedDirectiveOnThisLine();
 }
 
@@ -752,6 +770,13 @@ PragmaAssumeNonNullEnd(SourceLocation Loc) {
   setEmittedDirectiveOnThisLine();
 }
 
+void PrintPPOutputPPCallbacks::PragmaGLIBCXXVersion(SourceLocation Loc,
+                                                    std::uint64_t Value) {
+  MoveToLine(Loc, /*RequireStartOfLine=*/true);
+  *OS << "#pragma clang glibcxx_version " << Value;
+  setEmittedDirectiveOnThisLine();
+}
+
 void PrintPPOutputPPCallbacks::HandleWhitespaceBeforeTok(const Token &Tok,
                                                          bool RequireSpace,
                                                          bool RequireSameLine) 
{
@@ -1093,7 +1118,7 @@ static void DoPrintMacros(Preprocessor &PP, raw_ostream 
*OS) {
     // Ignore computed macros like __LINE__ and friends.
     if (MI.isBuiltinMacro()) continue;
 
-    PrintMacroDefinition(*MacrosByID[i].first, MI, PP, OS);
+    PrintMacroDefinition(MacrosByID[i].first, MI, PP, OS);
     *OS << '\n';
   }
 }
diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp
index 887fd25ac318d..50e7040291bc4 100644
--- a/clang/lib/Lex/PPExpressions.cpp
+++ b/clang/lib/Lex/PPExpressions.cpp
@@ -1018,6 +1018,13 @@ std::optional<uint64_t> 
Preprocessor::getStdLibCxxVersion() {
   return std::nullopt;
 }
 
+void Preprocessor::setStdLibCxxVersion(std::uint64_t Version) {
+  CXXStandardLibraryVersion = {
+      CXXStandardLibraryVersionInfo::LibStdCXX,
+      Version,
+  };
+}
+
 bool Preprocessor::NeedsStdLibCxxWorkaroundBefore(uint64_t FixedVersion) {
   assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
          "invalid value for __GLIBCXX__");
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 9b48a45ade668..6de348f9d63e5 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -2148,6 +2148,30 @@ struct PragmaFinalHandler : public PragmaHandler {
   }
 };
 
+/// "\#pragma clang glibcxx_version ..."
+///
+/// The syntax is
+/// \code
+///   #pragma clang glibcxx_version INTEGER
+/// \endcode
+struct PragmaGLIBCXXVersionHandler : PragmaHandler {
+  PragmaGLIBCXXVersionHandler() : PragmaHandler("glibcxx_version") {}
+  void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
+                    Token &Tok) override {
+    PP.Lex(Tok);
+    std::uint64_t Value;
+    if (Tok.is(tok::numeric_constant) &&
+        PP.parseSimpleIntegerLiteral(Tok, Value)) {
+      PP.setStdLibCxxVersion(Value);
+
+      if (PP.getPPCallbacks())
+        PP.getPPCallbacks()->PragmaGLIBCXXVersion(Introducer.Loc, Value);
+    } else {
+      PP.Diag(Tok.getLocation(),
+              diag::err_pp_pragma_glibcxx_version_requires_integer);
+    }
+  }
+};
 } // namespace
 
 /// RegisterBuiltinPragmas - Install the standard preprocessor pragmas:
@@ -2179,6 +2203,7 @@ void Preprocessor::RegisterBuiltinPragmas() {
   AddPragmaHandler("clang", new PragmaDeprecatedHandler());
   AddPragmaHandler("clang", new PragmaRestrictExpansionHandler());
   AddPragmaHandler("clang", new PragmaFinalHandler());
+  AddPragmaHandler("clang", new PragmaGLIBCXXVersionHandler());
 
   // #pragma clang module ...
   auto *ModuleHandler = new PragmaNamespace("module");
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 7268a9aa5b4ef..643392833759d 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4586,10 +4586,16 @@ static bool IsLibstdcxxStdFormatKind(Preprocessor &PP, 
VarDecl *Var) {
       !Var->getDeclContext()->isStdNamespace())
     return false;
 
-  // We don't check the libstdc++ version for this one since that doesn't work
-  // if a user preprocesses the input as a separate step (because __GLIBCXX__
-  // will not be defined).
-  return true;
+  // Checking old versions of libstdc++ is not needed because 15.1 is the first
+  // release in which users can access std::format_kind.
+  // We can use 20250520 as the final date, see the following commits.
+  // GCC releases/gcc-15 branch:
+  // https://gcc.gnu.org/g:fedf81ef7b98e5c9ac899b8641bb670746c51205
+  // https://gcc.gnu.org/g:53680c1aa92d9f78e8255fbf696c0ed36f160650
+  // GCC master branch:
+  // https://gcc.gnu.org/g:9361966d80f625c5accc25cbb439f0278dd8b278
+  // https://gcc.gnu.org/g:c65725eccbabf3b9b5965f27fff2d3b9f6c75930
+  return PP.NeedsStdLibCxxWorkaroundBefore(2025'05'20);
 }
 } // end anonymous namespace
 
diff --git a/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp 
b/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
new file mode 100644
index 0000000000000..fc613bc827497
--- /dev/null
+++ b/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 -E -verify %s
+
+#pragma clang glibcxx_version // expected-error {{expected integer after 
'#pragma clang glibcxx_version'}}
+#pragma clang glibcxx_version foo // expected-error {{expected integer after 
'#pragma clang glibcxx_version'}}
+#pragma clang glibcxx_version 100000000000000000000000000000 // expected-error 
{{expected integer after '#pragma clang glibcxx_version'}}
diff --git a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp 
b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
index 698b6ccf697e6..bff53344ef21b 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,6 +1,17 @@
+// '__GLIBCXX__' defined:
+//
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DDEFINE
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DDEFINE
+// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: FileCheck --input-file=%t.ii %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
+
+// Version set via pragma:
+//
 // RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
 // RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii
 // RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: FileCheck --input-file=%t.ii %s
 // RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
 
 // expected-no-diagnostics
@@ -8,8 +19,17 @@
 // Primary variable template std::format_kind is defined as followed since
 // libstdc++ 15.1, which triggers compilation error introduced by GH134522.
 // This file tests the workaround.
+//
+// Since the workaround relies on '__GLIBCXX__' being defined, we emit a pragma
+// that ensures '__GLIBCXX__' is defined if the user first preprocesses the 
file
+// with '-E' before passing the output of that back to Clang.
 
-#define __GLIBCXX__ 20250513
+// CHECK: #pragma clang glibcxx_version 20250513
+#ifdef DEFINE
+#   define __GLIBCXX__ 20250513
+#else
+#   pragma clang glibcxx_version 20250513
+#endif
 
 namespace std {
   template<typename _Rg>

>From 185353f6e466b811c7f4df8d631aa56966e2c52a Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Thu, 23 Jul 2026 22:00:21 +0200
Subject: [PATCH 4/5] Generalise the pragma

---
 .../include/clang/Basic/DiagnosticLexKinds.td |  8 ++-
 clang/include/clang/Lex/PPCallbacks.h         | 12 ++--
 clang/include/clang/Lex/Preprocessor.h        |  9 +++
 .../lib/Frontend/PrintPreprocessedOutput.cpp  | 24 ++++---
 clang/lib/Lex/PPExpressions.cpp               |  8 +--
 clang/lib/Lex/Pragma.cpp                      | 67 ++++++++++++++-----
 clang/lib/Lex/Preprocessor.cpp                |  3 +
 .../pragma_clang_glibcxx_version_invalid.cpp  |  5 --
 .../test/Preprocessor/pragma_set_pp_state.cpp | 27 ++++++++
 .../SemaCXX/libstdcxx_format_kind_hack.cpp    | 39 +++++++----
 10 files changed, 147 insertions(+), 55 deletions(-)
 delete mode 100644 
clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
 create mode 100644 clang/test/Preprocessor/pragma_set_pp_state.cpp

diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td 
b/clang/include/clang/Basic/DiagnosticLexKinds.td
index fd4e843601e3d..f6b98e8caeaff 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -864,8 +864,12 @@ def err_pp_include_in_arc_cf_code_audited : Error<
 def err_pp_eof_in_arc_cf_code_audited : Error<
   "'#pragma clang arc_cf_code_audited' was not ended within this file">;
 
-def err_pp_pragma_glibcxx_version_requires_integer : Error<
-  "expected integer after '#pragma clang glibcxx_version'">;
+def err_pp_pragma_set_pp_state_expected_name : Error<
+  "expected identifier after '#pragma clang __set_pp_state'">;
+def err_pp_pragma_set_pp_state_expected_int_after : Error<
+  "expected integer after '#pragma clang __set_pp_state %0'">;
+def err_pp_pragma_set_pp_state_invalid_arg : Error<
+  "invalid argument %0 in '#pragma clang __set_pp_state'">;
 
 def warn_pp_date_time : Warning<
   "expansion of date or time macro is not reproducible">,
diff --git a/clang/include/clang/Lex/PPCallbacks.h 
b/clang/include/clang/Lex/PPCallbacks.h
index 706f08f7c0735..5f117bab6848e 100644
--- a/clang/include/clang/Lex/PPCallbacks.h
+++ b/clang/include/clang/Lex/PPCallbacks.h
@@ -342,8 +342,9 @@ class PPCallbacks {
   /// is read.
   virtual void PragmaAssumeNonNullEnd(SourceLocation Loc) {}
 
-  /// Callback invoked when a \#pragma clang glibcxx_version directive is read.
-  virtual void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) {}
+  /// Callback invoked when a \#pragma clang __set_pp_state directive is read.
+  virtual void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+                                std::uint64_t Value) {}
 
   /// Called by Preprocessor::HandleMacroExpandedIdentifier when a
   /// macro invocation is found.
@@ -703,9 +704,10 @@ class PPChainedCallbacks : public PPCallbacks {
     Second->PragmaAssumeNonNullEnd(Loc);
   }
 
-  void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override {
-    First->PragmaGLIBCXXVersion(Loc, Value);
-    Second->PragmaGLIBCXXVersion(Loc, Value);
+  void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+                        std::uint64_t Value) override {
+    First->PragmaSetPPState(Loc, MacroName, Value);
+    Second->PragmaSetPPState(Loc, MacroName, Value);
   }
 
   void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
diff --git a/clang/include/clang/Lex/Preprocessor.h 
b/clang/include/clang/Lex/Preprocessor.h
index 5038bc87b210c..ec30c5b50ca59 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2979,6 +2979,10 @@ class Preprocessor {
   // Pragmas.
   void HandlePragmaDirective(PragmaIntroducer Introducer);
 
+  // Cached identifiers used to implement __set_pp_state.
+  IdentifierInfo *Ident__set_pp_state;
+  IdentifierInfo *Ident__GLIBCXX__;
+
 public:
   void HandlePragmaOnce(Token &OnceTok);
   void HandlePragmaMark(Token &MarkTok);
@@ -2990,8 +2994,13 @@ class Preprocessor {
   void HandlePragmaIncludeAlias(Token &Tok);
   void HandlePragmaModuleBuild(Token &Tok);
   void HandlePragmaHdrstop(Token &Tok);
+  void HandlePragmaSetPPState(PragmaIntroducer Introducer, Token& Tok);
   IdentifierInfo *ParsePragmaPushOrPopMacro(Token &Tok);
 
+  /// Check whether this is a macro name that can be used as an argument to
+  /// '#pragma clang __set_pp_state'.
+  bool isPragmaSetPPStateMacro(IdentifierInfo *II);
+
   // Return true and store the first token only if any CommentHandler
   // has inserted some tokens and getCommentRetentionState() is false.
   bool HandleComment(Token &result, SourceRange Comment);
diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp 
b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
index 87b6ded660507..b2452aa45cd45 100644
--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -184,7 +184,8 @@ class PrintPPOutputPPCallbacks : public PPCallbacks {
   void PragmaExecCharsetPop(SourceLocation Loc) override;
   void PragmaAssumeNonNullBegin(SourceLocation Loc) override;
   void PragmaAssumeNonNullEnd(SourceLocation Loc) override;
-  void PragmaGLIBCXXVersion(SourceLocation Loc, std::uint64_t Value) override;
+  void PragmaSetPPState(SourceLocation Loc, IdentifierInfo *MacroName,
+                        std::uint64_t Value) override;
 
   /// Insert whitespace before emitting the next token.
   ///
@@ -578,13 +579,14 @@ void PrintPPOutputPPCallbacks::MacroDefined(const Token 
&MacroNameTok,
       ShouldEmitDefine = false;
   }
 
+  IdentifierInfo *MacroName = MacroNameTok.getIdentifierInfo();
   if (!ShouldEmitDefine) {
-    // Preserve '__GLIBCXX__' as a pragma if we shouldn't print '#define's; 
this
-    // is required for a number of workarounds in Sema (which are enabled
-    // depending on the value of this macro).
-    if (MacroNameTok.getIdentifierInfo()->getName() == "__GLIBCXX__") {
+    // Preserve macro definitions of macros that can be used with
+    // '#pragma clang __set_pp_state' as pragmas if printing '#define's
+    // is disabled.
+    if (PP.isPragmaSetPPStateMacro(MacroName)) {
       MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
-      *OS << "#pragma clang glibcxx_version";
+      *OS << "#pragma clang __set_pp_state " << MacroName->getName();
       PrintMacroDefinition(/*II=*/nullptr, *MI, PP, OS);
       setEmittedDirectiveOnThisLine();
     }
@@ -592,7 +594,7 @@ void PrintPPOutputPPCallbacks::MacroDefined(const Token 
&MacroNameTok,
   }
 
   MoveToLine(DefLoc, /*RequireStartOfLine=*/true);
-  PrintMacroDefinition(MacroNameTok.getIdentifierInfo(), *MI, PP, OS);
+  PrintMacroDefinition(MacroName, *MI, PP, OS);
   setEmittedDirectiveOnThisLine();
 }
 
@@ -770,10 +772,12 @@ PragmaAssumeNonNullEnd(SourceLocation Loc) {
   setEmittedDirectiveOnThisLine();
 }
 
-void PrintPPOutputPPCallbacks::PragmaGLIBCXXVersion(SourceLocation Loc,
-                                                    std::uint64_t Value) {
+void PrintPPOutputPPCallbacks::PragmaSetPPState(SourceLocation Loc,
+                                                IdentifierInfo *MacroName,
+                                                std::uint64_t Value) {
   MoveToLine(Loc, /*RequireStartOfLine=*/true);
-  *OS << "#pragma clang glibcxx_version " << Value;
+  *OS << "#pragma clang __set_pp_state " << MacroName->getName() << " "
+      << Value;
   setEmittedDirectiveOnThisLine();
 }
 
diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp
index 50e7040291bc4..9f15abfdf4f1b 100644
--- a/clang/lib/Lex/PPExpressions.cpp
+++ b/clang/lib/Lex/PPExpressions.cpp
@@ -983,9 +983,9 @@ Preprocessor::EvaluateDirectiveExpression(IdentifierInfo 
*&IfNDefMacro,
 }
 
 static std::optional<CXXStandardLibraryVersionInfo>
-getCXXStandardLibraryVersion(Preprocessor &PP, StringRef MacroName,
+getCXXStandardLibraryVersion(Preprocessor &PP, IdentifierInfo *MacroName,
                              CXXStandardLibraryVersionInfo::Library Lib) {
-  MacroInfo *Macro = PP.getMacroInfo(PP.getIdentifierInfo(MacroName));
+  MacroInfo *Macro = PP.getMacroInfo(MacroName);
   if (!Macro || Macro->getNumTokens() != 1 || !Macro->isObjectLike())
     return std::nullopt;
 
@@ -1008,7 +1008,7 @@ getCXXStandardLibraryVersion(Preprocessor &PP, StringRef 
MacroName,
 std::optional<uint64_t> Preprocessor::getStdLibCxxVersion() {
   if (!CXXStandardLibraryVersion)
     CXXStandardLibraryVersion = getCXXStandardLibraryVersion(
-        *this, "__GLIBCXX__", CXXStandardLibraryVersionInfo::LibStdCXX);
+        *this, Ident__GLIBCXX__, CXXStandardLibraryVersionInfo::LibStdCXX);
   if (!CXXStandardLibraryVersion)
     return std::nullopt;
 
@@ -1027,7 +1027,7 @@ void Preprocessor::setStdLibCxxVersion(std::uint64_t 
Version) {
 
 bool Preprocessor::NeedsStdLibCxxWorkaroundBefore(uint64_t FixedVersion) {
   assert(FixedVersion >= 2000'00'00 && FixedVersion <= 2100'00'00 &&
-         "invalid value for __GLIBCXX__");
+         "invalid libstdc++ version number");
   std::optional<std::uint64_t> Ver = getStdLibCxxVersion();
   if (!Ver)
     return false;
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 6de348f9d63e5..c5bd6abff5766 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -913,6 +913,48 @@ void Preprocessor::HandlePragmaHdrstop(Token &Tok) {
     SkippingUntilPragmaHdrStop = false;
 }
 
+bool Preprocessor::isPragmaSetPPStateMacro(IdentifierInfo *MacroName) {
+  return MacroName == Ident__GLIBCXX__;
+}
+
+void Preprocessor::HandlePragmaSetPPState(PragmaIntroducer Introducer,
+                                          Token &Tok) {
+  // Lex the macro name we want to set.
+  LexUnexpandedToken(Tok);
+  if (!Tok.getIdentifierInfo()) {
+    Diag(Tok.getLocation(), diag::err_pp_pragma_set_pp_state_expected_name);
+    return;
+  }
+
+  IdentifierInfo *MacroName = Tok.getIdentifierInfo();
+  if (!isPragmaSetPPStateMacro(MacroName)) {
+    Diag(Tok.getLocation(), diag::err_pp_pragma_set_pp_state_invalid_arg)
+        << MacroName;
+    return;
+  }
+
+  // Lex the integer argument.
+  Lex(Tok);
+  std::uint64_t Value;
+  if (!Tok.is(tok::numeric_constant) ||
+      !parseSimpleIntegerLiteral(Tok, Value)) {
+    Diag(Tok.getLocation(), 
diag::err_pp_pragma_set_pp_state_expected_int_after)
+        // Don't pass an IdentifierInfo* here to avoid quoting.
+        << MacroName->getName();
+    return;
+  }
+
+  // Update the state.
+  if (MacroName == Ident__GLIBCXX__) {
+    setStdLibCxxVersion(Value);
+  } else {
+    llvm_unreachable("forgot to handle a possible argument to __set_pp_state");
+  }
+
+  if (Callbacks)
+    Callbacks->PragmaSetPPState(Introducer.Loc, MacroName, Value);
+}
+
 /// AddPragmaHandler - Add the specified pragma handler to the preprocessor.
 /// If 'Namespace' is non-null, then it is a token required to exist on the
 /// pragma line before the pragma string starts, e.g. "STDC" or "GCC".
@@ -2148,28 +2190,23 @@ struct PragmaFinalHandler : public PragmaHandler {
   }
 };
 
-/// "\#pragma clang glibcxx_version ..."
+/// "\#pragma clang __set_pp_state ..."
+///
+/// This pragma takes an identifier+value pair and sets some internal state in
+/// the compiler; it is intended primarily to preserve preprocessor state that
+/// is required for compilation to function properly across preprocessor runs
+/// if '-E' is used. This is an internal pragma that should not be used by
+/// users.
 ///
 /// The syntax is
 /// \code
-///   #pragma clang glibcxx_version INTEGER
+///   #pragma clang __set_pp_state glibcxx_version INTEGER
 /// \endcode
 struct PragmaGLIBCXXVersionHandler : PragmaHandler {
-  PragmaGLIBCXXVersionHandler() : PragmaHandler("glibcxx_version") {}
+  PragmaGLIBCXXVersionHandler() : PragmaHandler("__set_pp_state") {}
   void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
                     Token &Tok) override {
-    PP.Lex(Tok);
-    std::uint64_t Value;
-    if (Tok.is(tok::numeric_constant) &&
-        PP.parseSimpleIntegerLiteral(Tok, Value)) {
-      PP.setStdLibCxxVersion(Value);
-
-      if (PP.getPPCallbacks())
-        PP.getPPCallbacks()->PragmaGLIBCXXVersion(Introducer.Loc, Value);
-    } else {
-      PP.Diag(Tok.getLocation(),
-              diag::err_pp_pragma_glibcxx_version_requires_integer);
-    }
+    PP.HandlePragmaSetPPState(Introducer, Tok);
   }
 };
 } // namespace
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 427a3826b299a..7badfe812706d 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -159,6 +159,9 @@ Preprocessor::Preprocessor(const PreprocessorOptions 
&PPOpts,
     Ident_AbnormalTermination = nullptr;
   }
 
+  Ident__set_pp_state = getIdentifierInfo("__set_pp_state");
+  Ident__GLIBCXX__ = getIdentifierInfo("__GLIBCXX__");
+
   // Default incremental processing to -fincremental-extensions, clients can
   // override with `enableIncrementalProcessing` if desired.
   IncrementalProcessing = LangOpts.IncrementalExtensions;
diff --git a/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp 
b/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
deleted file mode 100644
index fc613bc827497..0000000000000
--- a/clang/test/Preprocessor/pragma_clang_glibcxx_version_invalid.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-// RUN: %clang_cc1 -E -verify %s
-
-#pragma clang glibcxx_version // expected-error {{expected integer after 
'#pragma clang glibcxx_version'}}
-#pragma clang glibcxx_version foo // expected-error {{expected integer after 
'#pragma clang glibcxx_version'}}
-#pragma clang glibcxx_version 100000000000000000000000000000 // expected-error 
{{expected integer after '#pragma clang glibcxx_version'}}
diff --git a/clang/test/Preprocessor/pragma_set_pp_state.cpp 
b/clang/test/Preprocessor/pragma_set_pp_state.cpp
new file mode 100644
index 0000000000000..763f23ce5868f
--- /dev/null
+++ b/clang/test/Preprocessor/pragma_set_pp_state.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -E -verify %s
+
+#pragma clang __set_pp_state // expected-error {{expected identifier after 
'#pragma clang __set_pp_state'}}
+#pragma clang __set_pp_state 123984 // expected-error {{expected identifier 
after '#pragma clang __set_pp_state'}}
+
+#pragma clang __set_pp_state foo // expected-error {{invalid argument 'foo' in 
'#pragma clang __set_pp_state'}}
+#pragma clang __set_pp_state void // expected-error {{invalid argument 'void' 
in '#pragma clang __set_pp_state'}}
+
+#pragma clang __set_pp_state __GLIBCXX__ foo // expected-error {{expected 
integer after '#pragma clang __set_pp_state __GLIBCXX__'}}
+#pragma clang __set_pp_state __GLIBCXX__ 100000000000000000000000000000 // 
expected-error {{expected integer after '#pragma clang __set_pp_state 
__GLIBCXX__'}}
+#pragma clang __set_pp_state __GLIBCXX__ 42.0 // expected-error {{expected 
integer after '#pragma clang __set_pp_state __GLIBCXX__'}}
+
+#pragma clang __set_pp_state __GLIBCXX__ 42L
+#pragma clang __set_pp_state __GLIBCXX__ 42
+
+// Check that we treat the identifier after '__set_pp_state' literally.
+#define MACRO __GLIBCXX__
+#pragma clang __set_pp_state MACRO // expected-error {{invalid argument 
'MACRO' in '#pragma clang __set_pp_state'}}
+
+// Check that we treat '__set_pp_state' literally.
+#define __set_pp_state foobar
+#pragma clang __set_pp_state __GLIBCXX__ 42
+
+// The pragma does *not* define __GLIBCXX__!
+#ifdef __GLIBCXX__
+#   error __set_pp_state __GLIBCXX__ should not define __GLIBCXX__
+#endif
diff --git a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp 
b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
index bff53344ef21b..b8cde0b6b2f2e 100644
--- a/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
+++ b/clang/test/SemaCXX/libstdcxx_format_kind_hack.cpp
@@ -1,19 +1,29 @@
-// '__GLIBCXX__' defined:
-//
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DDEFINE
-// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DDEFINE
-// RUN: echo '// expected-no-diagnostics' >> %t.ii
+// Check that we accept the program if '__GLIBCXX__' is defined:
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DDEFINE_GLIBCXX
+
+// Check that we preserve the value of __GLIBCXX__ via a pragma when 
preprocessing:
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DDEFINE_GLIBCXX
 // RUN: FileCheck --input-file=%t.ii %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
 
-// Version set via pragma:
-//
-// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s
-// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii
+// Check that the preprocessed file compiles with no diagnostics:
 // RUN: echo '// expected-no-diagnostics' >> %t.ii
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
+
+// Check that we accept the program if the pragma is present:
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %s -DUSE_PRAGMA
+
+// Check that we preserve the pragma when preprocessing:
+// RUN: %clang_cc1 -E -std=c++23 %s -o %t.ii -DUSE_PRAGMA
 // RUN: FileCheck --input-file=%t.ii %s
+
+// Check that the preprocessed file compiles with no diagnostics:
+// RUN: echo '// expected-no-diagnostics' >> %t.ii
 // RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify %t.ii
 
+// Irrespective of whether we used the pragma directly or defined __GLIBCXX__,
+// the preprocessed output should contain the pragma:
+// CHECK: #pragma clang __set_pp_state __GLIBCXX__ 20250513
+
 // expected-no-diagnostics
 
 // Primary variable template std::format_kind is defined as followed since
@@ -24,11 +34,12 @@
 // that ensures '__GLIBCXX__' is defined if the user first preprocesses the 
file
 // with '-E' before passing the output of that back to Clang.
 
-// CHECK: #pragma clang glibcxx_version 20250513
-#ifdef DEFINE
+#ifdef DEFINE_GLIBCXX
 #   define __GLIBCXX__ 20250513
-#else
-#   pragma clang glibcxx_version 20250513
+#endif
+
+#ifdef USE_PRAGMA
+#   pragma clang __set_pp_state __GLIBCXX__ 20250513
 #endif
 
 namespace std {

>From 31817288590026a2806f71cbb8bd2cdd1ff762d1 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Thu, 23 Jul 2026 22:00:29 +0200
Subject: [PATCH 5/5] clang-format

---
 clang/include/clang/Lex/Preprocessor.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Lex/Preprocessor.h 
b/clang/include/clang/Lex/Preprocessor.h
index ec30c5b50ca59..b01639d21010e 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -2994,7 +2994,7 @@ class Preprocessor {
   void HandlePragmaIncludeAlias(Token &Tok);
   void HandlePragmaModuleBuild(Token &Tok);
   void HandlePragmaHdrstop(Token &Tok);
-  void HandlePragmaSetPPState(PragmaIntroducer Introducer, Token& Tok);
+  void HandlePragmaSetPPState(PragmaIntroducer Introducer, Token &Tok);
   IdentifierInfo *ParsePragmaPushOrPopMacro(Token &Tok);
 
   /// Check whether this is a macro name that can be used as an argument to

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to