[clang] [C2y] Support WG14 N3457, the __COUNTER__ macro (PR #162662)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 9 09:17:56 PDT 2025
https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/162662
>From 7a5e159aceef541c054a53f6508cdb7fdd9af04d Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 9 Oct 2025 10:12:55 -0400
Subject: [PATCH 1/5] [C2y] Support WG14 N3457, the __COUNTER__ macro
This implements the parts of https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3457.htm
which were adopted at the recent meeting in Brno.
Clang already implemented __COUNTER__, but needed some changes for
conformance. Specifically, we now diagnose when the macro is expanded
more than 2147483647 times. Additionally, we now give the expected
extension and pre-compat warnings for the feature.
To support testing the limits, this also adds a -cc1-only option,
-finitial-counter-value=, which lets you specify the initial value the
__COUNTER__ macro should expand to.
---
clang/docs/LanguageExtensions.rst | 4 +-
clang/docs/ReleaseNotes.rst | 5 +++
.../include/clang/Basic/DiagnosticLexKinds.td | 8 ++++
clang/include/clang/Driver/Options.td | 4 ++
clang/include/clang/Lex/Preprocessor.h | 6 +--
clang/include/clang/Lex/PreprocessorOptions.h | 4 ++
clang/include/clang/Serialization/ASTReader.h | 8 ++--
clang/lib/Frontend/ASTUnit.cpp | 9 +++--
clang/lib/Frontend/InitPreprocessor.cpp | 3 ++
clang/lib/Lex/PPMacroExpansion.cpp | 13 ++++++-
clang/lib/Serialization/ASTReader.cpp | 4 +-
clang/test/C/C2y/n3457.c | 37 +++++++++++++++++++
clang/test/C/C2y/n3457_1.c | 20 ++++++++++
clang/test/C/C2y/n3457_2.c | 10 +++++
clang/www/c_status.html | 2 +-
15 files changed, 122 insertions(+), 15 deletions(-)
create mode 100644 clang/test/C/C2y/n3457.c
create mode 100644 clang/test/C/C2y/n3457_1.c
create mode 100644 clang/test/C/C2y/n3457_2.c
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 6bb99c757cd19..54c215e9ccfaa 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -385,7 +385,9 @@ Builtin Macros
``__COUNTER__``
Defined to an integer value that starts at zero and is incremented each time
- the ``__COUNTER__`` macro is expanded.
+ the ``__COUNTER__`` macro is expanded. This is a standard feature in C2y but
+ is an extension in earlier language modes and in C++. This macro can only be
+ expanded 2147483647 times at most.
``__INCLUDE_LEVEL__``
Defined to an integral value that is the include depth of the file currently
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 5e9a71e1e74d6..37e02e9638296 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -181,6 +181,11 @@ C Language Changes
C2y Feature Support
^^^^^^^^^^^^^^^^^^^
- Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops.
+- Clang's implementation of ``__COUNTER__`` was updated to conform to
+ `WG14 N3457 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3457.htm>`_.
+ This includes adding pedantic warnings for the feature being an extension in
+ other language modes as well as an error when the counter is expanded more
+ than 2147483647 times.
C23 Feature Support
^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index c7fe6e1db6d1f..46a7a88f7a50b 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -90,6 +90,14 @@ def err_unterminated___pragma : Error<"missing terminating ')' character">;
def err_conflict_marker : Error<"version control conflict marker in file">;
+def err_counter_overflow : Error<
+ "'__COUNTER__' value cannot exceed 2147483647">;
+def ext_counter : Extension<
+ "'__COUNTER__' is a C2y extension">, InGroup<C2y>;
+def warn_counter : Warning<
+ "'__COUNTER__' is incompatible with standards before C2y">,
+ InGroup<CPre2yCompat>, DefaultIgnore;
+
def err_raw_delim_too_long : Error<
"raw string delimiter longer than 16 characters"
"; use PREFIX( )PREFIX to delimit raw string">;
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index c8e96e125733c..2511f88f4e53c 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -8417,6 +8417,10 @@ def aligned_alloc_unavailable : Flag<["-"], "faligned-alloc-unavailable">,
MarshallingInfoFlag<LangOpts<"AlignedAllocationUnavailable">>,
ShouldParseIf<faligned_allocation.KeyPath>;
+def finitial_counter_value_EQ : Joined<["-"], "finitial-counter-value=">,
+ HelpText<"Sets the initial value for __COUNTER__, defaults to 0.">,
+ MarshallingInfoInt<PreprocessorOpts<"InitialCounterValue">, "0">;
+
} // let Visibility = [CC1Option]
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 39754847a93e4..b79e6072cff15 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -226,7 +226,7 @@ class Preprocessor {
LangOptions::FPEvalMethodKind::FEM_UnsetOnCommandLine;
// Next __COUNTER__ value, starts at 0.
- unsigned CounterValue = 0;
+ unsigned long CounterValue = 0;
enum {
/// Maximum depth of \#includes.
@@ -2421,8 +2421,8 @@ class Preprocessor {
bool SawDateOrTime() const {
return DATELoc != SourceLocation() || TIMELoc != SourceLocation();
}
- unsigned getCounterValue() const { return CounterValue; }
- void setCounterValue(unsigned V) { CounterValue = V; }
+ unsigned long getCounterValue() const { return CounterValue; }
+ void setCounterValue(unsigned long V) { CounterValue = V; }
LangOptions::FPEvalMethodKind getCurrentFPEvalMethod() const {
assert(CurrentFPEvalMethod != LangOptions::FEM_UnsetOnCommandLine &&
diff --git a/clang/include/clang/Lex/PreprocessorOptions.h b/clang/include/clang/Lex/PreprocessorOptions.h
index d4c4e1ccbf2c4..2b65e9422a5e5 100644
--- a/clang/include/clang/Lex/PreprocessorOptions.h
+++ b/clang/include/clang/Lex/PreprocessorOptions.h
@@ -198,6 +198,10 @@ class PreprocessorOptions {
/// If set, the UNIX timestamp specified by SOURCE_DATE_EPOCH.
std::optional<uint64_t> SourceDateEpoch;
+ /// The initial value for __COUNTER__; typically is zero but can be set via a
+ /// -cc1 flag for testing purposes.
+ unsigned long InitialCounterValue = 0;
+
public:
PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {}
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index af856a8097ab1..485bfad8c9e1a 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -221,7 +221,7 @@ class ASTReaderListener {
/// Receives __COUNTER__ value.
virtual void ReadCounter(const serialization::ModuleFile &M,
- unsigned Value) {}
+ unsigned long Value) {}
/// This is called for each AST file loaded.
virtual void visitModuleFile(StringRef Filename,
@@ -312,7 +312,8 @@ class ChainedASTReaderListener : public ASTReaderListener {
bool Complain,
std::string &SuggestedPredefines) override;
- void ReadCounter(const serialization::ModuleFile &M, unsigned Value) override;
+ void ReadCounter(const serialization::ModuleFile &M,
+ unsigned long Value) override;
bool needsInputFileVisitation() override;
bool needsSystemInputFileVisitation() override;
void visitModuleFile(StringRef Filename,
@@ -352,7 +353,8 @@ class PCHValidator : public ASTReaderListener {
StringRef ModuleFilename,
StringRef SpecificModuleCachePath,
bool Complain) override;
- void ReadCounter(const serialization::ModuleFile &M, unsigned Value) override;
+ void ReadCounter(const serialization::ModuleFile &M,
+ unsigned long Value) override;
};
/// ASTReaderListenter implementation to set SuggestedPredefines of
diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp
index cb445682ac48b..ff48faa02f593 100644
--- a/clang/lib/Frontend/ASTUnit.cpp
+++ b/clang/lib/Frontend/ASTUnit.cpp
@@ -520,7 +520,7 @@ class ASTInfoCollector : public ASTReaderListener {
CodeGenOptions &CodeGenOpts;
std::shared_ptr<TargetOptions> &TargetOpts;
IntrusiveRefCntPtr<TargetInfo> &Target;
- unsigned &Counter;
+ unsigned long &Counter;
bool InitializedLanguage = false;
bool InitializedHeaderSearchPaths = false;
@@ -529,7 +529,8 @@ class ASTInfoCollector : public ASTReaderListener {
HeaderSearchOptions &HSOpts, PreprocessorOptions &PPOpts,
LangOptions &LangOpt, CodeGenOptions &CodeGenOpts,
std::shared_ptr<TargetOptions> &TargetOpts,
- IntrusiveRefCntPtr<TargetInfo> &Target, unsigned &Counter)
+ IntrusiveRefCntPtr<TargetInfo> &Target,
+ unsigned long &Counter)
: PP(PP), Context(Context), HSOpts(HSOpts), PPOpts(PPOpts),
LangOpt(LangOpt), CodeGenOpts(CodeGenOpts), TargetOpts(TargetOpts),
Target(Target), Counter(Counter) {}
@@ -627,7 +628,7 @@ class ASTInfoCollector : public ASTReaderListener {
}
void ReadCounter(const serialization::ModuleFile &M,
- unsigned Value) override {
+ unsigned long Value) override {
Counter = Value;
}
@@ -873,7 +874,7 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromASTFile(
/*isysroot=*/"",
/*DisableValidationKind=*/disableValid, AllowASTWithCompilerErrors);
- unsigned Counter = 0;
+ unsigned long Counter = 0;
AST->Reader->setListener(std::make_unique<ASTInfoCollector>(
*AST->PP, AST->Ctx.get(), *AST->HSOpts, *AST->PPOpts, *AST->LangOpts,
*AST->CodeGenOpts, AST->TargetOpts, AST->Target, Counter));
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 877ab02850667..aaf01705caa5e 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -1569,6 +1569,9 @@ void clang::InitializePreprocessor(Preprocessor &PP,
llvm::raw_string_ostream Predefines(PredefineBuffer);
MacroBuilder Builder(Predefines);
+ // Ensure that the initial value of __COUNTER__ is hooked up.
+ PP.setCounterValue(InitOpts.InitialCounterValue);
+
// Emit line markers for various builtin sections of the file. The 3 here
// marks <built-in> as being a system header, which suppresses warnings when
// the same macro is defined multiple times.
diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp
index dec1956ea0f9a..37eef9060d4b5 100644
--- a/clang/lib/Lex/PPMacroExpansion.cpp
+++ b/clang/lib/Lex/PPMacroExpansion.cpp
@@ -1737,7 +1737,18 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
Diag(getLastFPEvalPragmaLocation(), diag::note_pragma_entered_here);
}
} else if (II == Ident__COUNTER__) {
- // __COUNTER__ expands to a simple numeric value.
+ Diag(Tok.getLocation(),
+ getLangOpts().C2y ? diag::warn_counter : diag::ext_counter);
+ // __COUNTER__ expands to a simple numeric value that must be less than
+ // 2147483647.
+ if (CounterValue > 2147483647) {
+ Diag(Tok.getLocation(), diag::err_counter_overflow);
+ // Retain the maximal value so we don't issue conversion-related
+ // diagnostics by overflowing into a long long. While this does produce
+ // a duplicate value, there's no way to ignore this error so there's no
+ // translation anyway.
+ CounterValue = 2147483647;
+ }
OS << CounterValue++;
Tok.setKind(tok::numeric_constant);
} else if (II == Ident__has_feature) {
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 6acf79acea111..32d0f36d17459 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -225,7 +225,7 @@ bool ChainedASTReaderListener::ReadPreprocessorOptions(
}
void ChainedASTReaderListener::ReadCounter(const serialization::ModuleFile &M,
- unsigned Value) {
+ unsigned long Value) {
First->ReadCounter(M, Value);
Second->ReadCounter(M, Value);
}
@@ -973,7 +973,7 @@ bool PCHValidator::ReadHeaderSearchOptions(const HeaderSearchOptions &HSOpts,
PP.getPreprocessorOpts());
}
-void PCHValidator::ReadCounter(const ModuleFile &M, unsigned Value) {
+void PCHValidator::ReadCounter(const ModuleFile &M, unsigned long Value) {
PP.setCounterValue(Value);
}
diff --git a/clang/test/C/C2y/n3457.c b/clang/test/C/C2y/n3457.c
new file mode 100644
index 0000000000000..77e7b7ed5ecfe
--- /dev/null
+++ b/clang/test/C/C2y/n3457.c
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -verify=ext -std=c23 -pedantic %s
+// RUN: %clang_cc1 -verify=pre -std=c2y -pedantic -Wpre-c2y-compat %s
+
+/* WG14 N3457: Clang 22
+ * The __COUNTER__ predefined macro
+ *
+ * This predefined macro was supported as an extension in earlier versions of
+ * Clang, but the required diagnostics for the limits were not added until 22.
+ */
+
+// Ensure that __COUNTER__ starts from 0.
+static_assert(__COUNTER__ == 0); /* ext-warning {{'__COUNTER__' is a C2y extension}}
+ pre-warning {{'__COUNTER__' is incompatible with standards before C2y}}
+ */
+
+// Ensure that the produced value can be used with token concatenation.
+#define CAT_IMPL(a, b) a ## b
+#define CAT(a, b) CAT_IMPL(a, b)
+#define NAME_WITH_COUNTER(a) CAT(a, __COUNTER__)
+void test() {
+ // Because this is the 2nd expansion, this defines test1.
+ int NAME_WITH_COUNTER(test); /* ext-warning {{'__COUNTER__' is a C2y extension}}
+ pre-warning {{'__COUNTER__' is incompatible with standards before C2y}}
+ */
+ int other_test = test1; // Ok
+}
+
+// Ensure that __COUNTER__ increments each time you mention it.
+static_assert(__COUNTER__ == 2); /* ext-warning {{'__COUNTER__' is a C2y extension}}
+ pre-warning {{'__COUNTER__' is incompatible with standards before C2y}}
+ */
+static_assert(__COUNTER__ == 3); /* ext-warning {{'__COUNTER__' is a C2y extension}}
+ pre-warning {{'__COUNTER__' is incompatible with standards before C2y}}
+ */
+static_assert(__COUNTER__ == 4); /* ext-warning {{'__COUNTER__' is a C2y extension}}
+ pre-warning {{'__COUNTER__' is incompatible with standards before C2y}}
+ */
diff --git a/clang/test/C/C2y/n3457_1.c b/clang/test/C/C2y/n3457_1.c
new file mode 100644
index 0000000000000..2612d096e23fc
--- /dev/null
+++ b/clang/test/C/C2y/n3457_1.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -verify -std=c2y -finitial-counter-value=2147483646 %s
+
+// The value produced needs to be a type that's representable with a signed
+// long. However, the actual type it expands to does *not* need to be forced to
+// be signed long because that would generally mean suffixing the value with L,
+// which would be very surprising for folks using this to generate unique ids.
+// We'll test this by ensuring the largest value can be expanded properly and
+// an assertion that signed long is always at least four bytes wide (which is
+// what's required to represent that maximal value).
+//
+// So we set the initial counter value to 2147483646, we'll validate that,
+// increment it once to get to the maximal value and ensure there's no
+// diagnostic, then increment again to ensure we get the constraint violation.
+
+static_assert(__COUNTER__ == 2147483646); // Test and increment
+static_assert(__COUNTER__ == 2147483647); // Test and increment
+
+// This one should fail.
+signed long i = __COUNTER__; // expected-error {{'__COUNTER__' value cannot exceed 2147483647}}
+
diff --git a/clang/test/C/C2y/n3457_2.c b/clang/test/C/C2y/n3457_2.c
new file mode 100644
index 0000000000000..cf268283e11c6
--- /dev/null
+++ b/clang/test/C/C2y/n3457_2.c
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -verify=good -std=c2y -finitial-counter-value=2147483648 %s
+// RUN: %clang_cc1 -verify -std=c2y -finitial-counter-value=2147483648 -DEXPAND_IT %s
+// good-no-diagnostics
+
+// This sets the intial __COUNTER__ value to something that's too big. Setting
+// the value too large is fine. Expanding to a too-large value is not.
+#ifdef EXPAND_IT
+ // This one should fail.
+ signed long i = __COUNTER__; // expected-error {{'__COUNTER__' value cannot exceed 2147483647}}
+#endif
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 380f66495a367..96dcaf14aae02 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -329,7 +329,7 @@ <h2 id="c2y">C2y implementation status</h2>
<tr>
<td>The __COUNTER__ predefined macro</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3457.htm">N3457</a></td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="full" align="center">Clang 22</td>
</tr>
<tr>
<td>Chasing Ghosts I: constant expressions v2</td>
>From 1d0a459b44489e6a1471464571bdfffe97158d1b Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 9 Oct 2025 12:11:52 -0400
Subject: [PATCH 2/5] Clang 22 isn't released yet
---
clang/www/c_status.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 96dcaf14aae02..eeff464313245 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -329,7 +329,7 @@ <h2 id="c2y">C2y implementation status</h2>
<tr>
<td>The __COUNTER__ predefined macro</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3457.htm">N3457</a></td>
- <td class="full" align="center">Clang 22</td>
+ <td class="unreleased" align="center">Clang 22</td>
</tr>
<tr>
<td>Chasing Ghosts I: constant expressions v2</td>
>From 7129a6ad493ca1a33b388eb4e6b6bde406cf9dd0 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 9 Oct 2025 12:13:15 -0400
Subject: [PATCH 3/5] Use a named constant; NFC
---
clang/lib/Lex/PPMacroExpansion.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp
index 37eef9060d4b5..8213e2bc2d769 100644
--- a/clang/lib/Lex/PPMacroExpansion.cpp
+++ b/clang/lib/Lex/PPMacroExpansion.cpp
@@ -1741,13 +1741,14 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
getLangOpts().C2y ? diag::warn_counter : diag::ext_counter);
// __COUNTER__ expands to a simple numeric value that must be less than
// 2147483647.
- if (CounterValue > 2147483647) {
+ constexpr unsigned long MaxPosValue = std::numeric_limits<int32_t>::max();
+ if (CounterValue > MaxPosValue) {
Diag(Tok.getLocation(), diag::err_counter_overflow);
// Retain the maximal value so we don't issue conversion-related
// diagnostics by overflowing into a long long. While this does produce
// a duplicate value, there's no way to ignore this error so there's no
// translation anyway.
- CounterValue = 2147483647;
+ CounterValue = MaxPosValue;
}
OS << CounterValue++;
Tok.setKind(tok::numeric_constant);
>From 1c123deb64d25e7e7f0966f37b73165a92916ed2 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 9 Oct 2025 12:14:28 -0400
Subject: [PATCH 4/5] Add C++ pedantic test
---
clang/test/C/C2y/n3457.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/C/C2y/n3457.c b/clang/test/C/C2y/n3457.c
index 77e7b7ed5ecfe..d71a3f37e1343 100644
--- a/clang/test/C/C2y/n3457.c
+++ b/clang/test/C/C2y/n3457.c
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -verify=ext -std=c23 -pedantic %s
+// RUN: %clang_cc1 -verify=ext -pedantic -x c++ %s
// RUN: %clang_cc1 -verify=pre -std=c2y -pedantic -Wpre-c2y-compat %s
/* WG14 N3457: Clang 22
>From 07207716fe0409e45d3383afef71867973974997 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 9 Oct 2025 12:15:37 -0400
Subject: [PATCH 5/5] Update Language Extensions docs for backporting
---
clang/docs/LanguageExtensions.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 54c215e9ccfaa..7fdc991af3b60 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1823,6 +1823,7 @@ Octal literals prefixed with ``0o`` or ``0O`` C
``_Countof`` (N3369, N3469) C2y C89
``_Generic`` with a type operand (N3260) C2y C89, C++
``++``/``--`` on ``_Complex`` value (N3259) C2y C89, C++
+``__COUNTER__`` (N3457) C2y C89, C++
============================================= ================================ ============= =============
Builtin type aliases
More information about the cfe-commits
mailing list