---
 gcc/testsuite/c-c++-common/cpp/va_count.c |  35 ++
 libcpp/identifiers.cc                     |   2 +
 libcpp/include/cpplib.h                   |   3 +
 libcpp/init.cc                            |  58 ++--
 libcpp/internal.h                         |   3 +-
 libcpp/lex.cc                             |  24 ++
 libcpp/macro.cc                           | 368 +++++++++++++++++++++-
 libcpp/pch.cc                             |   1 +
 8 files changed, 461 insertions(+), 33 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/cpp/va_count.c

diff --git a/gcc/testsuite/c-c++-common/cpp/va_count.c 
b/gcc/testsuite/c-c++-common/cpp/va_count.c
new file mode 100644
index 00000000000..95579eb2310
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/cpp/va_count.c
@@ -0,0 +1,35 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c2y" { target c } } */
+
+#define SA(expr, val) _Static_assert((expr) == (val), #expr " != " #val)
+
+static const int x = __VA_COUNT__(1, 2, 3); // int x = 3;
+static const int y = __VA_COUNT__();        // int y = 0;
+#define MACRO(...) __VA_COUNT__(__VA_ARGS__)
+static const int z = MACRO(a, b, c, d);     // int z = 4;
+static const int w = MACRO();               // int w = 0;
+static const int a = MACRO(a, , c, d);      // int a = 4;
+static const int b = MACRO(a, (, c, )d);    // int b = 2;
+#define MACRO2(...) __VA_COUNT__(__VA_COUNT__( __VA_ARGS__ ))
+static const int c = MACRO2(a, b);          // int c = 1;
+static const int d = MACRO2();              // int d = 1;
+
+SA(x, 3);
+SA(y, 0);
+SA(z, 4);
+SA(w, 0);
+SA(a, 4);
+SA(b, 2);
+SA(c, 1);
+SA(d, 1);
+
+#if __VA_COUNT__(1,2,3) != 3
+# error bad __VA_COUNT__(1,2,3)
+#endif
+
+
+int
+main ()
+{
+  return x + y + z + w + a + b + c + d; // 3 + 0 + 4 + 0 + 4 + 2 + 1 + 1 = 15
+}
diff --git a/libcpp/identifiers.cc b/libcpp/identifiers.cc
index 0b56c276483..f79c8f34bec 100644
--- a/libcpp/identifiers.cc
+++ b/libcpp/identifiers.cc
@@ -80,6 +80,8 @@ _cpp_init_hashtable (cpp_reader *pfile, cpp_hash_table *table,
   s->n__VA_ARGS__->flags |= NODE_DIAGNOSTIC;
   s->n__VA_OPT__        = cpp_lookup (pfile, DSC("__VA_OPT__"));
   s->n__VA_OPT__->flags |= NODE_DIAGNOSTIC;
+  s->n__VA_COUNT__        = cpp_lookup (pfile, DSC("__VA_COUNT__"));
+  s->n__VA_COUNT__->flags |= NODE_DIAGNOSTIC;
   /* __has_include{,_next} are inited in cpp_init_builtins.  */
 }
 
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index bc0d7714e96..e3d004c75ca 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -545,6 +545,9 @@ struct cpp_options
   /* Nonzero for C++20 __VA_OPT__ feature.  */
   unsigned char va_opt;
 
+  /* Nonzero for C2y N3792 feature __VA_COUNT__. */
+  unsigned char va_count;
+
   /* Nonzero for the '::' token.  */
   unsigned char scope;
 
diff --git a/libcpp/init.cc b/libcpp/init.cc
index a480d1d4a26..bcaced7462e 100644
--- a/libcpp/init.cc
+++ b/libcpp/init.cc
@@ -114,6 +114,7 @@ struct lang_flags
   unsigned int embed : 1;
   unsigned int imaginary_constants : 1;
   unsigned int low_ucns : 1;
