diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index cd54c517b18..5890c18bdc3 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -4064,7 +4064,8 @@ static tree builtin_types[(int) BT_LAST + 1];
 
 /* A helper function for c_common_nodes_and_builtins.  Build function type
    for DEF with return type RET and N arguments.  If VAR is true, then the
-   function should be variadic after those N arguments.
+   function should be variadic after those N arguments, or, if N is zero,
+   unprototyped.
 
    Takes special care not to ICE if any of the types involved are
    error_mark_node, which indicates that said type is not in fact available
@@ -4093,7 +4094,10 @@ def_fn_type (builtin_type def, builtin_type ret, bool var, int n, ...)
   if (t == error_mark_node)
     goto egress;
   if (var)
-    t = build_varargs_function_type_array (t, n, args);
+    if (n == 0)
+      t = build_function_type (t, NULL_TREE);
+    else
+      t = build_varargs_function_type_array (t, n, args);
   else
     t = build_function_type_array (t, n, args);
 
@@ -4661,8 +4665,7 @@ c_common_nodes_and_builtins (void)
     uintptr_type_node =
       TREE_TYPE (identifier_global_value (c_get_ident (UINTPTR_TYPE)));
 
-  default_function_type
-    = build_varargs_function_type_list (integer_type_node, NULL_TREE);
+  default_function_type = build_function_type (integer_type_node, NULL_TREE);
   unsigned_ptrdiff_type_node = c_common_unsigned_type (ptrdiff_type_node);
 
   lang_hooks.decls.pushdecl
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 01d480759ae..5f9f65f9d5b 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -1508,8 +1508,8 @@ fall-virtual
 C++ ObjC++ WarnRemoved
 
 fallow-parameterless-variadic-functions
-C ObjC Var(flag_allow_parameterless_variadic_functions)
-Allow variadic functions without named parameter.
+C ObjC Ignore
+Does nothing. Preserved for backward compatibility.
 
 falt-external-templates
 C++ ObjC++ WarnRemoved
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 80f6e912187..1c29a29b50f 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -7252,7 +7252,8 @@ grokdeclarator (const struct c_declarator *declarator,
 	      }
 	    type_quals = TYPE_UNQUALIFIED;
 
-	    type = build_function_type (type, arg_types);
+	    type = build_function_type (type, arg_types,
+					arg_info->no_named_args_stdarg_p);
 	    declarator = declarator->declarator;
 
 	    /* Set the TYPE_CONTEXTs for each tagged type which is local to
@@ -8017,7 +8018,8 @@ grokparms (struct c_arg_info *arg_info, bool funcdef_flag)
       /* In C2X, convert () to (void).  */
       if (flag_isoc2x
 	  && !arg_types
-	  && !arg_info->parms)
+	  && !arg_info->parms
+	  && !arg_info->no_named_args_stdarg_p)
 	arg_types = arg_info->types = void_list_node;
 
       /* If there is a parameter of incomplete type in a definition,
@@ -8087,6 +8089,7 @@ build_arg_info (void)
   ret->others = NULL_TREE;
   ret->pending_sizes = NULL;
   ret->had_vla_unspec = 0;
+  ret->no_named_args_stdarg_p = 0;
   return ret;
 }
 
@@ -8278,6 +8281,7 @@ get_parm_info (bool ellipsis, tree expr)
   arg_info->types = types;
   arg_info->others = others;
   arg_info->pending_sizes = expr;
+  arg_info->no_named_args_stdarg_p = ellipsis && !types;
   return arg_info;
 }
 
@@ -9815,7 +9819,8 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
       /* Make it return void instead.  */
       TREE_TYPE (decl1)
 	= build_function_type (void_type_node,
-			       TYPE_ARG_TYPES (TREE_TYPE (decl1)));
+			       TYPE_ARG_TYPES (TREE_TYPE (decl1)),
+			       TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (decl1)));
     }
 
   if (warn_about_return_type)
@@ -10414,7 +10419,7 @@ store_parm_decls (void)
      empty argument list was converted to (void) in grokparms; in
      older C standard versions, it does not give the function a type
      with a prototype for future calls.  */
