https://github.com/mysterymath updated 
https://github.com/llvm/llvm-project/pull/147431

>From 6101f2a38371cb6f9dd8c4c0e58447e787182fff Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 10 Jun 2025 14:06:53 -0700
Subject: [PATCH 01/21] [clang] "modular_format" attribute for functions using
 format strings

This provides a C language version of the new IR modular-format
attribute. This, in concert with the format attribute, allows a library
function to declare that a modular version of its implementation is
available.

See issue #146159 for context.
---
 clang/include/clang/Basic/Attr.td     | 11 +++++++++++
 clang/include/clang/Basic/AttrDocs.td | 25 +++++++++++++++++++++++++
 clang/lib/CodeGen/CGCall.cpp          | 12 ++++++++++++
 clang/lib/Sema/SemaDeclAttr.cpp       | 27 +++++++++++++++++++++++++++
 4 files changed, 75 insertions(+)

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index 8e5f7ef0bb82d..e40e46f2846c8 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5323,3 +5323,14 @@ def NonString : InheritableAttr {
   let Subjects = SubjectList<[Var, Field]>;
   let Documentation = [NonStringDocs];
 }
+
+def ModularFormat : InheritableAttr {
+  let Spellings = [Clang<"modular_format">];
+  let Args = [
+    IdentifierArgument<"ModularImplFn">,
+    StringArgument<"ImplName">,
+    VariadicStringArgument<"Aspects">
+  ];
+  let Subjects = SubjectList<[Function]>;
+  let Documentation = [ModularFormatDocs];
+}
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index c1b1510f363d4..f24674d7355ac 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9630,3 +9630,28 @@ silence diagnostics with code like:
   __attribute__((nonstring)) char NotAStr[3] = "foo"; // Not diagnosed
   }];
 }
+
+def ModularFormatDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+The ``modular_format`` attribute can be applied to a function that bears the
+``format`` attribute to indicate that the implementation is modular on the
+format string argument. When the format argument for a given call is constant,
+the compiler may redirect the call to the symbol given as the first argument to
+the attribute (the modular implementation function).
+
+The second argument is a implementation name, and the remaining arguments are
+aspects of the format string for the compiler to report. If the compiler does
+not understand a aspect, it must summarily report that the format string has
+that aspect.
+
+The compiler reports an aspect by issing a relocation for the symbol
+`<impl_name>_<aspect>``. This arranges for code and data needed to support the
+aspect of the implementation to be brought into the link to satisfy weak
+references in the modular implemenation function.
+
+The following aspects are currently supported:
+
+- ``float``: The call has a floating point argument
+  }];
+}
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index efacb3cc04c01..0a3ef36acbbc2 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2559,6 +2559,18 @@ void CodeGenModule::ConstructAttributeList(StringRef 
Name,
 
     if (TargetDecl->hasAttr<ArmLocallyStreamingAttr>())
       FuncAttrs.addAttribute("aarch64_pstate_sm_body");
+
+    if (auto *ModularFormat = TargetDecl->getAttr<ModularFormatAttr>()) {
+      // TODO: Error checking
+      FormatAttr *Format = TargetDecl->getAttr<FormatAttr>();
+      std::string FormatIdx = std::to_string(Format->getFormatIdx());
+      std::string FirstArg = std::to_string(Format->getFirstArg());
+      SmallVector<StringRef> Args = {
+          FormatIdx, FirstArg, ModularFormat->getModularImplFn()->getName(),
+          ModularFormat->getImplName()};
+      llvm::append_range(Args, ModularFormat->aspects());
+      FuncAttrs.addAttribute("modular-format", llvm::join(Args, ","));
+    }
   }
 
   // Attach "no-builtins" attributes to:
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index e3af5023c74d0..485b6876aa95f 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6973,6 +6973,29 @@ static void handleVTablePointerAuthentication(Sema &S, 
Decl *D,
       CustomDiscriminationValue));
 }
 
+static void handleModularFormat(Sema &S, Decl *D, const ParsedAttr &AL) {
+  StringRef ImplName;
+  if (!S.checkStringLiteralArgumentAttr(AL, 1, ImplName))
+    return;
+  SmallVector<StringRef> Aspects;
+  for (unsigned I = 2, E = AL.getNumArgs(); I != E; ++I) {
+    StringRef Aspect;
+    if (!S.checkStringLiteralArgumentAttr(AL, I, Aspect))
+      return;
+    Aspects.push_back(Aspect);
+  }
+
+  // Store aspects sorted and without duplicates.
+  llvm::sort(Aspects);
+  Aspects.erase(llvm::unique(Aspects), Aspects.end());
+
+  // TODO: Type checking on identifier
+  // TODO: Merge attributes
+  D->addAttr(::new (S.Context) ModularFormatAttr(
+      S.Context, AL, AL.getArgAsIdent(0)->getIdentifierInfo(), ImplName,
+      Aspects.data(), Aspects.size()));
+}
+
 
//===----------------------------------------------------------------------===//
 // Top Level Sema Entry Points
 
//===----------------------------------------------------------------------===//
@@ -7910,6 +7933,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_VTablePointerAuthentication:
     handleVTablePointerAuthentication(S, D, AL);
     break;
+
+  case ParsedAttr::AT_ModularFormat:
+    handleModularFormat(S, D, AL);
+    break;
   }
 }
 

>From 906b0b4c0b74d52fe120e0823354cb1d3cadb683 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 15 Jul 2025 11:28:20 -0700
Subject: [PATCH 02/21] Update docs to account for clang inferring format
 attribute

---
 clang/include/clang/Basic/AttrDocs.td | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index f24674d7355ac..31db4484c69d5 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9635,10 +9635,11 @@ def ModularFormatDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
 The ``modular_format`` attribute can be applied to a function that bears the
-``format`` attribute to indicate that the implementation is modular on the
-format string argument. When the format argument for a given call is constant,
-the compiler may redirect the call to the symbol given as the first argument to
-the attribute (the modular implementation function).
+``format`` attribute (or standard library functions) to indicate that the
+implementation is modular on the format string argument. When the format string
+for a given call is constant, the compiler may redirect the call to the symbol
+given as the first argument to the attribute (the modular implementation
+function).
 
 The second argument is a implementation name, and the remaining arguments are
 aspects of the format string for the compiler to report. If the compiler does

>From 4aaaa778fd8a159d95493ed253914aff7ef20ce4 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Wed, 16 Jul 2025 15:19:37 -0700
Subject: [PATCH 03/21] Add an example to clang attr doc

---
 clang/include/clang/Basic/AttrDocs.td | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 31db4484c69d5..1f25c90eb13d4 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9647,10 +9647,18 @@ not understand a aspect, it must summarily report that 
the format string has
 that aspect.
 
 The compiler reports an aspect by issing a relocation for the symbol
-`<impl_name>_<aspect>``. This arranges for code and data needed to support the
+``<impl_name>_<aspect>``. This arranges for code and data needed to support the
 aspect of the implementation to be brought into the link to satisfy weak
 references in the modular implemenation function.
 
