On 2024-03-18 00:30, Jonathan Wakely wrote:
> I don't have an opinion on the implementation, or the proposal itself,
> except that the implementation seems susprisingly simple, which is
> nice.

Hi Jonathan,

Here is an updated patch.

It rebased cleanly over more than newer 16000 commits, suggesting
that the area in the cpp code is "still waters", which is good.

I made the documentation change not to recommend using #if, but
#ifdef.

I got rid of the ChangeLog changes, and also tried to pay more
attention to the log message format, where the ChangeLog pieces
are specified.

In the first test case, I had to adjust the expected warning text
for two lines.

From 65effbcac172e8bb1a89f0621b3de6cdcb8dbab2 Mon Sep 17 00:00:00 2001
From: Kaz Kylheku <k...@kylheku.com>
Date: Wed, 20 Apr 2022 01:15:24 -0700
Subject: [PATCH] cpp: new built-in __EXP_COUNTER__

This change introduces a pair of related macros
__EXP_COUNTER__ and __UEXP_COUNTER__.  These macros access
integer values which enumerate macro expansions.
They can be used for the purposes of obtaining, unique
identifiers (within the scope of a translation unit), as a
replacement for unreliable hacks based on __LINE__.

Outside of macro expansions, these macros epand to 1,
so they are easy to test for in portable code that needs
to fall back on something, like __LINE__.

libcpp/ChangeLog:

	* libcpp/include/cpplib.h (struct cpp_macro): New members of
	type long long: exp_number and uexp_number.  These members are
	used to attach the current exp_counter value, and the parent
	context's copy of it to a new macro expansion.  The special
	macros then just access these.
	(enum cpp_builtin_type): New enumeration members,
	BT_EXP_COUNTER and BT_UEXP_COUNTER.

	* libcpp/macro.cc (exp_counter): New static variable for
	counting expansions.  There is an existing one,
	num_expanded_macros_counter, but that has its own purpose and
	is incremented in a specific place.  Plus it uses a narrower
	integer type.
	(_cpp_builtin_number_text): Change the local variable "number"
	from linenum_type to unsigned long long, so it holds at least
	64 bit values.  Handle the BT_EXP_COUNTER and BT_UEXP_COUNTER
	cases.  These just have to see if there is a current macro, and
	retrieve the values from it, otherwise do nothing so that the
	default 1 is produced.  In the case of BT_UEXP_COUNTER, if the
	value is zero, we don't use it, so 1 emerges.  The sprintf of
	the number is adjusted to use the right conversion specifier
	for the wider type.  Space is already being reserved for
	a 64 bit decimal.
	(enter_macro_context): After processing the macro arguments,
	if any, we increment exp_counter and attach its new value to
	the macro's context structure in the exp_number member.  We
	also calculate th emacro's uexp_number: the parent context's
	exp_cnumber.  This is tricky: we have to chase the previous
	macro context.  This works if the macro is object-like, or has
	no parameters.  If it has parameters, there is a parameter
	context, and so we have to climb one more flight of stairs to
	get to the real context.

gcc/ChangeLog:

	* gcc/doc/cpp.texi (__EXP_COUNTER__, __UEXP_COUNTER__):
	Special built-in macros documented.

gcc/testsuite/ChangeLog:

	* gcc.dg/cpp/expcounter1.c: New test.
	* gcc.dg/cpp/expcounter2.c: New test.
	* gcc.dg/cpp/expcounter3.c: New test.
	* gcc.dg/cpp/expcounter4.c: New test.
	* gcc.dg/cpp/expcounter5.c: New test.