-  proto = arg_info->types != 0;
+  proto = arg_info->types != 0 || arg_info->no_named_args_stdarg_p;
 
   if (proto)
     store_parm_decls_newstyle (fndecl, arg_info);
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 602e0235f2d..31438464e4e 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -4119,7 +4119,8 @@ c_parser_direct_declarator (c_parser *parser, bool type_seen_p, c_dtr_syn kind,
       if (kind != C_DTR_NORMAL
 	  && (c_parser_next_token_starts_declspecs (parser)
 	      || (!have_gnu_attrs
-		  && c_parser_nth_token_starts_std_attributes (parser, 1))
+		  && (c_parser_nth_token_starts_std_attributes (parser, 1)
+		      || c_parser_next_token_is (parser, CPP_ELLIPSIS)))
 	      || c_parser_next_token_is (parser, CPP_CLOSE_PAREN)))
 	{
 	  struct c_arg_info *args
@@ -4395,25 +4396,18 @@ c_parser_parms_list_declarator (c_parser *parser, tree attrs, tree expr,
       c_parser_consume_token (parser);
       return ret;
     }
-  if (c_parser_next_token_is (parser, CPP_ELLIPSIS))
+  if (c_parser_next_token_is (parser, CPP_ELLIPSIS) && !have_gnu_attrs)
     {
       struct c_arg_info *ret = build_arg_info ();
 
-      if (flag_allow_parameterless_variadic_functions)
-        {
-          /* F (...) is allowed.  */
-          ret->types = NULL_TREE;
-        }
-      else
-        {
-          /* Suppress -Wold-style-definition for this case.  */
-          ret->types = error_mark_node;
-          error_at (c_parser_peek_token (parser)->location,
-                    "ISO C requires a named argument before %<...%>");
-        }
+      ret->types = NULL_TREE;
+      pedwarn_c11 (c_parser_peek_token (parser)->location, OPT_Wpedantic,
+		   "ISO C requires a named argument before %<...%> "
+		   "before C2X");
       c_parser_consume_token (parser);
       if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
 	{
+	  ret->no_named_args_stdarg_p = true;
 	  c_parser_consume_token (parser);
 	  return ret;
 	}
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index e7cdd2f11dc..1bdadfffa52 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -457,6 +457,8 @@ struct c_arg_info {
   tree pending_sizes;
   /* True when these arguments had [*].  */
   BOOL_BITFIELD had_vla_unspec : 1;
+  /* True when the arguments are a (...) prototype.  */
+  BOOL_BITFIELD no_named_args_stdarg_p : 1;
 };
 
 /* A declarator.  */
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index fdb96c28c51..868fb781c47 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -542,17 +542,19 @@ composite_type (tree t1, tree t2)
 
 	/* Simple way if one arg fails to specify argument types.  */
 	if (TYPE_ARG_TYPES (t1) == NULL_TREE)
-	 {
-	    t1 = build_function_type (valtype, TYPE_ARG_TYPES (t2));
+	  {
+	    t1 = build_function_type (valtype, TYPE_ARG_TYPES (t2),
+				      TYPE_NO_NAMED_ARGS_STDARG_P (t2));
 	    t1 = build_type_attribute_variant (t1, attributes);
 	    return qualify_type (t1, t2);
 	 }
 	if (TYPE_ARG_TYPES (t2) == NULL_TREE)
-	 {
-	   t1 = build_function_type (valtype, TYPE_ARG_TYPES (t1));
-	   t1 = build_type_attribute_variant (t1, attributes);
-	   return qualify_type (t1, t2);
-	 }
+	  {
+	    t1 = build_function_type (valtype, TYPE_ARG_TYPES (t1),
+				      TYPE_NO_NAMED_ARGS_STDARG_P (t1));
+	    t1 = build_type_attribute_variant (t1, attributes);
+	    return qualify_type (t1, t2);
+	  }
 
 	/* If both args specify argument types, we must merge the two
 	   lists, argument by argument.  */
@@ -1700,6 +1702,8 @@ function_types_compatible_p (const_tree f1, const_tree f2,
 
   if (args1 == NULL_TREE)
     {
+      if (TYPE_NO_NAMED_ARGS_STDARG_P (f1) != TYPE_NO_NAMED_ARGS_STDARG_P (f2))
+	return 0;
       if (!self_promoting_args_p (args2))
 	return 0;
       /* If one of these types comes from a non-prototype fn definition,
@@ -1713,6 +1717,8 @@ function_types_compatible_p (const_tree f1, const_tree f2,
     }
   if (args2 == NULL_TREE)
     {
+      if (TYPE_NO_NAMED_ARGS_STDARG_P (f1) != TYPE_NO_NAMED_ARGS_STDARG_P (f2))
+	return 0;
       if (!self_promoting_args_p (args1))
 	return 0;
       if (TYPE_ACTUAL_ARG_TYPES (f2)
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 1d0f994f281..3e32d3f2b23 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -19856,7 +19856,8 @@ aarch64_setup_incoming_varargs (cumulative_args_t cum_v,
      argument.  Advance a local copy of CUM past the last "real" named
      argument, to find out how many registers are left over.  */
   local_cum = *cum;
-  aarch64_function_arg_advance (pack_cumulative_args(&local_cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    aarch64_function_arg_advance (pack_cumulative_args(&local_cum), arg);
 
   /* Found out how many registers we need to save.
      Honor tree-stdvar analysis results.  */
diff --git a/gcc/config/alpha/alpha.cc b/gcc/config/alpha/alpha.cc
index 66c17149d4d..333f2c602c4 100644
--- a/gcc/config/alpha/alpha.cc
+++ b/gcc/config/alpha/alpha.cc
@@ -6084,8 +6084,9 @@ alpha_setup_incoming_varargs (cumulative_args_t pcum,
 {
   CUMULATIVE_ARGS cum = *get_cumulative_args (pcum);
 
-  /* Skip the current argument.  */
-  targetm.calls.function_arg_advance (pack_cumulative_args (&cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    /* Skip the current argument.  */
+    targetm.calls.function_arg_advance (pack_cumulative_args (&cum), arg);
 
 #if TARGET_ABI_OPEN_VMS
   /* For VMS, we allocate space for all 6 arg registers plus a count.
diff --git a/gcc/config/arc/arc.cc b/gcc/config/arc/arc.cc
index e6f52d87714..604a116e966 100644
--- a/gcc/config/arc/arc.cc
+++ b/gcc/config/arc/arc.cc
@@ -2450,7 +2450,8 @@ arc_setup_incoming_varargs (cumulative_args_t args_so_far,
   /* We must treat `__builtin_va_alist' as an anonymous arg.  */
 
   next_cum = *get_cumulative_args (args_so_far);
-  arc_function_arg_advance (pack_cumulative_args (&next_cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    arc_function_arg_advance (pack_cumulative_args (&next_cum), arg);
   first_anon_arg = next_cum;
 
   if (FUNCTION_ARG_REGNO_P (first_anon_arg))
diff --git a/gcc/config/arm/arm.cc b/gcc/config/arm/arm.cc
index ee8f1babf8a..2eb4d51e4a3 100644
--- a/gcc/config/arm/arm.cc
+++ b/gcc/config/arm/arm.cc
@@ -29143,7 +29143,8 @@ arm_setup_incoming_varargs (cumulative_args_t pcum_v,
   if (pcum->pcs_variant <= ARM_PCS_AAPCS_LOCAL)
     {
       nregs = pcum->aapcs_ncrn;
-      if (nregs & 1)
+      if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl))
+	  && (nregs & 1))
 	{
 	  int res = arm_needs_doubleword_align (arg.mode, arg.type);
 	  if (res < 0 && warn_psabi)
diff --git a/gcc/config/csky/csky.cc b/gcc/config/csky/csky.cc
index f7b2bf8e7c1..537eee6ab88 100644
--- a/gcc/config/csky/csky.cc
+++ b/gcc/config/csky/csky.cc
@@ -2086,7 +2086,8 @@ csky_setup_incoming_varargs (cumulative_args_t pcum_v,
 
   cfun->machine->uses_anonymous_args = 1;
   local_cum = *pcum;
-  csky_function_arg_advance (local_cum_v, arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    csky_function_arg_advance (local_cum_v, arg);
   regs_to_push = CSKY_NPARM_REGS - local_cum.reg;
   if (regs_to_push)
     *pretend_size  = regs_to_push * UNITS_PER_WORD;
diff --git a/gcc/config/epiphany/epiphany.cc b/gcc/config/epiphany/epiphany.cc
index f8c04934085..c4e3ceaeb2a 100644
--- a/gcc/config/epiphany/epiphany.cc
+++ b/gcc/config/epiphany/epiphany.cc
@@ -727,11 +727,13 @@ epiphany_setup_incoming_varargs (cumulative_args_t cum,
   machine_function_t *mf = MACHINE_FUNCTION (cfun);
 
   /* All BLKmode values are passed by reference.  */
-  gcc_assert (arg.mode != BLKmode);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    gcc_assert (arg.mode != BLKmode);
 
   next_cum = *get_cumulative_args (cum);
-  next_cum = (ROUND_ADVANCE_CUM (next_cum, arg.mode, arg.type)
-	      + ROUND_ADVANCE_ARG (arg.mode, arg.type));
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    next_cum = (ROUND_ADVANCE_CUM (next_cum, arg.mode, arg.type)
+		+ ROUND_ADVANCE_ARG (arg.mode, arg.type));
   first_anon_arg = next_cum;
 
   if (first_anon_arg < MAX_EPIPHANY_PARM_REGS && !no_rtl)
diff --git a/gcc/config/fr30/fr30.cc b/gcc/config/fr30/fr30.cc
index c9b061d218b..334bb44e37f 100644
--- a/gcc/config/fr30/fr30.cc
+++ b/gcc/config/fr30/fr30.cc
@@ -471,16 +471,19 @@ fr30_setup_incoming_varargs (cumulative_args_t arg_regs_used_so_far_v,
     = get_cumulative_args (arg_regs_used_so_far_v);
   int size;
 
-  /* All BLKmode values are passed by reference.  */
-  gcc_assert (arg.mode != BLKmode);
-
-  /* ??? This run-time test as well as the code inside the if
-     statement is probably unnecessary.  */
-  if (targetm.calls.strict_argument_naming (arg_regs_used_so_far_v))
-    /* If TARGET_STRICT_ARGUMENT_NAMING returns true, then the last named
-       arg must not be treated as an anonymous arg.  */
-    /* ??? This is a pointer increment, which makes no sense.  */
-    arg_regs_used_so_far += fr30_num_arg_regs (arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    {
+      /* All BLKmode values are passed by reference.  */
+      gcc_assert (arg.mode != BLKmode);
+
+      /* ??? This run-time test as well as the code inside the if
+	 statement is probably unnecessary.  */
+      if (targetm.calls.strict_argument_naming (arg_regs_used_so_far_v))
+	/* If TARGET_STRICT_ARGUMENT_NAMING returns true, then the last named
+	   arg must not be treated as an anonymous arg.  */
+	/* ??? This is a pointer increment, which makes no sense.  */
+	arg_regs_used_so_far += fr30_num_arg_regs (arg);
+    }
 
   size = FR30_NUM_ARG_REGS - (* arg_regs_used_so_far);
 
diff --git a/gcc/config/frv/frv.cc b/gcc/config/frv/frv.cc
index 6f1904b358c..5cdb0bfe6e9 100644
--- a/gcc/config/frv/frv.cc
+++ b/gcc/config/frv/frv.cc
@@ -2104,7 +2104,8 @@ frv_setup_incoming_varargs (cumulative_args_t cum_v,
 {
   CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
 
-  if (TARGET_DEBUG_ARG)
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl))
+      && TARGET_DEBUG_ARG)
     fprintf (stderr,
 	     "setup_vararg: words = %2d, mode = %4s, pretend_size = %d, second_time = %d\n",
 	     *cum, GET_MODE_NAME (arg.mode), *pretend_size, second_time);
diff --git a/gcc/config/ft32/ft32.cc b/gcc/config/ft32/ft32.cc
index ed2d1229d61..d6b73d48686 100644
--- a/gcc/config/ft32/ft32.cc
+++ b/gcc/config/ft32/ft32.cc
@@ -634,8 +634,10 @@ ft32_setup_incoming_varargs (cumulative_args_t cum_v,
 			     int *pretend_size, int no_rtl ATTRIBUTE_UNUSED)
 {
   CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
-  int named_size =
-    GET_MODE_SIZE (SImode) * (*cum - FT32_R0) + GET_MODE_SIZE (arg.mode);
+  int named_size = 0;
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    named_size =
+      GET_MODE_SIZE (SImode) * (*cum - FT32_R0) + GET_MODE_SIZE (arg.mode);
 
   if (named_size < 24)
     *pretend_size = 24 - named_size;
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 480db35f6cd..e92b12129b8 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -4559,7 +4559,8 @@ ix86_setup_incoming_varargs (cumulative_args_t cum_v,
   /* For varargs, we do not want to skip the dummy va_dcl argument.
      For stdargs, we do want to skip the last named argument.  */
   next_cum = *cum;
-  if (stdarg_p (fntype))
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl))
+      && stdarg_p (fntype))
     ix86_function_arg_advance (pack_cumulative_args (&next_cum), arg);
 
   if (cum->call_abi == MS_ABI)
diff --git a/gcc/config/ia64/ia64.cc b/gcc/config/ia64/ia64.cc
index 995ff906940..6df1ce736bc 100644
--- a/gcc/config/ia64/ia64.cc
+++ b/gcc/config/ia64/ia64.cc
@@ -4596,8 +4596,9 @@ ia64_setup_incoming_varargs (cumulative_args_t cum,
 {
   CUMULATIVE_ARGS next_cum = *get_cumulative_args (cum);
 
-  /* Skip the current argument.  */
-  ia64_function_arg_advance (pack_cumulative_args (&next_cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    /* Skip the current argument.  */
+    ia64_function_arg_advance (pack_cumulative_args (&next_cum), arg);
 
   if (next_cum.words < MAX_ARGUMENT_SLOTS)
     {
diff --git a/gcc/config/loongarch/loongarch.cc b/gcc/config/loongarch/loongarch.cc
index e9ba3374e35..f54c233f90c 100644
--- a/gcc/config/loongarch/loongarch.cc
+++ b/gcc/config/loongarch/loongarch.cc
@@ -756,7 +756,8 @@ loongarch_setup_incoming_varargs (cumulative_args_t cum,
      argument.  Advance a local copy of CUM past the last "real" named
      argument, to find out how many registers are left over.  */
   local_cum = *get_cumulative_args (cum);
-  loongarch_function_arg_advance (pack_cumulative_args (&local_cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    loongarch_function_arg_advance (pack_cumulative_args (&local_cum), arg);
 
   /* Found out how many registers we need to save.  */
   gp_saved = MAX_ARGS_IN_REGISTERS - local_cum.num_gprs;
diff --git a/gcc/config/m32r/m32r.cc b/gcc/config/m32r/m32r.cc
index bca768172b7..e3489fb4dc0 100644
--- a/gcc/config/m32r/m32r.cc
+++ b/gcc/config/m32r/m32r.cc
@@ -1287,11 +1287,15 @@ m32r_setup_incoming_varargs (cumulative_args_t cum,
     return;
 
   /* All BLKmode values are passed by reference.  */
-  gcc_assert (arg.mode != BLKmode);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    gcc_assert (arg.mode != BLKmode);
 
-  first_anon_arg = (ROUND_ADVANCE_CUM (*get_cumulative_args (cum),
-				       arg.mode, arg.type)
-		    + ROUND_ADVANCE_ARG (arg.mode, arg.type));
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    first_anon_arg = (ROUND_ADVANCE_CUM (*get_cumulative_args (cum),
+					 arg.mode, arg.type)
+		      + ROUND_ADVANCE_ARG (arg.mode, arg.type));
+  else
+    first_anon_arg = *get_cumulative_args (cum);
 
   if (first_anon_arg < M32R_MAX_PARM_REGS)
     {
diff --git a/gcc/config/mcore/mcore.cc b/gcc/config/mcore/mcore.cc
index 28e707496d1..605d63b6a70 100644
--- a/gcc/config/mcore/mcore.cc
+++ b/gcc/config/mcore/mcore.cc
@@ -1953,8 +1953,9 @@ mcore_setup_incoming_varargs (cumulative_args_t args_so_far_v,
   /* We need to know how many argument registers are used before
      the varargs start, so that we can push the remaining argument
      registers during the prologue.  */
-  number_of_regs_before_varargs
-    = *args_so_far + mcore_num_arg_regs (arg.mode, arg.type);
+  number_of_regs_before_varargs = *args_so_far;
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    number_of_regs_before_varargs += mcore_num_arg_regs (arg.mode, arg.type);
   
   /* There is a bug somewhere in the arg handling code.
      Until I can find it this workaround always pushes the
diff --git a/gcc/config/mips/mips.cc b/gcc/config/mips/mips.cc
index 387376b3df8..53478e9227b 100644
--- a/gcc/config/mips/mips.cc
+++ b/gcc/config/mips/mips.cc
@@ -6683,7 +6683,8 @@ mips_setup_incoming_varargs (cumulative_args_t cum,
      argument.  Advance a local copy of CUM past the last "real" named
      argument, to find out how many registers are left over.  */
   local_cum = *get_cumulative_args (cum);
-  mips_function_arg_advance (pack_cumulative_args (&local_cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    mips_function_arg_advance (pack_cumulative_args (&local_cum), arg);
 
   /* Found out how many registers we need to save.  */
   gp_saved = MAX_ARGS_IN_REGISTERS - local_cum.num_gprs;
diff --git a/gcc/config/mmix/mmix.cc b/gcc/config/mmix/mmix.cc
index ffdd8c71cc1..1ac7b883ac5 100644
--- a/gcc/config/mmix/mmix.cc
+++ b/gcc/config/mmix/mmix.cc
@@ -999,7 +999,8 @@ mmix_setup_incoming_varargs (cumulative_args_t args_so_farp_v,
 
   /* We assume that one argument takes up one register here.  That should
      be true until we start messing with multi-reg parameters.  */
-  if ((7 + (MMIX_FUNCTION_ARG_SIZE (arg.mode, arg.type))) / 8 != 1)
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl))
+      && (7 + (MMIX_FUNCTION_ARG_SIZE (arg.mode, arg.type))) / 8 != 1)
     internal_error ("MMIX Internal: Last named vararg would not fit in a register");
 }
 
diff --git a/gcc/config/nds32/nds32.cc b/gcc/config/nds32/nds32.cc
index 67a612130fe..639baef6c17 100644
--- a/gcc/config/nds32/nds32.cc
+++ b/gcc/config/nds32/nds32.cc
@@ -2377,9 +2377,12 @@ nds32_setup_incoming_varargs (cumulative_args_t ca,
      for varargs.  */
   total_args_regs
     = NDS32_MAX_GPR_REGS_FOR_ARGS + NDS32_GPR_ARG_FIRST_REGNUM;
-  num_of_used_regs
-    = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, arg.mode, arg.type)
-      + NDS32_NEED_N_REGS_FOR_ARG (arg.mode, arg.type);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    num_of_used_regs
+      = NDS32_AVAILABLE_REGNUM_FOR_GPR_ARG (cum->gpr_offset, arg.mode, arg.type)
+        + NDS32_NEED_N_REGS_FOR_ARG (arg.mode, arg.type);
+  else
+    num_of_used_regs = cum->gpr_offset + NDS32_GPR_ARG_FIRST_REGNUM;
 
   remaining_reg_count = total_args_regs - num_of_used_regs;
   *pretend_args_size = remaining_reg_count * UNITS_PER_WORD;
diff --git a/gcc/config/nios2/nios2.cc b/gcc/config/nios2/nios2.cc
index 1a33c88f19f..6a894ec345e 100644
--- a/gcc/config/nios2/nios2.cc
+++ b/gcc/config/nios2/nios2.cc
@@ -3524,7 +3524,8 @@ nios2_setup_incoming_varargs (cumulative_args_t cum_v,
 
   cfun->machine->uses_anonymous_args = 1;
   local_cum = *cum;
-  nios2_function_arg_advance (local_cum_v, arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    nios2_function_arg_advance (local_cum_v, arg);
 
   regs_to_push = NUM_ARG_REGS - local_cum.regs_used;
 
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index ad57b995e7b..04d21ec8f3a 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -3728,7 +3728,8 @@ riscv_setup_incoming_varargs (cumulative_args_t cum,
      argument.  Advance a local copy of CUM past the last "real" named
      argument, to find out how many registers are left over.  */
   local_cum = *get_cumulative_args (cum);
-  riscv_function_arg_advance (pack_cumulative_args (&local_cum), arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    riscv_function_arg_advance (pack_cumulative_args (&local_cum), arg);
 
   /* Found out how many registers we need to save.  */
   gp_saved = MAX_ARGS_IN_REGISTERS - local_cum.num_gprs;
diff --git a/gcc/config/rs6000/rs6000-call.cc b/gcc/config/rs6000/rs6000-call.cc
index ac3cb7e3d36..6da4de67137 100644
--- a/gcc/config/rs6000/rs6000-call.cc
+++ b/gcc/config/rs6000/rs6000-call.cc
@@ -2253,7 +2253,9 @@ setup_incoming_varargs (cumulative_args_t cum,
 
   /* Skip the last named argument.  */
   next_cum = *get_cumulative_args (cum);
-  rs6000_function_arg_advance_1 (&next_cum, arg.mode, arg.type, arg.named, 0);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    rs6000_function_arg_advance_1 (&next_cum, arg.mode, arg.type, arg.named,
+				   0);
 
   if (DEFAULT_ABI == ABI_V4)
     {
@@ -2327,7 +2329,8 @@ setup_incoming_varargs (cumulative_args_t cum,
       first_reg_offset = next_cum.words;
       save_area = crtl->args.internal_arg_pointer;
 
-      if (targetm.calls.must_pass_in_stack (arg))
+      if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl))
+	  && targetm.calls.must_pass_in_stack (arg))
 	first_reg_offset += rs6000_arg_size (TYPE_MODE (arg.type), arg.type);
     }
 
diff --git a/gcc/config/sh/sh.cc b/gcc/config/sh/sh.cc
index 9bee618b639..1aec70a23d8 100644
--- a/gcc/config/sh/sh.cc
+++ b/gcc/config/sh/sh.cc
@@ -8183,11 +8183,12 @@ sh_setup_incoming_varargs (cumulative_args_t ca,
   gcc_assert (cfun->stdarg);
   if (TARGET_VARARGS_PRETEND_ARGS (current_function_decl))
     {
-      int named_parm_regs, anon_parm_regs;
+      int named_parm_regs = 0, anon_parm_regs;
 
-      named_parm_regs = (sh_round_reg (*get_cumulative_args (ca), arg.mode)
-			 + CEIL (arg.promoted_size_in_bytes (),
-				 UNITS_PER_WORD));
+      if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+	named_parm_regs = (sh_round_reg (*get_cumulative_args (ca), arg.mode)
+			   + CEIL (arg.promoted_size_in_bytes (),
+				   UNITS_PER_WORD));
       anon_parm_regs = NPARM_REGS (SImode) - named_parm_regs;
       if (anon_parm_regs > 0)
 	*pretend_arg_size = anon_parm_regs * 4;
diff --git a/gcc/config/visium/visium.cc b/gcc/config/visium/visium.cc
index 03c1a33e1b9..e7d15960fc7 100644
--- a/gcc/config/visium/visium.cc
+++ b/gcc/config/visium/visium.cc
@@ -1481,7 +1481,8 @@ visium_setup_incoming_varargs (cumulative_args_t pcum_v,
   /* The caller has advanced ARGS_SO_FAR up to, but not beyond, the last named
      argument.  Advance a local copy of ARGS_SO_FAR past the last "real" named
      argument, to find out how many registers are left over.  */
-  TARGET_FUNCTION_ARG_ADVANCE (local_args_so_far, arg);
+  if (!TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (current_function_decl)))
+    TARGET_FUNCTION_ARG_ADVANCE (local_args_so_far, arg);
 
   /* Find how many registers we need to save.  */
   locargs = get_cumulative_args (local_args_so_far);
diff --git a/gcc/config/vms/vms-c.cc b/gcc/config/vms/vms-c.cc
index 2f74fb574cd..ccf6d5fe3b6 100644
--- a/gcc/config/vms/vms-c.cc
+++ b/gcc/config/vms/vms-c.cc
@@ -455,9 +455,6 @@ vms_c_register_includes (const char *sysroot,
 void
 vms_c_common_override_options (void)
 {
-  /* Allow variadic functions without parameters (as declared in starlet).  */
-  flag_allow_parameterless_variadic_functions = TRUE;
-
   /* Initialize c_default_pointer_mode.  */
   switch (flag_vms_pointer_size)
     {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index cd4d3c1d72c..9f1481bbeb1 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -198,7 +198,7 @@ in the following sections.
 @item C Language Options
 @xref{C Dialect Options,,Options Controlling C Dialect}.
 @gccoptlist{-ansi  -std=@var{standard}  -aux-info @var{filename} @gol
--fallow-parameterless-variadic-functions  -fno-asm  @gol
+-fno-asm  @gol
 -fno-builtin  -fno-builtin-@var{function}  -fcond-mismatch @gol
 -ffreestanding  -fgimple  -fgnu-tm  -fgnu89-inline  -fhosted @gol
 -flax-vector-conversions  -fms-extensions @gol
@@ -2514,14 +2514,6 @@ character).  In the case of function definitions, a K&R-style list of
 arguments followed by their declarations is also provided, inside
 comments, after the declaration.
 
-@item -fallow-parameterless-variadic-functions
-@opindex fallow-parameterless-variadic-functions
-Accept variadic functions without named parameters.
-
-Although it is possible to define such a function, this is not very
-useful as it is not possible to read the arguments.  This is only
-supported for C as this construct is allowed by C++.
-
 @item -fno-asm
 @opindex fno-asm
 @opindex fasm
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 110f8dfa0a9..63c8a3177ee 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -5378,7 +5378,9 @@ pass all their arguments on the stack.
 The argument @var{args_so_far} points to the @code{CUMULATIVE_ARGS} data
 structure, containing the values that are obtained after processing the
 named arguments.  The argument @var{arg} describes the last of these named
-arguments.
+arguments.  The argument @var{arg} should not be used if the function type
+satisfies @code{TYPE_NO_NAMED_ARGS_STDARG_P}, since in that case there are
+no named arguments and all arguments are accessed with @code{va_arg}.
 
 The target hook should do two things: first, push onto the stack all the
 argument registers @emph{not} used for the named arguments, and second,
diff --git a/gcc/function.cc b/gcc/function.cc
index 6474a663b30..d3da20ede7f 100644
--- a/gcc/function.cc
+++ b/gcc/function.cc
@@ -3647,6 +3647,12 @@ assign_parms (tree fndecl)
   assign_parms_initialize_all (&all);
   fnargs = assign_parms_augmented_arg_list (&all);
 
+  if (TYPE_NO_NAMED_ARGS_STDARG_P (TREE_TYPE (fndecl)))
+    {
+      struct assign_parm_data_one data = {};
+      assign_parms_setup_varargs (&all, &data, false);
+    }
+
   FOR_EACH_VEC_ELT (fnargs, i, parm)
     {
       struct assign_parm_data_one data;
diff --git a/gcc/ginclude/stdarg.h b/gcc/ginclude/stdarg.h
index 7545ed30424..c704c9ffcf2 100644
--- a/gcc/ginclude/stdarg.h
+++ b/gcc/ginclude/stdarg.h
@@ -44,7 +44,11 @@ typedef __builtin_va_list __gnuc_va_list;
    if this invocation was from the user program.  */
 #ifdef _STDARG_H
 
+#if defined __STDC_VERSION__ && __STDC_VERSION__ > 201710L
+#define va_start(v, ...)	__builtin_va_start(v, 0)
+#else
 #define va_start(v,l)	__builtin_va_start(v,l)
+#endif
 #define va_end(v)	__builtin_va_end(v)
 #define va_arg(v,l)	__builtin_va_arg(v,l)
 #if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L \
diff --git a/gcc/objc/objc-next-runtime-abi-01.cc b/gcc/objc/objc-next-runtime-abi-01.cc
index 409b777b9e5..8d41886902b 100644
--- a/gcc/objc/objc-next-runtime-abi-01.cc
+++ b/gcc/objc/objc-next-runtime-abi-01.cc
@@ -2443,7 +2443,7 @@ build_next_objc_exception_stuff (void)
   /* int _setjmp(...); */
   /* If the user includes <setjmp.h>, this shall be superseded by
      'int _setjmp(jmp_buf);' */
-  temp_type = build_varargs_function_type_list (integer_type_node, NULL_TREE);
+  temp_type = build_function_type (integer_type_node, NULL_TREE);
   objc_setjmp_decl
     = add_builtin_function (TAG_SETJMP, temp_type, 0, NOT_BUILT_IN, NULL, NULL_TREE);
 
diff --git a/gcc/target.def b/gcc/target.def
index a3d3b04a165..25f94c19fa7 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -4680,7 +4680,9 @@ pass all their arguments on the stack.\n\
 The argument @var{args_so_far} points to the @code{CUMULATIVE_ARGS} data\n\
 structure, containing the values that are obtained after processing the\n\
 named arguments.  The argument @var{arg} describes the last of these named\n\
-arguments.\n\
+arguments.  The argument @var{arg} should not be used if the function type\n\
+satisfies @code{TYPE_NO_NAMED_ARGS_STDARG_P}, since in that case there are\n\
+no named arguments and all arguments are accessed with @code{va_arg}.\n\
 \n\
 The target hook should do two things: first, push onto the stack all the\n\
 argument registers @emph{not} used for the named arguments, and second,\n\
diff --git a/gcc/testsuite/gcc.dg/Wold-style-definition-2.c b/gcc/testsuite/gcc.dg/Wold-style-definition-2.c
index a69aae6fd27..8e297c96411 100644
--- a/gcc/testsuite/gcc.dg/Wold-style-definition-2.c
+++ b/gcc/testsuite/gcc.dg/Wold-style-definition-2.c
@@ -5,6 +5,6 @@
 /* { dg-do compile } */
 /* { dg-options "-Wold-style-definition" } */
 
-void bar1 ( ... ) {} /* { dg-error "ISO C requires a named argument" } */
+void bar1 ( ... ) {}
 
 void bar2 (int a, ... ) {}
diff --git a/gcc/testsuite/gcc.dg/c11-stdarg-1.c b/gcc/testsuite/gcc.dg/c11-stdarg-1.c
new file mode 100644
index 00000000000..984577fe656
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-stdarg-1.c
@@ -0,0 +1,7 @@
+/* Test variadic functions with no named parameters not supported in C11.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+int f (...); /* { dg-error "ISO C requires a named argument before" } */
+int g (int (...)); /* { dg-error "ISO C requires a named argument before" } */
+int h (...) { return 0; } /* { dg-error "ISO C requires a named argument before" } */
diff --git a/gcc/testsuite/gcc.dg/c11-stdarg-2.c b/gcc/testsuite/gcc.dg/c11-stdarg-2.c
new file mode 100644
index 00000000000..bd115e8850c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-stdarg-2.c
@@ -0,0 +1,7 @@
+/* Test variadic functions with no named parameters not supported in C11.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c11 -pedantic" } */
+
+int f (...); /* { dg-warning "ISO C requires a named argument before" } */
+int g (int (...)); /* { dg-warning "ISO C requires a named argument before" } */
+int h (...) { return 0; } /* { dg-warning "ISO C requires a named argument before" } */
diff --git a/gcc/testsuite/gcc.dg/c11-stdarg-3.c b/gcc/testsuite/gcc.dg/c11-stdarg-3.c
new file mode 100644
index 00000000000..009292461bd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-stdarg-3.c
@@ -0,0 +1,8 @@
+/* Test variadic functions with no named parameters not supported in C11, but
+   diagnostic disabled with -Wno-c11-c2x-compat.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c11 -pedantic-errors -Wno-c11-c2x-compat" } */
+
+int f (...);
+int g (int (...));
+int h (...) { return 0; }
diff --git a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
index 9501b514f1c..9f2cb6c8256 100644
--- a/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
+++ b/gcc/testsuite/gcc.dg/c2x-nullptr-1.c
@@ -226,6 +226,7 @@ test4 (void)
 static void
 test5 (int i, ...)
 {
+  (void) i;
   va_list ap;
   va_start (ap, i);
   if (va_arg (ap, void *))
diff --git a/gcc/testsuite/gcc.dg/c2x-stdarg-1.c b/gcc/testsuite/gcc.dg/c2x-stdarg-1.c
new file mode 100644
index 00000000000..7def49d3ce2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-stdarg-1.c
@@ -0,0 +1,22 @@
+/* Test C2x variadic functions with no named parameters.  Compilation tests,
+   valid code.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+int f (...);
+int g (int (...));
+int h (...) { return 0; }
+
+typedef int A[];
+typedef int A2[2];
+
+A *f1 (...);
+A2 *f1 (...);
+A *f1 (...) { return 0; }
+
+A2 *f2 (...);
+A *f2 (...);
+A2 *f2 (...) { return 0; }
+typeof (f1) f2;
+
+int t () { return f () + f (1) + f (1, 2) + h () + h (1.5, 2, f1) + g (f); }
diff --git a/gcc/testsuite/gcc.dg/c2x-stdarg-2.c b/gcc/testsuite/gcc.dg/c2x-stdarg-2.c
new file mode 100644
index 00000000000..27782401c93
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-stdarg-2.c
@@ -0,0 +1,22 @@
+/* Test C2x variadic functions with no named parameters.  Compilation tests,
+   valid code, verify not considered unprototyped functions.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors -Wstrict-prototypes -Wold-style-definition" } */
+
+int f (...);
+int g (int (...));
+int h (...) { return 0; }
+
+typedef int A[];
+typedef int A2[2];
+
+A *f1 (...);
+A2 *f1 (...);
+A *f1 (...) { return 0; }
+
+A2 *f2 (...);
+A *f2 (...);
+A2 *f2 (...) { return 0; }
+typeof (f1) f2;
+
+int t () { return f () + f (1) + f (1, 2) + h () + h (1.5, 2, f1) + g (f); }
diff --git a/gcc/testsuite/gcc.dg/c2x-stdarg-3.c b/gcc/testsuite/gcc.dg/c2x-stdarg-3.c
new file mode 100644
index 00000000000..e2e14063e91
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-stdarg-3.c
@@ -0,0 +1,16 @@
+/* Test C2x variadic functions with no named parameters.  Compilation tests,
+   invalid code.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+int f (...); /* { dg-message "previous declaration" } */
+int f (); /* { dg-error "conflicting types" } */
+
+int f2 (...); /* { dg-message "previous declaration" } */
+int f2 (int); /* { dg-error "conflicting types" } */
+
+int g (); /* { dg-message "previous declaration" } */
+int g (...); /* { dg-error "conflicting types" } */
+
+int g2 (int); /* { dg-message "previous declaration" } */
+int g2 (...); /* { dg-error "conflicting types" } */
diff --git a/gcc/testsuite/gcc.dg/c2x-stdarg-4.c b/gcc/testsuite/gcc.dg/c2x-stdarg-4.c
new file mode 100644
index 00000000000..cb7a5cb9317
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-stdarg-4.c
@@ -0,0 +1,42 @@
+/* Test C2x variadic functions with no named parameters.  Execution tests.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#include <stdarg.h>
+
+extern void abort (void);
+extern void exit (int);
+
+double
+f (...)
+{
+  va_list ap;
+  va_start (ap);
+  double ret = va_arg (ap, int);
+  ret += va_arg (ap, double);
+  ret += va_arg (ap, int);
+  ret += va_arg (ap, double);
+  va_end (ap);
+  return ret;
+}
+
+void
+g (...)
+{
+  va_list ap;
+  va_start (ap, random ! ignored, ignored ** text);
+  for (int i = 0; i < 10; i++)
+    if (va_arg (ap, double) != i)
+      abort ();
+  va_end (ap);
+}
+
+int
+main ()
+{
+  if (f (1, 2.0, 3, 4.0) != 10.0)
+    abort ();
+  g (0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
+  g (0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f);
+  exit (0);
+}
diff --git a/gcc/testsuite/gcc.dg/diagnostic-token-ranges.c b/gcc/testsuite/gcc.dg/diagnostic-token-ranges.c
index 7d7ec0a9e0b..31085be1421 100644
--- a/gcc/testsuite/gcc.dg/diagnostic-token-ranges.c
+++ b/gcc/testsuite/gcc.dg/diagnostic-token-ranges.c
@@ -1,4 +1,4 @@
-/* { dg-options "-fdiagnostics-show-caret -Wc++-compat -std=c11" } */
+/* { dg-options "-fdiagnostics-show-caret -Wc++-compat -std=c11 -pedantic" } */
 
 /* Verify that various diagnostics show source code ranges.  */
 
@@ -48,7 +48,7 @@ void test_identifier_conflicts_with_cplusplus (void)
 }
 
 extern void
-bogus_varargs (...); /* { dg-error "ISO C requires a named argument before '...'" } */
+bogus_varargs (...); /* { dg-warning "ISO C requires a named argument before '...'" } */
 /*
 { dg-begin-multiline-output "" }
  bogus_varargs (...);
diff --git a/gcc/testsuite/gcc.dg/format/sentinel-1.c b/gcc/testsuite/gcc.dg/format/sentinel-1.c
index 0c8a2ac7737..16c75a8a961 100644
--- a/gcc/testsuite/gcc.dg/format/sentinel-1.c
+++ b/gcc/testsuite/gcc.dg/format/sentinel-1.c
@@ -15,7 +15,7 @@ extern char *envp[];
 extern int a ATTR; /* { dg-warning "applies to function types" "sentinel" } */
 
 extern void foo1 (const char *, ...) ATTR; /* { dg-message "note: declared here" } */
-extern void foo2 (...) ATTR; /* { dg-error "ISO C requires|named arguments" "sentinel" } */
+extern void foo2 (...) ATTR;
 extern void foo3 () ATTR; /* { dg-warning "named arguments" "sentinel" } */
 extern void foo4 (const char *, int) ATTR; /* { dg-warning "variadic functions" "sentinel" } */
 extern void foo5 (const char *, ...) __attribute__ ((__sentinel__(1)));
diff --git a/gcc/testsuite/gcc.dg/gnu2x-stdarg-1.c b/gcc/testsuite/gcc.dg/gnu2x-stdarg-1.c
new file mode 100644
index 00000000000..bb64cdeb48a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu2x-stdarg-1.c
@@ -0,0 +1,8 @@
+/* Test variadic functions with no named parameters do not accept GNU
+   attributes before '...'.  */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu2x" } */
+
+int f (__attribute__(()) ...); /* { dg-error "expected" } */
+int g (int (__attribute__(()) ...)); /* { dg-error "expected" } */
+int h (__attribute__(()) ...) { return 0; } /* { dg-error "expected" } */
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 80b886cc3e4..af75522504f 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1717,7 +1717,8 @@ struct GTY(()) tree_type_common {
   unsigned typeless_storage : 1;
   unsigned empty_flag : 1;
   unsigned indivisible_p : 1;
-  unsigned spare : 16;
+  unsigned no_named_args_stdarg_p : 1;
+  unsigned spare : 15;
 
   alias_set_type alias_set;
   tree pointer_to;
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 81a6ceaf181..48ab60d78fe 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -6112,7 +6112,9 @@ type_cache_hasher::equal (type_hash *a, type_hash *b)
 				      TYPE_FIELDS (b->type))));
 
     case FUNCTION_TYPE:
-      if (TYPE_ARG_TYPES (a->type) == TYPE_ARG_TYPES (b->type)
+      if ((TYPE_ARG_TYPES (a->type) == TYPE_ARG_TYPES (b->type)
+	   && (TYPE_NO_NAMED_ARGS_STDARG_P (a->type)
+	       == TYPE_NO_NAMED_ARGS_STDARG_P (b->type)))
 	  || (TYPE_ARG_TYPES (a->type)
 	      && TREE_CODE (TYPE_ARG_TYPES (a->type)) == TREE_LIST
 	      && TYPE_ARG_TYPES (b->type)
@@ -7364,10 +7366,13 @@ maybe_canonicalize_argtypes (tree argtypes,
    given arguments of types ARG_TYPES.
    ARG_TYPES is a chain of TREE_LIST nodes whose TREE_VALUEs
    are data type nodes for the arguments of the function.
+   NO_NAMED_ARGS_STDARG_P is true if this is a prototyped
+   variable-arguments function with (...) prototype (no named arguments).
    If such a type has already been constructed, reuse it.  */
 
 tree
-build_function_type (tree value_type, tree arg_types)
+build_function_type (tree value_type, tree arg_types,
+		     bool no_named_args_stdarg_p)
 {
   tree t;
   inchash::hash hstate;
@@ -7386,6 +7391,11 @@ build_function_type (tree value_type, tree arg_types)
   t = make_node (FUNCTION_TYPE);
   TREE_TYPE (t) = value_type;
   TYPE_ARG_TYPES (t) = arg_types;
+  if (no_named_args_stdarg_p)
+    {
+      gcc_assert (arg_types == NULL_TREE);
+      TYPE_NO_NAMED_ARGS_STDARG_P (t) = 1;
+    }
 
   /* If we already have such a type, use the old one.  */
   hashval_t hash = type_hash_canon_hash (t);
@@ -7436,7 +7446,7 @@ build_function_type_list_1 (bool vaargs, tree return_type, va_list argp)
       args = nreverse (args);
       TREE_CHAIN (last) = void_list_node;
     }
-  args = build_function_type (return_type, args);
+  args = build_function_type (return_type, args, vaargs && args == NULL_TREE);
 
   return args;
 }
@@ -7491,7 +7501,7 @@ build_function_type_array_1 (bool vaargs, tree return_type, int n,
   for (i = n - 1; i >= 0; i--)
     t = tree_cons (NULL_TREE, arg_types[i], t);
 
-  return build_function_type (return_type, t);
+  return build_function_type (return_type, t, vaargs && n == 0);
 }
 
 /* Build a function type.  RETURN_TYPE is the type returned by the
@@ -9994,7 +10004,8 @@ reconstruct_complex_type (tree type, tree bottom)
   else if (TREE_CODE (type) == FUNCTION_TYPE)
     {
       inner = reconstruct_complex_type (TREE_TYPE (type), bottom);
-      outer = build_function_type (inner, TYPE_ARG_TYPES (type));
+      outer = build_function_type (inner, TYPE_ARG_TYPES (type),
+				   TYPE_NO_NAMED_ARGS_STDARG_P (type));
     }
   else if (TREE_CODE (type) == METHOD_TYPE)
     {
@@ -11612,6 +11623,9 @@ stdarg_p (const_tree fntype)
   if (!fntype)
     return false;
 
+  if (TYPE_NO_NAMED_ARGS_STDARG_P (fntype))
+    return true;
+
   FOREACH_FUNCTION_ARGS (fntype, t, args_iter)
     {
       n = t;
@@ -11629,6 +11643,9 @@ prototype_p (const_tree fntype)
 
   gcc_assert (fntype != NULL_TREE);
 
+  if (TYPE_NO_NAMED_ARGS_STDARG_P (fntype))
+    return true;
+
   t = TYPE_ARG_TYPES (fntype);
   return (t != NULL_TREE);
 }
@@ -13647,7 +13664,9 @@ gimple_canonical_types_compatible_p (const_tree t1, const_tree t2,
 						trust_type_canonical))
 	return false;
 
-      if (TYPE_ARG_TYPES (t1) == TYPE_ARG_TYPES (t2))
+      if (TYPE_ARG_TYPES (t1) == TYPE_ARG_TYPES (t2)
+	  && (TYPE_NO_NAMED_ARGS_STDARG_P (t1)
+	      == TYPE_NO_NAMED_ARGS_STDARG_P (t2)))
 	return true;
       else
 	{
diff --git a/gcc/tree.h b/gcc/tree.h
index 9af971cf401..885df4d0d66 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -772,6 +772,12 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
    normal GNU extensions for target-specific vector types.  */
 #define TYPE_INDIVISIBLE_P(NODE) (TYPE_CHECK (NODE)->type_common.indivisible_p)
 
+/* True if this is a stdarg function with no named arguments (C2x
+   (...) prototype, where arguments can be accessed with va_start and
+   va_arg), as opposed to an unprototyped function.  */
+#define TYPE_NO_NAMED_ARGS_STDARG_P(NODE) \
+  (TYPE_CHECK (NODE)->type_common.no_named_args_stdarg_p)
+
 /* In an IDENTIFIER_NODE, this means that assemble_name was called with
    this string as an argument.  */
 #define TREE_SYMBOL_REFERENCED(NODE) \
@@ -4727,7 +4733,7 @@ extern tree build_array_type_1 (tree, tree, bool, bool, bool);
 extern tree build_array_type (tree, tree, bool = false);
 extern tree build_nonshared_array_type (tree, tree);
 extern tree build_array_type_nelts (tree, poly_uint64);
-extern tree build_function_type (tree, tree);
+extern tree build_function_type (tree, tree, bool = false);
 extern tree build_function_type_list (tree, ...);
 extern tree build_varargs_function_type_list (tree, ...);
 extern tree build_function_type_array (tree, int, tree *);
