From df1d786c460e0e47c9074f3533f098190ebfbc1b Mon Sep 17 00:00:00 2001
From: Aaron Ballman
Date: Mon, 3 Nov 2025 07:50:25 -0500
Subject: [PATCH] [C2y] Support WG14 N3457, the __COUNTER__ macro (#162662)
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 | 5 ++-
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 | 6 +--
clang/lib/Frontend/InitPreprocessor.cpp | 3 ++
clang/lib/Lex/PPMacroExpansion.cpp | 14 ++++++-
clang/lib/Serialization/ASTReader.cpp | 4 +-
clang/test/C/C2y/n3457.c | 38 +++++++++++++++++++
clang/test/C/C2y/n3457_1.c | 20 ++++++++++
clang/test/C/C2y/n3457_2.c | 10 +++++
clang/www/c_status.html | 2 +-
.../benchmark/include/benchmark/benchmark.h | 10 +++++
16 files changed, 132 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 495f2ab3926c..baa0bbb5ea63 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
@@ -1821,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
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 92fc9381a586..6959e61cac98 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -196,6 +196,11 @@ C2y Feature Support
function or variable within an extern inline function is no longer a
constraint per `WG14 N3622 `_.
- Clang now supports `N3355 `_ Named Loops.
+- Clang's implementation of ``__COUNTER__`` was updated to conform to
+ `WG14 N3457 `_.
+ 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 c7fe6e1db6d1..417187222e44 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 2'147'483'647">;
+def ext_counter : Extension<
+ "'__COUNTER__' is a C2y extension">, InGroup;
+def warn_counter : Warning<
+ "'__COUNTER__' is incompatible with standards before C2y">,
+ InGroup, 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 6e1c9425d8d7..20955ef1b852 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -8445,6 +8445,10 @@ def aligned_alloc_unavailable : Flag<["-"], "faligned-alloc-unavailable">,
MarshallingInfoFlag>,
ShouldParseIf;
+def finitial_counter_value_EQ : Joined<["-"], "finitial-counter-value=">,
+ HelpText<"Sets the initial value for __COUNTER__, defaults to 0.">,
+ MarshallingInfoInt, "0">;
+
} // let Visibility = [CC1Option]
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h
index 39754847a93e..412002259c05 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;
+ uint32_t CounterValue = 0;
enum {
/// Maximum depth of \#includes.
@@ -2421,8 +2421,8 @@ public:
bool SawDateOrTime() const {
return DATELoc != SourceLocation() || TIMELoc != SourceLocation();
}
- unsigned getCounterValue() const { return CounterValue; }
- void setCounterValue(unsigned V) { CounterValue = V; }
+ uint32_t getCounterValue() const { return CounterValue; }
+ void setCounterValue(uint32_t 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 d4c4e1ccbf2c..1c2f6e72e1b9 100644
--- a/clang/include/clang/Lex/PreprocessorOptions.h
+++ b/clang/include/clang/Lex/PreprocessorOptions.h
@@ -198,6 +198,10 @@ public:
/// If set, the UNIX timestamp specified by SOURCE_DATE_EPOCH.
std::optional SourceDateEpoch;
+ /// The initial value for __COUNTER__; typically is zero but can be set via a
+ /// -cc1 flag for testing purposes.
+ uint32_t InitialCounterValue = 0;
+
public:
PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {}
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index af856a8097ab..4ca45a16408a 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -220,8 +220,8 @@ public:
}
/// Receives __COUNTER__ value.
- virtual void ReadCounter(const serialization::ModuleFile &M,
- unsigned Value) {}
+ virtual void ReadCounter(const serialization::ModuleFile &M, uint32_t Value) {
+ }
/// This is called for each AST file loaded.
virtual void visitModuleFile(StringRef Filename,
@@ -312,7 +312,7 @@ public:
bool Complain,
std::string &SuggestedPredefines) override;
- void ReadCounter(const serialization::ModuleFile &M, unsigned Value) override;
+ void ReadCounter(const serialization::ModuleFile &M, uint32_t Value) override;
bool needsInputFileVisitation() override;
bool needsSystemInputFileVisitation() override;
void visitModuleFile(StringRef Filename,
@@ -352,7 +352,7 @@ public:
StringRef ModuleFilename,
StringRef SpecificModuleCachePath,
bool Complain) override;
- void ReadCounter(const serialization::ModuleFile &M, unsigned Value) override;
+ void ReadCounter(const serialization::ModuleFile &M, uint32_t Value) override;
};
/// ASTReaderListenter implementation to set SuggestedPredefines of
diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp
index 6cc709484615..1169acb389ac 100644
--- a/clang/lib/Frontend/ASTUnit.cpp
+++ b/clang/lib/Frontend/ASTUnit.cpp
@@ -518,14 +518,14 @@ class ASTInfoCollector : public ASTReaderListener {
LangOptions &LangOpts;
CodeGenOptions &CodeGenOpts;
TargetOptions &TargetOpts;
- unsigned &Counter;
+ uint32_t &Counter;
public:
ASTInfoCollector(HeaderSearchOptions &HSOpts,
std::string &SpecificModuleCachePath,
PreprocessorOptions &PPOpts, LangOptions &LangOpts,
CodeGenOptions &CodeGenOpts, TargetOptions &TargetOpts,
- unsigned &Counter)
+ uint32_t &Counter)
: HSOpts(HSOpts), SpecificModuleCachePath(SpecificModuleCachePath),
PPOpts(PPOpts), LangOpts(LangOpts), CodeGenOpts(CodeGenOpts),
TargetOpts(TargetOpts), Counter(Counter) {}
@@ -577,7 +577,7 @@ public:
}
void ReadCounter(const serialization::ModuleFile &M,
- unsigned NewCounter) override {
+ uint32_t NewCounter) override {
Counter = NewCounter;
}
};
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 8602be1d8a17..ed3f1f93d25d 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -1542,6 +1542,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 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 dd80ae586a1f..5efa4b5b3f87 100644
--- a/clang/lib/Lex/PPMacroExpansion.cpp
+++ b/clang/lib/Lex/PPMacroExpansion.cpp
@@ -1735,7 +1735,19 @@ 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.
+ constexpr uint32_t MaxPosValue = std::numeric_limits::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 = MaxPosValue;
+ }
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 e3106f8d8e13..d5528219bb7d 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) {
+ uint32_t 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, uint32_t 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 000000000000..d71a3f37e134
--- /dev/null
+++ b/clang/test/C/C2y/n3457.c
@@ -0,0 +1,38 @@
+// 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
+ * 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 000000000000..76c5a0b9a700
--- /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 2'147'483'647}}
+
diff --git a/clang/test/C/C2y/n3457_2.c b/clang/test/C/C2y/n3457_2.c
new file mode 100644
index 000000000000..018c8f439076
--- /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 2'147'483'647}}
+#endif
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index b8039622fe69..80a52f791dfc 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -329,7 +329,7 @@ conformance.
| The __COUNTER__ predefined macro |
N3457 |
- Unknown |
+ Clang 22 |
| Chasing Ghosts I: constant expressions v2 |
diff --git a/third-party/benchmark/include/benchmark/benchmark.h b/third-party/benchmark/include/benchmark/benchmark.h
index 08cfe29da344..c2debb216d64 100644
--- a/third-party/benchmark/include/benchmark/benchmark.h
+++ b/third-party/benchmark/include/benchmark/benchmark.h
@@ -250,6 +250,10 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#define BENCHMARK_RESTORE_DEPRECATED_WARNING _Pragma("GCC diagnostic pop")
+#define BENCHMARK_DISABLE_PEDANTIC_WARNING \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wpedantic\"")
+#define BENCHMARK_RESTORE_PEDANTIC_WARNING _Pragma("GCC diagnostic pop")
#elif defined(__NVCOMPILER)
#define BENCHMARK_BUILTIN_EXPECT(x, y) __builtin_expect(x, y)
#define BENCHMARK_DEPRECATED_MSG(msg) __attribute__((deprecated(msg)))
@@ -257,6 +261,8 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);
_Pragma("diagnostic push") \
_Pragma("diag_suppress deprecated_entity_with_custom_message")
#define BENCHMARK_RESTORE_DEPRECATED_WARNING _Pragma("diagnostic pop")
+#define BENCHMARK_DISABLE_PEDANTIC_WARNING
+#define BENCHMARK_RESTORE_PEDANTIC_WARNING
#else
#define BENCHMARK_BUILTIN_EXPECT(x, y) x
#define BENCHMARK_DEPRECATED_MSG(msg)
@@ -265,6 +271,8 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);
__LINE__) ") : warning note: " msg))
#define BENCHMARK_DISABLE_DEPRECATED_WARNING
#define BENCHMARK_RESTORE_DEPRECATED_WARNING
+#define BENCHMARK_DISABLE_PEDANTIC_WARNING
+#define BENCHMARK_RESTORE_PEDANTIC_WARNING
#endif
// clang-format on
@@ -1462,11 +1470,13 @@ class Fixture : public internal::Benchmark {
// Check that __COUNTER__ is defined and that __COUNTER__ increases by 1
// every time it is expanded. X + 1 == X + 0 is used in case X is defined to be
// empty. If X is empty the expression becomes (+1 == +0).
+BENCHMARK_DISABLE_PEDANTIC_WARNING
#if defined(__COUNTER__) && (__COUNTER__ + 1 == __COUNTER__ + 0)
#define BENCHMARK_PRIVATE_UNIQUE_ID __COUNTER__
#else
#define BENCHMARK_PRIVATE_UNIQUE_ID __LINE__
#endif
+BENCHMARK_RESTORE_PEDANTIC_WARNING
// Helpers for generating unique variable names
#ifdef BENCHMARK_HAS_CXX11