+For example, say ``printf`` is annotated with
+``modular_format(__modular_printf, __printf, float)``. Then, a call to
+``printf(var, 42)`` would be untouched. A call to ``printf("%d", 42)`` would
+become a call to ``__modular_printf`` with the same arguments, as would
+``printf("%f", 42.0)``. The latter would be accompanied with a strong
+relocation against the symbol ``__printf_float``, which would bring floating
+point support for ``printf`` into the link.
+
 The following aspects are currently supported:
 
 - ``float``: The call has a floating point argument

>From 5ad6e9acb7a51f5c6ebac4189640d4edf343f14d Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 22 Jul 2025 13:35:46 -0700
Subject: [PATCH 04/21] Emit the new type arg from format attr

---
 clang/lib/CodeGen/CGCall.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 0a3ef36acbbc2..3b85a9db15bcf 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2563,10 +2563,12 @@ void CodeGenModule::ConstructAttributeList(StringRef 
Name,
     if (auto *ModularFormat = TargetDecl->getAttr<ModularFormatAttr>()) {
       // TODO: Error checking
       FormatAttr *Format = TargetDecl->getAttr<FormatAttr>();
+      StringRef Type = Format->getType()->getName();
       std::string FormatIdx = std::to_string(Format->getFormatIdx());
       std::string FirstArg = std::to_string(Format->getFirstArg());
       SmallVector<StringRef> Args = {
-          FormatIdx, FirstArg, ModularFormat->getModularImplFn()->getName(),
+          Type, FormatIdx, FirstArg,
+          ModularFormat->getModularImplFn()->getName(),
           ModularFormat->getImplName()};
       llvm::append_range(Args, ModularFormat->aspects());
       FuncAttrs.addAttribute("modular-format", llvm::join(Args, ","));

>From 8d9246f594899c7f04bc18674e7b7c8b444987e7 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 22 Jul 2025 15:01:56 -0700
Subject: [PATCH 05/21] Correct typos

---
 clang/include/clang/Basic/AttrDocs.td | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 1f25c90eb13d4..7e8cd3d93772e 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9643,10 +9643,10 @@ function).
 
 The second argument is a implementation name, and the remaining arguments are
 aspects of the format string for the compiler to report. If the compiler does
-not understand a aspect, it must summarily report that the format string has
+not understand an aspect, it must summarily report that the format string has
 that aspect.
 
-The compiler reports an aspect by issing a relocation for the symbol
+The compiler reports an aspect by issuing a relocation for the symbol
 ``<impl_name>_<aspect>``. This arranges for code and data needed to support the
 aspect of the implementation to be brought into the link to satisfy weak
 references in the modular implemenation function.

>From 4f1d07c3330f89c7e042a8f1cab77c13bc30d091 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 17 Jul 2025 15:56:10 -0700
Subject: [PATCH 06/21] Tests for successful format string passthrough

---
 clang/test/CodeGen/attr-modular-format.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
 create mode 100644 clang/test/CodeGen/attr-modular-format.c

diff --git a/clang/test/CodeGen/attr-modular-format.c 
b/clang/test/CodeGen/attr-modular-format.c
new file mode 100644
index 0000000000000..7d0580def41e9
--- /dev/null
+++ b/clang/test/CodeGen/attr-modular-format.c
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm %s -o - | 
FileCheck %s
+
+int printf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));
+int myprintf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2)));
+
+// CHECK-LABEL: define dso_local void @test_inferred_format(
+// CHECK:    {{.*}} = call i32 (ptr, ...) @printf(ptr noundef @.str) 
#[[ATTR:[0-9]+]]
+void test_inferred_format(void) {
+  printf("hello");
+}
+
+// CHECK-LABEL: define dso_local void @test_explicit_format(
+// CHECK:    {{.*}} = call i32 (ptr, ...) @myprintf(ptr noundef @.str) 
#[[ATTR:[0-9]+]]
+void test_explicit_format(void) {
+  myprintf("hello");
+}
+
+// CHECK: attributes #[[ATTR]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,float" }

>From ad4e4258818db3c1c48ff0e197a6bab6d31e2bcf Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Fri, 5 Sep 2025 17:10:37 -0700
Subject: [PATCH 07/21] Add redeclaration test

---
 clang/lib/Sema/SemaDeclAttr.cpp          |  1 -
 clang/test/CodeGen/attr-modular-format.c | 10 ++++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 485b6876aa95f..b897ed807e48c 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6990,7 +6990,6 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   Aspects.erase(llvm::unique(Aspects), Aspects.end());
 
   // TODO: Type checking on identifier
-  // TODO: Merge attributes
   D->addAttr(::new (S.Context) ModularFormatAttr(
       S.Context, AL, AL.getArgAsIdent(0)->getIdentifierInfo(), ImplName,
       Aspects.data(), Aspects.size()));
diff --git a/clang/test/CodeGen/attr-modular-format.c 
b/clang/test/CodeGen/attr-modular-format.c
index 7d0580def41e9..2c647214b3bca 100644
--- a/clang/test/CodeGen/attr-modular-format.c
+++ b/clang/test/CodeGen/attr-modular-format.c
@@ -15,4 +15,14 @@ void test_explicit_format(void) {
   myprintf("hello");
 }
 
+int redecl(const char *fmt, ...) __attribute__((modular_format(__first_impl, 
"__first", "one"), format(printf, 1, 2)));
+int redecl(const char *fmt, ...) __attribute__((modular_format(__second_impl, 
"__second", "two", "three")));
+
+// CHECK-LABEL: define dso_local void @test_redecl(
+// CHECK:    {{.*}} = call i32 (ptr, ...) @redecl(ptr noundef @.str) 
#[[ATTR_REDECL:[0-9]+]]
+void test_redecl(void) {
+  redecl("hello");
+}
+
 // CHECK: attributes #[[ATTR]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,float" }
+// CHECK: attributes #[[ATTR_REDECL]] = { 
"modular-format"="printf,1,2,__second_impl,__second,three,two" }

