diff --git a/gcc/doc/cpp.texi b/gcc/doc/cpp.texi
index 90b2767e39a..d52450958d7 100644
--- a/gcc/doc/cpp.texi
+++ b/gcc/doc/cpp.texi
@@ -1941,6 +1941,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{#if __EXP_COUNTER__}.
+When @code{__EXP_COUNTER__} occurs in the replacement token sequence
+of a macro, it expands to 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/ChangeLog b/gcc/testsuite/ChangeLog
index e147f69c8eb..fee0ae0ad4b 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,11 @@
+2022-04-21  Kaz Kylheku  <kaz@kylheku.com>
+
+	* 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.
+
  2022-04-15  Paul A. Clarke  <pc@us.ibm.com>

  	* g++.dg/debug/dwarf2/const2.C: Move to g++.target/powerpc.
diff --git a/gcc/testsuite/gcc.dg/cpp/expcounter1.c 
b/gcc/testsuite/gcc.dg/cpp/expcounter1.c
new file mode 100644
index 00000000000..21bb89c7d13
--- /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 "-:redefined" }
+#define __UEXP_COUNTER__ 73 // { dg-warning "-: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/ChangeLog b/libcpp/ChangeLog
index a0242895188..024924f9fe1 100644
--- a/libcpp/ChangeLog
+++ b/libcpp/ChangeLog
@@ -1,3 +1,52 @@
+2022-04-21  Kaz Kylheku  <kaz@kylheku.com>
+
+	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__.
+
+	* gcc/doc/cpp.texi (__EXP_COUNTER__, __UEXP_COUNTER__):
+	Special built-in macros documented.
+
+	* 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 the macro's uexp_number: the parent context's
+	exp_number.  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.
+
  2022-02-11  Joseph Myers  <joseph@codesourcery.com>

  	* Makefile.in (po/$(PACKAGE).pot): Also handle cpp_warning_at,
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 3eba6f74b57..f3c724a1a15 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -827,6 +827,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;

@@ -920,6 +926,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 f4ab83d2145..60b0e482412 100644
--- a/libcpp/init.cc
+++ b/libcpp/init.cc
@@ -411,6 +411,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 8ebf360c03c..5a14a655c0f 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.  */