+  unsigned int va_count : 1;
 };
 
 static const struct lang_flags lang_defaults[] = {
@@ -124,34 +125,34 @@ static const struct lang_flags lang_defaults[] = {
                   c c n x c d s i l l l c s r l o o d l d d l d t f b m u
                   9 + u i 1 i t g i i i s e i i p p f i e i i u a a e a c
                   9 + m d 1 d d r t t t t p g t t e p t f r m c l l d g n */
-  /* GNUC89   */ { 0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
-  /* GNUC99   */ { 1,0,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
-  /* GNUC11   */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
-  /* GNUC17   */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
-  /* GNUC23   */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1 },
-  /* GNUC2Y   */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1 },
-  /* STDC89   */ { 0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
-  /* STDC94   */ { 0,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
-  /* STDC99   */ { 1,0,1,1,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
-  /* STDC11   */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
-  /* STDC17   */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
-  /* STDC23   */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1 },
-  /* STDC2Y   */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1 },
-  /* GNUCXX   */ { 0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* CXX98    */ { 0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* GNUCXX11 */ { 1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* CXX11    */ { 1,1,0,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* GNUCXX14 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* CXX14    */ { 1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* GNUCXX17 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* CXX17    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* GNUCXX20 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* CXX20    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1 },
-  /* GNUCXX23 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1 },
-  /* CXX23    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1 },
-  /* GNUCXX26 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1 },
-  /* CXX26    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1 },
-  /* ASM      */ { 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
+  /* GNUC89   */ { 0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* GNUC99   */ { 1,0,1,1,0,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* GNUC11   */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* GNUC17   */ { 1,0,1,1,1,0,0,1,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* GNUC23   */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1,0 },
+  /* GNUC2Y   */ { 1,0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1 },
+  /* STDC89   */ { 0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* STDC94   */ { 0,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* STDC99   */ { 1,0,1,1,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* STDC11   */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* STDC17   */ { 1,0,1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+  /* STDC23   */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,0,0,0,1,1,0,1,0 },
+  /* STDC2Y   */ { 1,0,1,1,1,1,1,1,1,0,0,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1 },
+  /* GNUCXX   */ { 0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* CXX98    */ { 0,1,0,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* GNUCXX11 */ { 1,1,1,1,1,1,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* CXX11    */ { 1,1,0,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* GNUCXX14 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* CXX14    */ { 1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* GNUCXX17 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* CXX17    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* GNUCXX20 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* CXX20    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0 },
+  /* GNUCXX23 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1,0 },
+  /* CXX23    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,0,1,0 },
+  /* GNUCXX26 */ { 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1,0 },
+  /* CXX26    */ { 1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,0,1,0 },
+  /* ASM      */ { 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
 };
 
 /* Sets internal flags correctly for a given language.  */
@@ -190,6 +191,7 @@ cpp_set_lang (cpp_reader *pfile, enum c_lang lang)
   CPP_OPTION (pfile, embed)                     = l->embed;
   CPP_OPTION (pfile, imaginary_constants)       = l->imaginary_constants;
   CPP_OPTION (pfile, low_ucns)                  = l->low_ucns;
+  CPP_OPTION (pfile, va_count)                  = l->va_count;
 }
 
 /* Initialize library global state.  */
diff --git a/libcpp/internal.h b/libcpp/internal.h
index 017e980a964..7f5d08fc379 100644
--- a/libcpp/internal.h
+++ b/libcpp/internal.h
@@ -263,7 +263,7 @@ struct lexer_state
      all directives apart from #define.  */
   unsigned char save_comments;
 
-  /* Nonzero if lexing __VA_ARGS__ and __VA_OPT__ are valid.  */
+  /* Nonzero if lexing __VA_ARGS__, __VA_COUNT__ and __VA_OPT__ are valid.  */
   unsigned char va_args_ok;
 
   /* Nonzero if lexing poisoned identifiers is valid.  */
@@ -306,6 +306,7 @@ struct spec_nodes
   cpp_hashnode *n_false;               /* C++ keyword false */
   cpp_hashnode *n__VA_ARGS__;          /* C99 vararg macros */
   cpp_hashnode *n__VA_OPT__;           /* C++ vararg macros */
+  cpp_hashnode *n__VA_COUNT__;        /* C2y N3792 vararg macros */
 
   enum {M_EXPORT, M_MODULE, M_IMPORT, M__IMPORT, M_HWM};
 
diff --git a/libcpp/lex.cc b/libcpp/lex.cc
index df278534eee..e0a7152de42 100644
--- a/libcpp/lex.cc
+++ b/libcpp/lex.cc
@@ -2146,6 +2146,26 @@ forms_identifier_p (cpp_reader *pfile, int first,
   return false;
 }
 
+/* Helper function to issue error about improper __VA_COUNT__ use.  */
+static void
+maybe_va_count_error (cpp_reader *pfile)
+{
+  if (CPP_PEDANTIC (pfile) && !CPP_OPTION (pfile, va_count))
+    {
+      /* __VA_COUNT__ should not be accepted at all, but allow it in
+        system headers.  */
+      if (!_cpp_in_system_header (pfile))
+       {
+         if (CPP_OPTION (pfile, cplusplus))
+           cpp_pedwarning (pfile, CPP_W_PEDANTIC,
+                           "%<__VA_COUNT__%> is not standardised or proposed 
for C++");
+         else
+           cpp_pedwarning (pfile, CPP_W_PEDANTIC,
+                           "%<__VA_COUNT__%> is not available until C2y");
+       }
+    }
+}
+
 /* Helper function to issue error about improper __VA_OPT__ use.  */
 static void
 maybe_va_opt_error (cpp_reader *pfile)
@@ -2214,6 +2234,10 @@ identifier_diagnostics_on_lex (cpp_reader *pfile, 
cpp_hashnode *node)
   if (node == pfile->spec_nodes.n__VA_OPT__)
     maybe_va_opt_error (pfile);
 
+  /* __VA_COUNT__ checks  */
+  if (node == pfile->spec_nodes.n__VA_COUNT__)
+    maybe_va_count_error (pfile);
+
   /* For -Wc++-compat, warn about use of C++ named operators.  */
   if (node->flags & NODE_WARN_OPERATOR)
     cpp_warning (pfile, CPP_W_CXX_OPERATOR_NAMES,
diff --git a/libcpp/macro.cc b/libcpp/macro.cc
index c29a34302b6..c7bafb60de0 100644
--- a/libcpp/macro.cc
+++ b/libcpp/macro.cc
@@ -93,6 +93,9 @@ struct macro_arg_saved_data {
 static const char *vaopt_paste_error =
   N_("'##' cannot appear at either end of __VA_OPT__");
 
+static const char *vacount_paren_error =
+  N_("%<__VA_COUNT__%> must be followed by an open parenthesis");
+
 static void expand_arg (cpp_reader *, macro_arg *);
 
 /* A class for tracking __VA_OPT__ state while iterating over a
@@ -276,6 +279,159 @@ class vaopt_state {
   update_type m_update;
 };
 
+/* A class for tracking __VA_COUNT__ state while iterating over a
+   sequence of tokens.  This is used during macro definition (to
+   validate structure) and during expansion (to count arguments and
+   replace the invocation).  */
+class vacount_state {
+ public:
+  enum update_type
+  {
+    ERROR,
+    DROP,
+    INCLUDE,
+    BEGIN,
+    END
+  };
+
+  vacount_state (cpp_reader *pfile)
+    : m_pfile (pfile),
+      m_stringify (false),
+      m_state (0),
+      m_paren_depth (0),
+      m_count (0),
+      m_started (false),
+      m_paste_left (false),
+      m_location (0),
+      m_flags (0)
+  {
+  }
+
+  /* Given a token, update the state of this tracker and return an
+     update_type indicating whether the token should be included in
+     the output stream.  For __VA_COUNT__, tokens of the invocation
+     are always dropped, and END indicates that a replacement token
+     should be emitted.  */
+  update_type update (const cpp_token *token)
+  {
+    /* Start recognizing a __VA_COUNT__ invocation only when we're not
+       already inside one.  Nested __VA_COUNT__ tokens inside the operand
+       list are just ordinary tokens and must not reset state or error.  */
+    if (m_state == 0
+       && token->type == CPP_NAME
+  && token->val.node.node == m_pfile->spec_nodes.n__VA_COUNT__) {
+       m_state = 1;
+       m_location = token->src_loc;
+       m_stringify = (token->flags & STRINGIFY_ARG) != 0;
+       m_flags = token->flags & (PREV_WHITE | PREV_FALLTHROUGH);
+       m_paren_depth = 0;
+       m_count = 0;
+       m_started = false;
+       m_paste_left = false;
+       return BEGIN;
+      }
+    else if (m_state == 1)
+      {
+       /* Require a following '(' (definition-time constraint).  */
+       if (token->type != CPP_OPEN_PAREN)
+         {
+           cpp_error_at (m_pfile, CPP_DL_ERROR, m_location,
+                         vacount_paren_error);
+           m_state = 0;
+           return ERROR;
+         }
+       m_state = 2;
+       /* Drop the '(' from output: the whole invocation is replaced.  */
+       return DROP;
+      }
+    else if (m_state >= 2)
+      {
+       /* Drop padding/comments inside the invocation, they should not
+          affect argument splitting.  */
+       if (token->type == CPP_PADDING
+           || token->type == CPP_COMMENT)
+         return DROP;
+
+       /* First significant token after '(' determines empty invocation.  */
+       if (!m_started)
+         {
+           if (token->type == CPP_CLOSE_PAREN)
+             {
+               /* __VA_COUNT__() => 0.  */
+               m_paste_left = (token->flags & PASTE_LEFT) != 0;
+               m_state = 0;
+               m_count = 0;
+               return END;
+             }
+           /* Non-empty invocation => at least one argument (possibly empty).  
*/
+           m_started = true;
+           m_count = 1;
+         }
+
+       if (token->type == CPP_OPEN_PAREN)
+         {
+           ++m_paren_depth;
+           return DROP;
+         }
+       else if (token->type == CPP_CLOSE_PAREN)
+         {
+           if (m_paren_depth-- == 0)
+             {
+               /* Final ')'.  */
+               m_paste_left = (token->flags & PASTE_LEFT) != 0;
+               m_state = 0;
+               return END;
+             }
+           return DROP;
+         }
+       else if (token->type == CPP_COMMA)
+         {
+           if (m_paren_depth == 0)
+             ++m_count;
+           return DROP;
+         }
+       else if (token->type == CPP_EOF
+                || (token->type == CPP_HASH && (token->flags & BOL)))
+         {
+      cpp_error_at (m_pfile, CPP_DL_ERROR, m_location,
+                         "unterminated %<__VA_COUNT__%>");
+           m_state = 0;
+           m_count = 0;
+           return ERROR;
+         }
+
+       return DROP;
+      }
+
+    return INCLUDE;
+  }
+
+  bool completed ()
+  {
+    if (m_state != 0)
+      cpp_error_at (m_pfile, CPP_DL_ERROR, m_location,
+                   "unterminated %<__VA_COUNT__%>");
+    return m_state == 0;
+  }
+
+  bool stringify () const { return m_stringify; }
+  unsigned long count () const { return m_count; }
+  bool paste_left () const { return m_paste_left; }
+  location_t location () const { return m_location; }
+  unsigned flags () const { return m_flags; }
+
+ private:
+  cpp_reader *m_pfile;
+  bool m_stringify;
+  int m_state;
+  unsigned m_paren_depth;
+  unsigned long m_count;
+  bool m_started;
+  bool m_paste_left;
+  location_t m_location;
+  unsigned m_flags;
+};
+
 /* Macro expansion.  */
 
 static cpp_macro *get_deferred_or_lazy_macro (cpp_reader *, cpp_hashnode *,
@@ -312,7 +468,8 @@ static const cpp_token **arg_token_ptr_at (const macro_arg 
*,
                                           size_t,
                                           enum macro_arg_token_kind,
                                           location_t **virt_location);
-
+static int maybe_expand_va_count (cpp_reader *, const cpp_token *,
+                                location_t);
 static void macro_arg_token_iter_init (macro_arg_token_iter *, bool,
                                       enum macro_arg_token_kind,
                                       const macro_arg *,
@@ -895,6 +1052,180 @@ builtin_macro (cpp_reader *pfile, cpp_hashnode *node,
   return 1;
 }
 
+/* Try to expand __VA_COUNT__(...) at the current point in the token stream.
+   NAME is the __VA_COUNT__ token we just read.  NAME_VIRT_LOC is the
+   virtual location associated to NAME (as returned by cpp_get_token_1).
+
+   On success, pushes a context containing a single token (either a
+   preprocessing-number token or, for #__VA_COUNT__, a string token)
+   and returns 1.  If there is no following '(', returns 0 and leaves
+   the token stream unchanged (as per function-like macro rules).  */
+static int
+maybe_expand_va_count (cpp_reader *pfile, const cpp_token *name,
+                      location_t name_virt_loc)
+{
+  const cpp_token *token, *padding = NULL;
+  location_t virt_loc = 0;
+  unsigned long m = 0;
+  bool stringify = (name->flags & STRINGIFY_ARG) != 0;
+  bool paste_left = false;
+  unsigned out_flags = name->flags & (PREV_WHITE | PREV_FALLTHROUGH);
+
+  /* Parse like funlike_invocation_p/collect_args: inhibit expansion while
+     recognizing and consuming the parenthesized argument list.  */
+  int saved_prevent_expansion = pfile->state.prevent_expansion;
+  int saved_keep_tokens = pfile->keep_tokens;
+  int saved_parsing_args = pfile->state.parsing_args;
+
+  pfile->state.prevent_expansion++;
+  pfile->keep_tokens++;
+  pfile->state.parsing_args = 1;
+
+  /* Search for the opening '(' while preserving intervening padding.  */
+  for (;;)
+    {
+      token = cpp_get_token_1 (pfile, &virt_loc);
+      if (token->type != CPP_PADDING)
+       break;
+      gcc_assert ((token->flags & PREV_WHITE) == 0);
+      if (padding == NULL
+         || padding->val.source == NULL
+         || (!(padding->val.source->flags & PREV_WHITE)
+             && token->val.source == NULL))
+       padding = token;
+    }
+
+  if (token->type != CPP_OPEN_PAREN)
+    {
+      /* Not an invocation: back up and re-insert padding if needed.  */
+      if (token->type != CPP_EOF || token == &pfile->endarg)
+       {
+         _cpp_backup_tokens (pfile, 1);
+         if (padding)
+           _cpp_push_token_context (pfile, NULL, padding, 1);
+       }
+      pfile->state.parsing_args = saved_parsing_args;
+      pfile->keep_tokens = saved_keep_tokens;
+      pfile->state.prevent_expansion = saved_prevent_expansion;
+      return 0;
+    }
+
+  /* We are in an invocation.  */
+  pfile->state.parsing_args = 2;
+
+  /* Use the same counting rule as function-like macro arg parsing:
+     m = 0 for empty list, else 1 + #top-level commas.  */
+  {
+    unsigned paren_depth = 0;
+    bool started = false;
+
+    for (;;)
+      {
+       token = cpp_get_token_1 (pfile, &virt_loc);
+
+       if (token->type == CPP_PADDING || token->type == CPP_COMMENT)
+         continue;
+
+       if (!started)
+         {
+           if (token->type == CPP_CLOSE_PAREN)
+             {
+               m = 0;
+               paste_left = (token->flags & PASTE_LEFT) != 0;
+               break;
+             }
+           started = true;
+           m = 1;
+         }
+
+       if (token->type == CPP_OPEN_PAREN)
+         ++paren_depth;
+       else if (token->type == CPP_CLOSE_PAREN)
+         {
+           if (paren_depth-- == 0)
+             {
+               paste_left = (token->flags & PASTE_LEFT) != 0;
+               break;
+             }
+         }
+       else if (token->type == CPP_COMMA)
+         {
+           if (paren_depth == 0)
+             ++m;
+         }
+       else if (token->type == CPP_EOF
+                || (token->type == CPP_HASH && (token->flags & BOL)))
+         {
+           cpp_error (pfile, CPP_DL_ERROR,
+                      "unterminated argument list invoking macro %qs",
+                      "__VA_COUNT__");
+           m = 0;
+           break;
+         }
+      }
+  }
+
+  pfile->state.parsing_args = saved_parsing_args;
+  pfile->keep_tokens = saved_keep_tokens;
+  pfile->state.prevent_expansion = saved_prevent_expansion;
+
+  if (m > (unsigned long) LONG_MAX)
+    {
+      cpp_error_with_line (pfile, CPP_DL_ERROR, name->src_loc, 0,
+                          "%<__VA_COUNT__%> result %lu is not representable "
+                          "as %<signed long%>", m);
+      m = 0;
+    }
+
+  /* Materialize the replacement token.  */
+  cpp_token *numtok = _cpp_temp_token (pfile);
+  {
+    size_t buflen = 3 * sizeof (long) + 2;
+    uchar *buf = _cpp_unaligned_alloc (pfile, buflen);
+    sprintf ((char *) buf, "%ld", (long) m);
+    numtok->type = CPP_NUMBER;
+    numtok->val.str.text = buf;
+    numtok->val.str.len = strlen ((const char *) buf);
+    numtok->src_loc = name->src_loc;
+    numtok->flags = out_flags | (paste_left ? PASTE_LEFT : 0);
+  }
+
+  const cpp_token *out = numtok;
+  if (stringify)
+    {
+      const cpp_token *arr[1] = { out };
+      out = stringify_arg (pfile, arr, 1);
+      /* Preserve spacing/paste semantics on the string token too.  */
+      cpp_token *t = _cpp_temp_token (pfile);
+      *t = *out;
+      t->flags |= out_flags;
+      if (paste_left)
+       t->flags |= PASTE_LEFT;
+      t->src_loc = name->src_loc;
+      out = t;
+    }
+
+  /* Push the token in its own context, like builtin_macro does.  */
+  if (pfile->context->tokens_kind == TOKENS_KIND_EXTENDED)
+    {
+      location_t *virt_locs = NULL;
+      _cpp_buff *token_buf = tokens_buff_new (pfile, 1, &virt_locs);
+      const line_map_macro *map =
+       linemap_enter_macro (pfile->line_table,
+                            pfile->spec_nodes.n__VA_COUNT__,
+                            name_virt_loc, 1);
+      tokens_buff_add_token (token_buf, virt_locs, out,
+                            name_virt_loc, name->src_loc, map, 0);
+      push_extended_tokens_context (pfile, pfile->spec_nodes.n__VA_COUNT__,
+                                   token_buf, virt_locs,
+                                   (const cpp_token **) token_buf->base, 1);
+    }
+  else
+    _cpp_push_token_context (pfile, NULL, out, 1);
+
+  return 1;
+}
+
 /* Copies SRC, of length LEN, to DEST, adding backslashes before all
    backslashes and double quotes. DEST must be of sufficient size.
    Returns a pointer to the end of the string.  */
@@ -3057,6 +3388,24 @@ cpp_get_token_1 (cpp_reader *pfile, location_t *location)
 
       node = result->val.node.node;
 
+      /* __VA_COUNT__(...) handling, modeled after __VA_OPT__ being a
+        special reserved identifier: expand only when it is actually
+        invoked (i.e. followed by '('), using function-like macro
+        argument parsing rules.  */
+      if (node == pfile->spec_nodes.n__VA_COUNT__)
+       {
+         if (!pfile->state.prevent_expansion
+             && maybe_expand_va_count (pfile, result, virt_loc))
+           {
+             if (pfile->state.in_directive)
+               continue;
+             result = padding_token (pfile, result);
+             goto out;
+           }
+         /* Not an invocation => leave as-is.  */
+         break;
+       }
+
       if (node->type == NT_VOID || (result->flags & NO_EXPAND))
        break;
 
@@ -3791,9 +4140,10 @@ create_iso_definition (cpp_reader *pfile)
       if (macro->count > 1 && token[-1].type == CPP_HASH && macro->fun_like)
        {
          if (token->type == CPP_MACRO_ARG
-             || (macro->variadic
-                 && token->type == CPP_NAME
-                 && token->val.node.node == pfile->spec_nodes.n__VA_OPT__))
+             || (token->type == CPP_NAME
+                 && (token->val.node.node == pfile->spec_nodes.n__VA_COUNT__
+                     || (macro->variadic
+                         && token->val.node.node == 
pfile->spec_nodes.n__VA_OPT__))))
            {
              if (token->flags & PREV_WHITE)
                token->flags |= SP_PREV_WHITE;
@@ -3826,6 +4176,16 @@ create_iso_definition (cpp_reader *pfile)
            }
          if (!vaopt_tracker.completed ())
            goto out;
+    /* Validate balanced __VA_COUNT__(...) in macro definitions.  */
+         {
+           vacount_state vacount_tracker (pfile);
+           /* Walk the replacement list tokens already collected.  */
+           for (unsigned j = 0; j < macro->count; ++j)
+             if (vacount_tracker.update (&macro->exp.tokens[j]) == 
vacount_state::ERROR)
+               goto out;
+           if (!vacount_tracker.completed ())
+             goto out;
+         }
          break;
        }
 
diff --git a/libcpp/pch.cc b/libcpp/pch.cc
index b27c299eb3f..04fc53a2942 100644
--- a/libcpp/pch.cc
+++ b/libcpp/pch.cc
@@ -852,6 +852,7 @@ cpp_read_state (cpp_reader *r, const char *name, FILE *f,
     s->n_false         = cpp_lookup (r, DSC("false"));
     s->n__VA_ARGS__     = cpp_lookup (r, DSC("__VA_ARGS__"));
     s->n__VA_OPT__      = cpp_lookup (r, DSC("__VA_OPT__"));
+    s->n__VA_COUNT__    = cpp_lookup (r, DSC("__VA_COUNT__"));
   }
 
   old_state = r->state;
-- 
2.51.0

Reply via email to