>From ad78cf89fc6d1ef25e0379b32747581c1da00f70 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 3 Nov 2025 16:48:55 -0800
Subject: [PATCH 08/21] Clarify and correct docs

---
 clang/include/clang/Basic/AttrDocs.td | 23 ++++++++++++-----------
 clang/lib/Sema/SemaDeclAttr.cpp       |  1 -
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 7e8cd3d93772e..db231d6b9ae3b 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9636,23 +9636,24 @@ def ModularFormatDocs : Documentation {
   let Content = [{
 The ``modular_format`` attribute can be applied to a function that bears the
 ``format`` attribute (or standard library functions) to indicate that the
-implementation is modular on the format string argument. When the format string
-for a given call is constant, the compiler may redirect the call to the symbol
-given as the first argument to the attribute (the modular implementation
-function).
+implementation is "modular", that is, that the implemenation is logically
+divided into a number of named aspects. When the compiler can determine that
+not all aspects of the implementation are needed for a given call, the compiler
+may redirect the call to the identifier given as the first argument to the
+attribute (the modular implementation function).
 
 The second argument is a implementation name, and the remaining arguments are
 aspects of the format string for the compiler to report. If the compiler does
-not understand an aspect, it must summarily report that the format string has
-that aspect.
+not understand an aspect, it must summarily consider any call to require that
+aspect. 
 
-The compiler reports an aspect by issuing a relocation for the symbol
-``<impl_name>_<aspect>``. This arranges for code and data needed to support the
-aspect of the implementation to be brought into the link to satisfy weak
-references in the modular implemenation function.
+The compiler reports that a call requires an aspect by issuing a relocation for
+the symbol ``<impl_name>_<aspect>`` at the point of the call. This arranges for
+code and data needed to support the aspect of the implementation to be brought
+into the link to satisfy weak references in the modular implemenation function.
 
 For example, say ``printf`` is annotated with
-``modular_format(__modular_printf, __printf, float)``. Then, a call to
+``modular_format(__modular_printf, "__printf", "float")``. Then, a call to
 ``printf(var, 42)`` would be untouched. A call to ``printf("%d", 42)`` would
 become a call to ``__modular_printf`` with the same arguments, as would
 ``printf("%f", 42.0)``. The latter would be accompanied with a strong
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index b897ed807e48c..0c93106596c46 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6989,7 +6989,6 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   llvm::sort(Aspects);
   Aspects.erase(llvm::unique(Aspects), Aspects.end());
 
-  // TODO: Type checking on identifier
   D->addAttr(::new (S.Context) ModularFormatAttr(
       S.Context, AL, AL.getArgAsIdent(0)->getIdentifierInfo(), ImplName,
       Aspects.data(), Aspects.size()));

>From 0afe987f9c9a427398eb8f4528c06143ccbd8653 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 4 Nov 2025 13:17:47 -0800
Subject: [PATCH 09/21] Emit error for missing format attribute

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 3 +++
 clang/lib/Sema/SemaDecl.cpp                      | 6 ++++++
 clang/test/Sema/attr-modular-format.c            | 7 +++++++
 3 files changed, 16 insertions(+)
 create mode 100644 clang/test/Sema/attr-modular-format.c

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 53aa86a7dabde..b8386eda89cb3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13063,6 +13063,9 @@ def err_get_vtable_pointer_requires_complete_type
     : Error<"__builtin_get_vtable_pointer requires an argument with a complete 
"
             "type, but %0 is incomplete">;
 
+def err_modular_format_attribute_no_format : Error<
+    "'modular_format' attribute requires 'format' attribute">;
+
 // SYCL-specific diagnostics
 def warn_sycl_kernel_num_of_template_params : Warning<
   "'sycl_kernel' attribute only applies to a function template with at least"
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 651437a6f4c30..89485e2850381 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7217,6 +7217,11 @@ static void checkLifetimeBoundAttr(Sema &S, NamedDecl 
&ND) {
   }
 }
 
+static void checkModularFormatAttr(Sema &S, NamedDecl &ND) {
+  if (ND.hasAttr<ModularFormatAttr>() && !ND.hasAttr<FormatAttr>())
+    S.Diag(ND.getLocation(), diag::err_modular_format_attribute_no_format);
+}
+
 static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
   // Ensure that an auto decl is deduced otherwise the checks below might cache
   // the wrong linkage.
@@ -7229,6 +7234,7 @@ static void checkAttributesAfterMerging(Sema &S, 
NamedDecl &ND) {
   checkHybridPatchableAttr(S, ND);
   checkInheritableAttr(S, ND);
   checkLifetimeBoundAttr(S, ND);
+  checkModularFormatAttr(S, ND);
 }
 
 static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl,
diff --git a/clang/test/Sema/attr-modular-format.c 
b/clang/test/Sema/attr-modular-format.c
new file mode 100644
index 0000000000000..46811b980f854
--- /dev/null
+++ b/clang/test/Sema/attr-modular-format.c
@@ -0,0 +1,7 @@
+//RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#include <stdarg.h>
+
+int printf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));  // 
no-error
+int myprintf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));  // 
expected-error {{'modular_format' attribute requires 'format' attribute}}
+

>From afd92f69dd97478953a890374083f5e7034e7aae Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 4 Nov 2025 16:38:55 -0800
Subject: [PATCH 10/21] Clarify semantics

---
 clang/include/clang/Basic/AttrDocs.td | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index db231d6b9ae3b..4f9efc39e5115 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9643,14 +9643,15 @@ may redirect the call to the identifier given as the 
first argument to the
 attribute (the modular implementation function).
 
 The second argument is a implementation name, and the remaining arguments are
-aspects of the format string for the compiler to report. If the compiler does
-not understand an aspect, it must summarily consider any call to require that
-aspect. 
+aspects of the format string for the compiler to report. The implementation
+name is an unevaluted identifier be in the C namespace.
 
 The compiler reports that a call requires an aspect by issuing a relocation for
 the symbol ``<impl_name>_<aspect>`` at the point of the call. This arranges for
 code and data needed to support the aspect of the implementation to be brought
 into the link to satisfy weak references in the modular implemenation function.
+If the compiler does not understand an aspect, it must summarily consider any
+call to require that aspect. 
 
 For example, say ``printf`` is annotated with
 ``modular_format(__modular_printf, "__printf", "float")``. Then, a call to

>From 56183a3bbe8979628b38fce5729dc6124c29336a Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 4 Nov 2025 16:40:11 -0800
Subject: [PATCH 11/21] clang-format

