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