[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.
This commit is contained in:
Aaron Ballman 2025-11-03 07:50:25 -05:00 committed by GitHub
parent ef9ff15587
commit df1d786c46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 132 additions and 15 deletions

View File

@ -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

View File

@ -196,6 +196,11 @@ C2y Feature Support
function or variable within an extern inline function is no longer a
constraint per `WG14 N3622 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3622.txt>`_.
- 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
^^^^^^^^^^^^^^^^^^^

View File

@ -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<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">;

View File

@ -8445,6 +8445,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]
//===----------------------------------------------------------------------===//

View File

@ -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 &&

View File

@ -198,6 +198,10 @@ public:
/// 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.
uint32_t InitialCounterValue = 0;
public:
PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {}

View File

@ -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

View File

@ -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;
}
};

View File

@ -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 <built-in> as being a system header, which suppresses warnings when
// the same macro is defined multiple times.

View File

@ -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<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 = MaxPosValue;
}
OS << CounterValue++;
Tok.setKind(tok::numeric_constant);
} else if (II == Ident__has_feature) {

View File

@ -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);
}

38
clang/test/C/C2y/n3457.c Normal file
View File

@ -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}}
*/

View File

@ -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}}

View File

@ -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

View File

@ -329,7 +329,7 @@ conformance.</p>
<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="unreleased" align="center">Clang 22</td>
</tr>
<tr>
<td>Chasing Ghosts I: constant expressions v2</td>

View File

@ -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