---
 clang/include/clang/Basic/Attr.td                | 7 ++-----
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 4 ++--
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index e40e46f2846c8..73b6b2688cb48 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5326,11 +5326,8 @@ def NonString : InheritableAttr {
 
 def ModularFormat : InheritableAttr {
   let Spellings = [Clang<"modular_format">];
-  let Args = [
-    IdentifierArgument<"ModularImplFn">,
-    StringArgument<"ImplName">,
-    VariadicStringArgument<"Aspects">
-  ];
+  let Args = [IdentifierArgument<"ModularImplFn">, StringArgument<"ImplName">,
+              VariadicStringArgument<"Aspects">];
   let Subjects = SubjectList<[Function]>;
   let Documentation = [ModularFormatDocs];
 }
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b8386eda89cb3..2bb86ffef17da 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13063,8 +13063,8 @@ def err_get_vtable_pointer_requires_complete_type
     : Error<"__builtin_get_vtable_pointer requires an argument with a complete 
"
             "type, but %0 is incomplete">;
 
-def err_modular_format_attribute_no_format : Error<
-    "'modular_format' attribute requires 'format' attribute">;
+def err_modular_format_attribute_no_format
+    : Error<"'modular_format' attribute requires 'format' attribute">;
 
 // SYCL-specific diagnostics
 def warn_sycl_kernel_num_of_template_params : Warning<

>From 46f88d58be82c248e92a4f6412f14ead51073ab3 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 4 Nov 2025 16:44:44 -0800
Subject: [PATCH 12/21] Add a blurb to the release notes

---
 clang/docs/ReleaseNotes.rst | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 51f07256c5d9f..3fb4aa54e75f9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -356,6 +356,11 @@ Attribute Changes in Clang
   attribute, but `malloc_span` applies not to functions returning pointers, 
but to functions returning
   span-like structures (i.e. those that contain a pointer field and a size 
integer field or two pointers).
 
+- New attribute ``modular_format`` to allow dynamically selecting at link time
+  which aspects of a statically linked libc's printf (et al) implementation are
+  required. This can reduce code size without requiring e.g. multilibs for
+  printf features. Requires cooperation with the libc implementation.
+
 Improvements to Clang's diagnostics
 -----------------------------------
 - Diagnostics messages now refer to ``structured binding`` instead of 
``decomposition``,

>From 5e234479ab25879d17d6020363d21eb96e9b29bd Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Thu, 6 Nov 2025 14:31:24 -0800
Subject: [PATCH 13/21] Update newly-failing test from merge

---
 clang/test/Misc/pragma-attribute-supported-attributes-list.test | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test 
b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 747eb17446c87..1737516e2e734 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -110,6 +110,7 @@
 // CHECK-NEXT: Mips16 (SubjectMatchRule_function)
 // CHECK-NEXT: MipsLongCall (SubjectMatchRule_function)
 // CHECK-NEXT: MipsShortCall (SubjectMatchRule_function)
+// CHECK-NEXT: ModularFormat (SubjectMatchRule_function)
 // CHECK-NEXT: NSConsumed (SubjectMatchRule_variable_is_parameter)
 // CHECK-NEXT: NSConsumesSelf (SubjectMatchRule_objc_method)
 // CHECK-NEXT: NSErrorDomain (SubjectMatchRule_enum)

>From 25675a6d88ec648acee09c4f8069e74d1f132d25 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Wed, 12 Nov 2025 13:21:45 -0800
Subject: [PATCH 14/21] Correct typos and misc nits

---
 clang/docs/ReleaseNotes.rst           | 9 +++++----
 clang/include/clang/Basic/AttrDocs.td | 4 ++--
 clang/lib/CodeGen/CGCall.cpp          | 1 -
 clang/test/Sema/attr-modular-format.c | 2 --
 4 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3fb4aa54e75f9..21500db27ae0c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -356,10 +356,11 @@ Attribute Changes in Clang
   attribute, but `malloc_span` applies not to functions returning pointers, 
but to functions returning
   span-like structures (i.e. those that contain a pointer field and a size 
integer field or two pointers).
 
-- New attribute ``modular_format`` to allow dynamically selecting at link time
-  which aspects of a statically linked libc's printf (et al) implementation are
-  required. This can reduce code size without requiring e.g. multilibs for
-  printf features. Requires cooperation with the libc implementation.
+- Added new attribute ``modular_format`` to allow dynamically selecting at link
+  time which aspects of a statically linked libc's printf (et al)
+  implementation are required. This can reduce code size without requiring e.g.
+  multilibs for printf features. Requires cooperation with the libc
+  implementation.
 
 Improvements to Clang's diagnostics
 -----------------------------------
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 4f9efc39e5115..51f009fba8b46 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9636,7 +9636,7 @@ def ModularFormatDocs : Documentation {
   let Content = [{
 The ``modular_format`` attribute can be applied to a function that bears the
 ``format`` attribute (or standard library functions) to indicate that the
-implementation is "modular", that is, that the implemenation is logically
+implementation is "modular", that is, that the implementation is logically
 divided into a number of named aspects. When the compiler can determine that
 not all aspects of the implementation are needed for a given call, the compiler
 may redirect the call to the identifier given as the first argument to the
@@ -9644,7 +9644,7 @@ attribute (the modular implementation function).
 
 The second argument is a implementation name, and the remaining arguments are
 aspects of the format string for the compiler to report. The implementation
-name is an unevaluted identifier be in the C namespace.
+name is an unevaluated identifier be in the C namespace.
 
 The compiler reports that a call requires an aspect by issuing a relocation for
 the symbol ``<impl_name>_<aspect>`` at the point of the call. This arranges for
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 3b85a9db15bcf..4a9025b6e0b0f 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2561,7 +2561,6 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
       FuncAttrs.addAttribute("aarch64_pstate_sm_body");
 
     if (auto *ModularFormat = TargetDecl->getAttr<ModularFormatAttr>()) {
-      // TODO: Error checking
       FormatAttr *Format = TargetDecl->getAttr<FormatAttr>();
       StringRef Type = Format->getType()->getName();
       std::string FormatIdx = std::to_string(Format->getFormatIdx());
diff --git a/clang/test/Sema/attr-modular-format.c 
b/clang/test/Sema/attr-modular-format.c
index 46811b980f854..3cc6fac29def7 100644
--- a/clang/test/Sema/attr-modular-format.c
+++ b/clang/test/Sema/attr-modular-format.c
@@ -1,7 +1,5 @@
 //RUN: %clang_cc1 -fsyntax-only -verify %s
 
-#include <stdarg.h>
-
 int printf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));  // 
no-error
 int myprintf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));  // 
expected-error {{'modular_format' attribute requires 'format' attribute}}
 

>From 36b4e84b48cc616757f8728f8e8eff185256a195 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 1 Dec 2025 12:31:31 -0800
Subject: [PATCH 15/21] Add modular_format attribute sorting test

---
 clang/test/CodeGen/attr-modular-format.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/clang/test/CodeGen/attr-modular-format.c 
b/clang/test/CodeGen/attr-modular-format.c
index 2c647214b3bca..bda958e83a3dd 100644
--- a/clang/test/CodeGen/attr-modular-format.c
+++ b/clang/test/CodeGen/attr-modular-format.c
@@ -24,5 +24,17 @@ void test_redecl(void) {
   redecl("hello");
 }
 
+int order1(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "a", "b"), 
format(printf, 1, 2)));
+int order2(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "b", "a"), 
format(printf, 1, 2)));
+
+// CHECK-LABEL: define dso_local void @test_order(
+// CHECK:    {{.*}} = call i32 (ptr, ...) @order1(ptr noundef @.str) 
#[[ATTR_ORDER:[0-9]+]]
+// CHECK:    {{.*}} = call i32 (ptr, ...) @order2(ptr noundef @.str) 
#[[ATTR_ORDER]]
+void test_order(void) {
+  order1("hello");
+  order2("hello");
+}
+
 // CHECK: attributes #[[ATTR]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,float" }
 // CHECK: attributes #[[ATTR_REDECL]] = { 