Signed-off-by: Kaz Kylheku <k...@kylheku.com>
---
 gcc/doc/cpp.texi                       | 81 ++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/cpp/expcounter1.c | 16 +++++
 gcc/testsuite/gcc.dg/cpp/expcounter2.c | 21 +++++++
 gcc/testsuite/gcc.dg/cpp/expcounter3.c | 22 +++++++
 gcc/testsuite/gcc.dg/cpp/expcounter4.c | 22 +++++++
 gcc/testsuite/gcc.dg/cpp/expcounter5.c | 28 +++++++++
 libcpp/include/cpplib.h                |  8 +++
 libcpp/init.cc                         |  2 +
 libcpp/macro.cc                        | 44 +++++++++++++-
 9 files changed, 242 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/cpp/expcounter1.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/expcounter2.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/expcounter3.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/expcounter4.c
 create mode 100644 gcc/testsuite/gcc.dg/cpp/expcounter5.c

diff --git a/gcc/doc/cpp.texi b/gcc/doc/cpp.texi
index 3de6e7aa737..a21e8b44be1 100644
--- a/gcc/doc/cpp.texi
+++ b/gcc/doc/cpp.texi
@@ -1942,6 +1942,87 @@ generate unique identifiers.  Care must be taken to ensure that
 @code{__COUNTER__} is not expanded prior to inclusion of precompiled headers
 which use it.  Otherwise, the precompiled headers will not be used.
 