"modular-format"="printf,1,2,__second_impl,__second,three,two" }
+// CHECK: attributes #[[ATTR_ORDER]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,a,b" }

>From 299c3640e02fc2478a50d4960a38937b304f9ea9 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 1 Dec 2025 12:37:50 -0800
Subject: [PATCH 16/21] Add error for duplicate modular_format aspects and
 tests

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  3 +++
 clang/lib/Sema/SemaDeclAttr.cpp                  | 15 +++++++++++++--
 clang/test/Sema/attr-modular-format.c            |  4 ++++
 3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2bb86ffef17da..e14bb5cad2564 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13066,6 +13066,9 @@ def err_get_vtable_pointer_requires_complete_type
 def err_modular_format_attribute_no_format
     : Error<"'modular_format' attribute requires 'format' attribute">;
 
+def err_modular_format_duplicate_aspect
+    : Error<"duplicate aspect '%0' in 'modular_format' attribute">;
+
 // SYCL-specific diagnostics
 def warn_sycl_kernel_num_of_template_params : Warning<
   "'sycl_kernel' attribute only applies to a function template with at least"
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 0c93106596c46..9ee8e3f37c520 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6978,16 +6978,27 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   if (!S.checkStringLiteralArgumentAttr(AL, 1, ImplName))
     return;
   SmallVector<StringRef> Aspects;
+  llvm::DenseSet<StringRef> SeenAspects;
+  bool HasDuplicate = false;
   for (unsigned I = 2, E = AL.getNumArgs(); I != E; ++I) {
     StringRef Aspect;
     if (!S.checkStringLiteralArgumentAttr(AL, I, Aspect))
       return;
+    if (!SeenAspects.insert(Aspect).second) {
+      S.Diag(AL.getArgAsExpr(I)->getExprLoc(),
+             diag::err_modular_format_duplicate_aspect)
+          << Aspect;
+      HasDuplicate = true;
+      continue;
+    }
     Aspects.push_back(Aspect);
   }
 
-  // Store aspects sorted and without duplicates.
+  if (HasDuplicate)
+    return;
+
+  // Store aspects sorted.
   llvm::sort(Aspects);
-  Aspects.erase(llvm::unique(Aspects), Aspects.end());
 
   D->addAttr(::new (S.Context) ModularFormatAttr(
       S.Context, AL, AL.getArgAsIdent(0)->getIdentifierInfo(), ImplName,
diff --git a/clang/test/Sema/attr-modular-format.c 
b/clang/test/Sema/attr-modular-format.c
index 3cc6fac29def7..fdaf3a25c182b 100644
--- a/clang/test/Sema/attr-modular-format.c
+++ b/clang/test/Sema/attr-modular-format.c
@@ -3,3 +3,7 @@
 int printf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));  // 
no-error
 int myprintf(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float")));  // 
expected-error {{'modular_format' attribute requires 'format' attribute}}
 
+int dupe(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float", "int", 
"float"), format(printf, 1, 2))); // expected-error {{duplicate aspect 'float' 
in 'modular_format' attribute}}
+int multi_dupe(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float", "int", 
"float", "int"), format(printf, 1, 2))); // expected-error {{duplicate aspect 
'float' in 'modular_format' attribute}} \
+                                                                               
                                                                                
  // expected-error {{duplicate aspect 'int' in 'modular_format' attribute}}
+

>From 7272717427988a6759bb79ce27da226d64501b14 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 1 Dec 2025 14:48:40 -0800
Subject: [PATCH 17/21] Warn about duplicate attributes

---
 clang/lib/Sema/SemaDeclAttr.cpp       | 30 ++++++++++++++++++++++++++-
 clang/test/Sema/attr-modular-format.c | 12 +++++++++++
 2 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 9ee8e3f37c520..76bea8c3990f4 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6973,6 +6973,24 @@ static void handleVTablePointerAuthentication(Sema &S, 
Decl *D,
       CustomDiscriminationValue));
 }
 
+static bool modularFormatIsSame(const ModularFormatAttr *Existing,
+                                IdentifierInfo *ModularImplFn,
+                                StringRef ImplName,
+                                ArrayRef<StringRef> Aspects) {
+  if (Existing->getModularImplFn() != ModularImplFn)
+    return false;
+  if (Existing->getImplName() != ImplName)
+    return false;
+  if (Existing->aspects_size() != Aspects.size())
+    return false;
+  unsigned I = 0;
+  for (const auto &ExistingAspect : Existing->aspects()) {
+    if (ExistingAspect != Aspects[I++])
+      return false;
+  }
+  return true;
+}
+
 static void handleModularFormat(Sema &S, Decl *D, const ParsedAttr &AL) {
   StringRef ImplName;
   if (!S.checkStringLiteralArgumentAttr(AL, 1, ImplName))
@@ -7000,8 +7018,18 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   // Store aspects sorted.
   llvm::sort(Aspects);
 
+  IdentifierInfo *ModularImplFn = AL.getArgAsIdent(0)->getIdentifierInfo();
+
+  if (const auto *Existing = D->getAttr<ModularFormatAttr>()) {
+    if (!modularFormatIsSame(Existing, ModularImplFn, ImplName, Aspects)) {
+      S.Diag(AL.getLoc(), diag::warn_duplicate_attribute) << AL;
+      S.Diag(Existing->getLocation(), diag::note_conflicting_attribute);
+    }
+    D->dropAttr<ModularFormatAttr>();
+  }
+
   D->addAttr(::new (S.Context) ModularFormatAttr(
-      S.Context, AL, AL.getArgAsIdent(0)->getIdentifierInfo(), ImplName,
+      S.Context, AL, ModularImplFn, ImplName,
       Aspects.data(), Aspects.size()));
 }
 
diff --git a/clang/test/Sema/attr-modular-format.c 
b/clang/test/Sema/attr-modular-format.c
index fdaf3a25c182b..d05d0ed581e74 100644
--- a/clang/test/Sema/attr-modular-format.c
+++ b/clang/test/Sema/attr-modular-format.c
@@ -7,3 +7,15 @@ int dupe(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf,
 int multi_dupe(const char *fmt, ...)  
__attribute__((modular_format(__modular_printf, "__printf", "float", "int", 
"float", "int"), format(printf, 1, 2))); // expected-error {{duplicate aspect 
'float' in 'modular_format' attribute}} \
                                                                                
                                                                                
  // expected-error {{duplicate aspect 'int' in 'modular_format' attribute}}
 
+// Test with multiple identical attributes on the same declaration.
+int same_attr(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
modular_format(__modular_printf, "__printf", "float"), format(printf, 1, 2))); 
// no-warning
+
+// Test with multiple different attributes on the same declaration.
+int diff_attr(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__modular_printf, "__printf", "int"))); // 
expected-warning {{attribute 'modular_format' is already applied with different 
arguments}} expected-note {{conflicting attribute is here}}
+
+int diff_attr2(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__modular_printf, "__other", "float"))); 
// expected-warning {{attribute 'modular_format' is already applied with 
different arguments}} expected-note {{conflicting attribute is here}}
+
+int diff_attr3(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__other, "__printf", "float"))); // 
expected-warning {{attribute 'modular_format' is already applied with different 
arguments}} expected-note {{conflicting attribute is here}}
+
+// Test with same attributes but different aspect order.
+int diff_order(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float", "int"), 
format(printf, 1, 2), modular_format(__modular_printf, "__printf", "int", 
"float"))); // no-warning

>From c067ccea6c8717a5caff7a5831851e76c816a836 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 1 Dec 2025 16:24:21 -0800
Subject: [PATCH 18/21] clang-format

---
 clang/lib/Sema/SemaDeclAttr.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 76bea8c3990f4..f39de42184c2d 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7029,8 +7029,7 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   }
 
   D->addAttr(::new (S.Context) ModularFormatAttr(
-      S.Context, AL, ModularImplFn, ImplName,
-      Aspects.data(), Aspects.size()));
+      S.Context, AL, ModularImplFn, ImplName, Aspects.data(), Aspects.size()));
 }
 
 
//===----------------------------------------------------------------------===//

>From a1c9f84de7436af5bce3be7e95f3a050db2c1d28 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 1 Dec 2025 16:28:44 -0800
Subject: [PATCH 19/21] Minor cleanup

---
 clang/lib/Sema/SemaDeclAttr.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index f39de42184c2d..c8d816a03f0b5 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7012,9 +7012,6 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
     Aspects.push_back(Aspect);
   }
 
-  if (HasDuplicate)
-    return;
-
   // Store aspects sorted.
   llvm::sort(Aspects);
 
@@ -7028,6 +7025,9 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
     D->dropAttr<ModularFormatAttr>();
   }
 
+  if (HasDuplicate)
+    return;
+
   D->addAttr(::new (S.Context) ModularFormatAttr(
       S.Context, AL, ModularImplFn, ImplName, Aspects.data(), Aspects.size()));
 }

>From 7cbf5b7da351f541a754369c926f364db2db79f5 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Mon, 1 Dec 2025 16:31:44 -0800
Subject: [PATCH 20/21] Add a codegen test for the overwrite behavior

---
 clang/test/CodeGen/attr-modular-format.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/clang/test/CodeGen/attr-modular-format.c 
b/clang/test/CodeGen/attr-modular-format.c
index bda958e83a3dd..e38287ee4a6a6 100644
--- a/clang/test/CodeGen/attr-modular-format.c
+++ b/clang/test/CodeGen/attr-modular-format.c
@@ -35,6 +35,15 @@ void test_order(void) {
   order2("hello");
 }
 
+int overwrite(const char *fmt, ...) __attribute__((modular_format(__impl1, 
"__name1", "1"), modular_format(__impl2, "__name2", "2"), format(printf, 1, 
2)));
+
+// CHECK-LABEL: define dso_local void @test_overwrite(
+// CHECK:    {{.*}} = call i32 (ptr, ...) @overwrite(ptr noundef @.str) 
#[[ATTR_OVERWRITE:[0-9]+]]
+void test_overwrite(void) {
+  overwrite("hello");
+}
+
 // CHECK: attributes #[[ATTR]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,float" }
 // CHECK: attributes #[[ATTR_REDECL]] = { 
"modular-format"="printf,1,2,__second_impl,__second,three,two" }
 // CHECK: attributes #[[ATTR_ORDER]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,a,b" }
+// CHECK: attributes #[[ATTR_OVERWRITE]] = { 
"modular-format"="printf,1,2,__impl2,__name2,2" }

>From ffc8742ff1a624c18a8ca143146efe109def179d Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <[email protected]>
Date: Tue, 2 Dec 2025 10:31:52 -0800
Subject: [PATCH 21/21] Allow only duplicates, whether on the same decl or
 redecls

---
 clang/include/clang/Basic/AttrDocs.td         |  4 ++
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/include/clang/Sema/Sema.h               |  5 ++
 clang/lib/Sema/SemaDecl.cpp                   |  5 ++
 clang/lib/Sema/SemaDeclAttr.cpp               | 60 +++++++++++--------
 clang/test/CodeGen/attr-modular-format.c      | 20 +++----
 clang/test/Sema/attr-modular-format.c         | 13 ++--
 7 files changed, 69 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 51f009fba8b46..f8248f295d665 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9661,6 +9661,10 @@ become a call to ``__modular_printf`` with the same 