+@item __EXP_COUNTER__
+This macro's name means "(macro) expansion counter".
+Outside of macro replacement sequences, it expands to the integer
+token @code{1}.  This make it possible to easily test for the presence
+of this feature using conditional directives such as
+@code{#ifdef __EXP_COUNTER__}.
+When @code{__EXP_COUNTER__} occurs in the replacement token sequence
+of a macro, it expands to a positive decimal integer token which
+uniquely identifies the expansion, within a translation unit.
+Unlike @code{__COUNTER__}, @code{__EXP_COUNTER__} does not increment
+on each access: all references to it which occur in the same token
+replacement sequence of the same instance of a macro being expanded
+produce the same integer token.
+
+The implementation of this feature works as follows: a counter is initialized
+to zero prior to the processing of any tokens of the translation unit.  Whenever
+a macro is about to be expanded, the counter is incremented, and the counter's
+value is associated with that macro expansion context.  Subsequent increments of
+the counter, such as during rescanning of a token sequence for more macros, do
+not affect the captured value.  Nested macro expansions are associated with
+their own @code{__EXP_COUNTER__} values.  The counter uses the
+@code{unsigned long long} type of the host implementation for which the
+preprocessor is compiled.  If this counter overflows and @code{__EXP_COUNTER__}
+is subsequently accessed, the behavior is unspecified.
+
+The main use for @code{__EXP_COUNTER__} is the generation of unique symbols,
+as in the following example:
+
+@smallexample
+#define cat(a, b) a ## b
+#define xcat(a, b) cat (a, b)
+#define uni(pfx, count) xcat (pfx, xcat (_uniq_, count))
+
+#define repeat(count)                                       \
+  for (int uni (i, __EXP_COUNTER__) = 0,                    \
+       uni (c, __EXP_COUNTER__) = (count);                  \
+       uni (i, __EXP_COUNTER__) < uni (c, __EXP_COUNTER__); \
+       uni (i, __EXP_COUNTER__)++)
+
+repeat (get_repeat_count ())
+  puts ("Hello, world!")
+@end smallexample
+
+@item __UEXP_COUNTER__
+This macro is closely related to @code{__EXP_COUNTER__}.  In the
+expansion of a macro which is being expanded in the middle of another
+macro expansion, @code{__UEXP_COUNTER__} ("upper context expansion counter")
+produces the @code{__EXP_COUNTER__} value of the parent expansion.
+In any other situation, this macro expands to @code{1}.
+
+Consider the following example:
+
+@smallexample
+#define child __UEXP_COUNTER__ __EXP_COUNTER__
+#define parent @{ __EXP_COUNTER__ child @}
+parent
+@end smallexample
+
+Here, @code{parent} will expand to a sequence similar to @code{@{ 42 42 43 @}}.
+The @code{__UEXP_COUNTER__} value from @code{child} matches the
+@code{__EXP_COUNTER__} in @code{parent}, but the @code{child}'s own
+@code{__EXP_COUNTER__} is, of course, different.
+
+The main use for @code{__UEXP_COUNTER__} is the simplification of macros
+that require @code{__EXP_COUNTER__}.  The example given for
+@code{__EXP_COUNTER__} can be simplified like this:
+
+@smallexample
+#define cat(a, b) a ## b
+#define xcat(a, b) cat (a, b)
+#define uni(pfx) xcat (pfx, xcat (_uniq_, __UEXP_COUNTER__))
+
+#define repeat(count)                       \
+  for (int uni (i) = 0, uni (c) = (count);  \
+       uni (i) < uni (c);                   \
+       uni (i)++)
+
+repeat (get_repeat_count ())
+  puts ("Hello, world!")
+@end smallexample
+
 @item __GFORTRAN__
 The GNU Fortran compiler defines this.
 
diff --git a/gcc/testsuite/gcc.dg/cpp/expcounter1.c b/gcc/testsuite/gcc.dg/cpp/expcounter1.c
new file mode 100644
index 00000000000..0734fc07114
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/expcounter1.c
@@ -0,0 +1,16 @@
+/* { dg-do preprocess } */
+/* { dg-options "-Wcpp" } */
+#if __EXP_COUNTER__ == 1
+#warning "yes" // { dg-warning "-:yes" }
+#endif
+#if __UEXP_COUNTER__ == 1
+#warning "yes" // { dg-warning "-:yes" }
+#endif
+#define __EXP_COUNTER__ 42 // { dg-warning "-:\"__EXP_COUNTER__\" redefined" }
+#define __UEXP_COUNTER__ 73 // { dg-warning "-:\"__UEXP_COUNTER__\" redefined" }
+#if __EXP_COUNTER__ == 42
+#warning "yes" // { dg-warning "-:yes" }
+#endif
+#if __UEXP_COUNTER__ == 73
+#warning "yes" // { dg-warning "-:yes" }
+#endif
diff --git a/gcc/testsuite/gcc.dg/cpp/expcounter2.c b/gcc/testsuite/gcc.dg/cpp/expcounter2.c
new file mode 100644
index 00000000000..de1cf6fca45
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/expcounter2.c
@@ -0,0 +1,21 @@
+/* { dg-do run } */
+/* { dg-options "" } */
+
+#define M0(X) X, __EXP_COUNTER__
+#define M1(X) X, M0(__EXP_COUNTER__), __EXP_COUNTER__
+#define M2(X) X, M1(__EXP_COUNTER__), __EXP_COUNTER__
+
+int out[] = { M2(__EXP_COUNTER__), __EXP_COUNTER__ };
+int ref[] = { 1, 3, 4, 5, 4, 3, 1 };
+
+#include <string.h>
+#include <stdlib.h>
+
+int main()
+{
+  if (sizeof(out) != sizeof(ref))
+    return EXIT_FAILURE;
+  if (memcmp(out, ref, sizeof out) != 0)
+    return EXIT_FAILURE;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/cpp/expcounter3.c b/gcc/testsuite/gcc.dg/cpp/expcounter3.c
new file mode 100644
index 00000000000..b7f9bb03a7f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/expcounter3.c
@@ -0,0 +1,22 @@
+/* { dg-do run } */
+/* { dg-options "" } */
+
+#define M0(X) X, __UEXP_COUNTER__
+#define M1(X) X, M0(__UEXP_COUNTER__), __UEXP_COUNTER__
+#define M2(X) X, M1(__UEXP_COUNTER__), __UEXP_COUNTER__
+#define M3(X) X, M2(__UEXP_COUNTER__), __UEXP_COUNTER__
+
+int out[] = { M3(__UEXP_COUNTER__), __UEXP_COUNTER__ };
+int ref[] = { 1, 1, 3, 4, 5, 4, 3, 1, 1 };
+
+#include <string.h>
+#include <stdlib.h>
+
+int main()
+{
+  if (sizeof(out) != sizeof(ref))
+    return EXIT_FAILURE;
+  if (memcmp(out, ref, sizeof out) != 0)
+    return EXIT_FAILURE;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/cpp/expcounter4.c b/gcc/testsuite/gcc.dg/cpp/expcounter4.c
new file mode 100644
index 00000000000..47f0732a655
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/expcounter4.c
@@ -0,0 +1,22 @@
+/* { dg-do run } */
+/* { dg-options "" } */
+
+#define M0 __UEXP_COUNTER__
+#define M1 M0, __EXP_COUNTER__, __UEXP_COUNTER__
+#define M2 M1, __EXP_COUNTER__, __UEXP_COUNTER__
+#define M3 M2, __EXP_COUNTER__, __UEXP_COUNTER__
+
+int out[] = { M3, __EXP_COUNTER__, __UEXP_COUNTER__ };
+int ref[] = { 5, 5, 4, 4, 3, 3, 1, 1, 1 };
+
+#include <string.h>
+#include <stdlib.h>
+
+int main()
+{
+  if (sizeof(out) != sizeof(ref))
+    return EXIT_FAILURE;
+  if (memcmp(out, ref, sizeof out) != 0)
+    return EXIT_FAILURE;
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/cpp/expcounter5.c b/gcc/testsuite/gcc.dg/cpp/expcounter5.c
new file mode 100644
index 00000000000..59b0e780768
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/cpp/expcounter5.c
@@ -0,0 +1,28 @@
+/* { dg-do run } */
+/* { dg-options "" } */
+
+#define cat(a, b) a ## b
+#define xcat(a, b) cat(a, b)
+#define uni(pfx) xcat(pfx, xcat(_uniq_, __UEXP_COUNTER__))
+
+#define repeat(count)                       \
+  for (int uni(i) = 0, uni(c) = (count);    \
+       uni(i) < uni(c);                     \
+       uni(i)++)
+
+#define str(x) #x
+#define xstr(x) str(x)
+
+const char *out = xstr(repeat(42) repeat(73) foo(););
+const char *ref = "for (int i_uniq_3 = 0, c_uniq_3 = (42); "
+                  "i_uniq_3 < c_uniq_3; i_uniq_3++) "
+                  "for (int i_uniq_29 = 0, c_uniq_29 = (73); "
+                  "i_uniq_29 < c_uniq_29; i_uniq_29++) foo();";
+
+#include <string.h>
+#include <stdlib.h>
+
+int main()
+{
+  return (strcmp(out, ref) == 0) ? 0 : EXIT_FAILURE;
+}
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index c62374d3192..25550b0281b 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -875,6 +875,12 @@ struct GTY(()) cpp_macro {
     cpp_macro *GTY ((tag ("true"))) next;
   } GTY ((desc ("%1.kind == cmk_assert"))) parm;
 
+  /* Global expansion number, derived from exp_counter.  */
+  long long exp_number;
+
+  /* Parent expansion number; zero if unavailable.  */
+  long long uexp_number;
+
   /* Definition line number.  */
   location_t line;
 
@@ -968,6 +974,8 @@ enum cpp_builtin_type
   BT_PRAGMA,			/* `_Pragma' operator */
   BT_TIMESTAMP,			/* `__TIMESTAMP__' */
   BT_COUNTER,			/* `__COUNTER__' */
+  BT_EXP_COUNTER,		/* `__EXP_COUNTER__' */
+  BT_UEXP_COUNTER,		/* `__UEXP_COUNTER__' */
   BT_HAS_ATTRIBUTE,		/* `__has_attribute(x)' */
   BT_HAS_STD_ATTRIBUTE,		/* `__has_c_attribute(x)' */
   BT_HAS_BUILTIN,		/* `__has_builtin(x)' */
diff --git a/libcpp/init.cc b/libcpp/init.cc
index 54fc9236d38..0074634c2e7 100644
--- a/libcpp/init.cc
+++ b/libcpp/init.cc
@@ -426,6 +426,8 @@ static const struct builtin_macro builtin_array[] =
   B("__LINE__",		 BT_SPECLINE,      true),
   B("__INCLUDE_LEVEL__", BT_INCLUDE_LEVEL, true),
   B("__COUNTER__",	 BT_COUNTER,       true),
+  B("__EXP_COUNTER__",	 BT_EXP_COUNTER,   true),
+  B("__UEXP_COUNTER__",	 BT_UEXP_COUNTER,  true),
   /* Make sure to update the list of built-in
      function-like macros in traditional.cc:
      fun_like_macro() when adding more following */
diff --git a/libcpp/macro.cc b/libcpp/macro.cc
index 352eb2e4fd9..2a7ca90d955 100644
--- a/libcpp/macro.cc
+++ b/libcpp/macro.cc
@@ -362,6 +362,10 @@ static const cpp_token* cpp_get_token_1 (cpp_reader *, location_t *);
 
 static cpp_hashnode* macro_of_context (cpp_context *context);
 
+/* Counter tracking the number of macros expanded, for purposes
+   of __EXP_COUNTER__.  */
+static unsigned long exp_counter = 0;
+
 /* Statistical counter tracking the number of macros that got
    expanded.  */
 unsigned num_expanded_macros_counter = 0;
@@ -493,7 +497,7 @@ _cpp_builtin_macro_text (cpp_reader *pfile, cpp_hashnode *node,
 			 location_t loc)
 {
   const uchar *result = NULL;
-  linenum_type number = 1;
+  unsigned long long number = 1;
 
   switch (node->value.builtin)
     {
@@ -663,6 +667,26 @@ _cpp_builtin_macro_text (cpp_reader *pfile, cpp_hashnode *node,
       number = pfile->counter++;
       break;
 
+    case BT_EXP_COUNTER:
+      {
+	cpp_hashnode *macro = macro_of_context (pfile->context);
+	if (macro != NULL)
+	  number = macro->value.macro->exp_number;
+      }
+      break;
+
+    case BT_UEXP_COUNTER:
+      {
+	cpp_hashnode *macro = macro_of_context (pfile->context);
+	if (macro != NULL)
+	  {
+	    unsigned long long ue = macro->value.macro->uexp_number;
+	    if (ue > 0)
+	      number = ue;
+	  }
+      }
+      break;
+
     case BT_HAS_ATTRIBUTE:
       number = pfile->cb.has_attribute (pfile, false);
       break;
@@ -692,7 +716,7 @@ _cpp_builtin_macro_text (cpp_reader *pfile, cpp_hashnode *node,
     {
       /* 21 bytes holds all NUL-terminated unsigned 64-bit numbers.  */
       result = _cpp_unaligned_alloc (pfile, 21);
-      sprintf ((char *) result, "%u", number);
+      sprintf ((char *) result, "%llu", number);
     }
 
   return result;      
@@ -1501,6 +1525,22 @@ enter_macro_context (cpp_reader *pfile, cpp_hashnode *node,
       /* Disable the macro within its expansion.  */
       node->flags |= NODE_DISABLED;
 
+      /* Increment and capture __EXP_COUNTER__ counter.  */
+      macro->exp_number = ++exp_counter;
+
+      /* If we are in the middle of an existing macro, get *its*
+	 exp_number into uexp_number, for __UEXP_COUNTER__. */
+      {
+	cpp_hashnode *existing_macro = macro_of_context (pfile->context);
+	cpp_macro *pmac = existing_macro != NULL
+	                  ? existing_macro->value.macro
+			  : NULL;
+	if (pmac != NULL && pmac->paramc > 0)
+	  existing_macro = macro_of_context (pfile->context->prev);
+	if (existing_macro != NULL)
+	  macro->uexp_number = existing_macro->value.macro->exp_number;
+      }
+
       /* Laziness can only affect the expansion tokens of the macro,
 	 not its fun-likeness or parameters.  */
       _cpp_maybe_notify_macro_use (pfile, node, location);
-- 
2.17.1

Reply via email to