arguments, as would
 relocation against the symbol ``__printf_float``, which would bring floating
 point support for ``printf`` into the link.
 
+If the attribute appears more than once on a declaration, or across a chain of
+redeclarations, it is an error for the attributes to have different arguments,
+excepting that the aspects may be in any order.
+
 The following aspects are currently supported:
 
 - ``float``: The call has a floating point argument
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e14bb5cad2564..2b85148c35bde 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11270,6 +11270,8 @@ def warn_duplicate_attribute_exact : Warning<
 def warn_duplicate_attribute : Warning<
   "attribute %0 is already applied with different arguments">,
   InGroup<IgnoredAttributes>;
+def err_duplicate_attribute
+    : Error<"attribute %0 is already applied with different arguments">;
 def err_disallowed_duplicate_attribute : Error<
   "attribute %0 cannot appear more than once on a declaration">;
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ae500139ee6f7..7dbf0fa654047 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4957,6 +4957,11 @@ class Sema final : public SemaBase {
                                             IdentifierInfo *Format,
                                             int FormatIdx,
                                             StringLiteral *FormatStr);
+  ModularFormatAttr *mergeModularFormatAttr(Decl *D,
+                                            const AttributeCommonInfo &CI,
+                                            IdentifierInfo *ModularImplFn,
+                                            StringRef ImplName,
+                                            MutableArrayRef<StringRef> 
Aspects);
 
   /// AddAlignedAttr - Adds an aligned attribute to a particular declaration.
   void AddAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 89485e2850381..f07c187804af0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -58,6 +58,7 @@
 #include "clang/Sema/SemaSwift.h"
 #include "clang/Sema/SemaWasm.h"
 #include "clang/Sema/Template.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallPtrSet.h"
@@ -2901,6 +2902,10 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D,
   else if (const auto *FMA = dyn_cast<FormatMatchesAttr>(Attr))
     NewAttr = S.mergeFormatMatchesAttr(
         D, *FMA, FMA->getType(), FMA->getFormatIdx(), FMA->getFormatString());
+  else if (const auto *MFA = dyn_cast<ModularFormatAttr>(Attr))
+    NewAttr = S.mergeModularFormatAttr(
+        D, *MFA, MFA->getModularImplFn(), MFA->getImplName(),
+        MutableArrayRef<StringRef>{MFA->aspects_begin(), MFA->aspects_size()});
   else if (const auto *SA = dyn_cast<SectionAttr>(Attr))
     NewAttr = S.mergeSectionAttr(D, *SA, SA->getName());
   else if (const auto *CSA = dyn_cast<CodeSegAttr>(Attr))
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c8d816a03f0b5..5a9ad7abc56c5 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6973,31 +6973,40 @@ static void handleVTablePointerAuthentication(Sema &S, 
Decl *D,
       CustomDiscriminationValue));
 }
 
-static bool modularFormatIsSame(const ModularFormatAttr *Existing,
-                                IdentifierInfo *ModularImplFn,
-                                StringRef ImplName,
-                                ArrayRef<StringRef> Aspects) {
-  if (Existing->getModularImplFn() != ModularImplFn)
-    return false;
-  if (Existing->getImplName() != ImplName)
-    return false;
-  if (Existing->aspects_size() != Aspects.size())
-    return false;
-  unsigned I = 0;
-  for (const auto &ExistingAspect : Existing->aspects()) {
-    if (ExistingAspect != Aspects[I++])
-      return false;
+static bool modularFormatAttrsEquiv(const ModularFormatAttr *Existing,
+                                    IdentifierInfo *ModularImplFn,
+                                    StringRef ImplName,
+                                    ArrayRef<StringRef> Aspects) {
+  return Existing->getModularImplFn() == ModularImplFn &&
+         Existing->getImplName() == ImplName &&
+         Existing->aspects_size() == Aspects.size() &&
+         llvm::equal(Existing->aspects(), Aspects);
+}
+
+ModularFormatAttr *
+Sema::mergeModularFormatAttr(Decl *D, const AttributeCommonInfo &CI,
+                             IdentifierInfo *ModularImplFn, StringRef ImplName,
+                             MutableArrayRef<StringRef> Aspects) {
+  if (const auto *Existing = D->getAttr<ModularFormatAttr>()) {
+    if (!modularFormatAttrsEquiv(Existing, ModularImplFn, ImplName, Aspects)) {
+      Diag(Existing->getLocation(), diag::err_duplicate_attribute) << 
*Existing;
+      Diag(CI.getLoc(), diag::note_conflicting_attribute);
+    }
+    // Drop the existing attribute on the declaration in favor of the newly
+    // inherited one.
+    D->dropAttr<ModularFormatAttr>();
   }
-  return true;
+  return ::new (Context) ModularFormatAttr(Context, CI, ModularImplFn, 
ImplName,
+                                           Aspects.data(), Aspects.size());
 }
 
 static void handleModularFormat(Sema &S, Decl *D, const ParsedAttr &AL) {
+  bool Valid = true;
   StringRef ImplName;
   if (!S.checkStringLiteralArgumentAttr(AL, 1, ImplName))
-    return;
+    Valid = false;
   SmallVector<StringRef> Aspects;
   llvm::DenseSet<StringRef> SeenAspects;
-  bool HasDuplicate = false;
   for (unsigned I = 2, E = AL.getNumArgs(); I != E; ++I) {
     StringRef Aspect;
     if (!S.checkStringLiteralArgumentAttr(AL, I, Aspect))
@@ -7006,27 +7015,26 @@ static void handleModularFormat(Sema &S, Decl *D, const 
ParsedAttr &AL) {
       S.Diag(AL.getArgAsExpr(I)->getExprLoc(),
              diag::err_modular_format_duplicate_aspect)
           << Aspect;
-      HasDuplicate = true;
+      Valid = false;
       continue;
     }
     Aspects.push_back(Aspect);
   }
+  if (!Valid)
+    return;
 
   // Store aspects sorted.
   llvm::sort(Aspects);
-
   IdentifierInfo *ModularImplFn = AL.getArgAsIdent(0)->getIdentifierInfo();
 
   if (const auto *Existing = D->getAttr<ModularFormatAttr>()) {
-    if (!modularFormatIsSame(Existing, ModularImplFn, ImplName, Aspects)) {
-      S.Diag(AL.getLoc(), diag::warn_duplicate_attribute) << AL;
-      S.Diag(Existing->getLocation(), diag::note_conflicting_attribute);
+    if (!modularFormatAttrsEquiv(Existing, ModularImplFn, ImplName, Aspects)) {
+      S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << *Existing;
+      S.Diag(Existing->getLoc(), diag::note_conflicting_attribute);
     }
-    D->dropAttr<ModularFormatAttr>();
-  }
-
-  if (HasDuplicate)
+    // Ignore the later declaration in favor of the earlier one.
     return;
+  }
 
   D->addAttr(::new (S.Context) ModularFormatAttr(
       S.Context, AL, ModularImplFn, ImplName, Aspects.data(), Aspects.size()));
diff --git a/clang/test/CodeGen/attr-modular-format.c 
b/clang/test/CodeGen/attr-modular-format.c
index e38287ee4a6a6..5474ce361fbc2 100644
--- a/clang/test/CodeGen/attr-modular-format.c
+++ b/clang/test/CodeGen/attr-modular-format.c
@@ -15,11 +15,12 @@ void test_explicit_format(void) {
   myprintf("hello");
 }
 
-int redecl(const char *fmt, ...) __attribute__((modular_format(__first_impl, 
"__first", "one"), format(printf, 1, 2)));
-int redecl(const char *fmt, ...) __attribute__((modular_format(__second_impl, 
"__second", "two", "three")));
+int redecl(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+int redecl(const char *fmt, ...) __attribute__((modular_format(__dupe_impl, 
"__dupe", "1")));
+int redecl(const char *fmt, ...) __attribute__((modular_format(__dupe_impl, 
"__dupe", "1")));
 
 // CHECK-LABEL: define dso_local void @test_redecl(
-// CHECK:    {{.*}} = call i32 (ptr, ...) @redecl(ptr noundef @.str) 
#[[ATTR_REDECL:[0-9]+]]
+// CHECK:    {{.*}} = call i32 (ptr, ...) @redecl(ptr noundef @.str) 
#[[ATTR_DUPE_IDENTICAL:[0-9]+]]
 void test_redecl(void) {
   redecl("hello");
 }
@@ -35,15 +36,14 @@ void test_order(void) {
   order2("hello");
 }
 
-int overwrite(const char *fmt, ...) __attribute__((modular_format(__impl1, 
"__name1", "1"), modular_format(__impl2, "__name2", "2"), format(printf, 1, 
2)));
+int duplicate_identical(const char *fmt, ...) 
__attribute__((modular_format(__dupe_impl, "__dupe", "1"), 
modular_format(__dupe_impl, "__dupe", "1"), format(printf, 1, 2)));
 
-// CHECK-LABEL: define dso_local void @test_overwrite(
-// CHECK:    {{.*}} = call i32 (ptr, ...) @overwrite(ptr noundef @.str) 
#[[ATTR_OVERWRITE:[0-9]+]]
-void test_overwrite(void) {
-  overwrite("hello");
+// CHECK-LABEL: define dso_local void @test_duplicate_identical(
+// CHECK:    {{.*}} = call i32 (ptr, ...) @duplicate_identical(ptr noundef 
@.str) #[[ATTR_DUPE_IDENTICAL]]
+void test_duplicate_identical(void) {
+  duplicate_identical("hello");
 }
 
 // CHECK: attributes #[[ATTR]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,float" }
-// CHECK: attributes #[[ATTR_REDECL]] = { 
"modular-format"="printf,1,2,__second_impl,__second,three,two" }
+// CHECK: attributes #[[ATTR_DUPE_IDENTICAL]] = { 
"modular-format"="printf,1,2,__dupe_impl,__dupe,1" }
 // CHECK: attributes #[[ATTR_ORDER]] = { 
"modular-format"="printf,1,2,__modular_printf,__printf,a,b" }
-// CHECK: attributes #[[ATTR_OVERWRITE]] = { 
"modular-format"="printf,1,2,__impl2,__name2,2" }
diff --git a/clang/test/Sema/attr-modular-format.c 
b/clang/test/Sema/attr-modular-format.c
index d05d0ed581e74..fc5b28b0b88be 100644
--- a/clang/test/Sema/attr-modular-format.c
+++ b/clang/test/Sema/attr-modular-format.c
@@ -11,11 +11,16 @@ int multi_dupe(const char *fmt, ...)  
__attribute__((modular_format(__modular_pr
 int same_attr(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
modular_format(__modular_printf, "__printf", "float"), format(printf, 1, 2))); 
// no-warning
 
 // Test with multiple different attributes on the same declaration.
-int diff_attr(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__modular_printf, "__printf", "int"))); // 
expected-warning {{attribute 'modular_format' is already applied with different 
arguments}} expected-note {{conflicting attribute is here}}
+int diff_attr(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__modular_printf, "__printf", "int"))); // 
expected-error {{attribute 'modular_format' is already applied with different 
arguments}} expected-note {{conflicting attribute is here}}
 
-int diff_attr2(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__modular_printf, "__other", "float"))); 
// expected-warning {{attribute 'modular_format' is already applied with 
different arguments}} expected-note {{conflicting attribute is here}}
+int diff_attr2(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__modular_printf, "__other", "float"))); 
// expected-error {{attribute 'modular_format' is already applied with 
different arguments}} expected-note {{conflicting attribute is here}}
 
-int diff_attr3(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__other, "__printf", "float"))); // 
expected-warning {{attribute 'modular_format' is already applied with different 
arguments}} expected-note {{conflicting attribute is here}}
+int diff_attr3(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"), 
format(printf, 1, 2), modular_format(__other, "__printf", "float"))); // 
expected-error {{attribute 'modular_format' is already applied with different 
arguments}} expected-note {{conflicting attribute is here}}
 
 // Test with same attributes but different aspect order.
-int diff_order(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float", "int"), 
format(printf, 1, 2), modular_format(__modular_printf, "__printf", "int", 
"float"))); // no-warning
+int diff_order(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float", "int"), 
format(printf, 1, 2), modular_format(__modular_printf, "__printf", "int", 
"float"))); // no-error
+
+// Test with multiple different attributes on a declaration and a redeclaration
+int redecl(const char *fmt, ...) __attribute__((format(printf, 1, 2))); // 
no-error
+int redecl(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "float"))); // 
expected-note {{conflicting attribute is here}}
+int redecl(const char *fmt, ...) 
__attribute__((modular_format(__modular_printf, "__printf", "int"))); // 
expected-error {{attribute 'modular_format' is already applied with different 
arguments}}

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

Reply via email to