c: tree: target: C2x (...) function prototypes and va_start relaxation

Message ID alpine.DEB.2.22.394.2210212214320.150427@digraph.polyomino.org.uk
State New
Headers
Series c: tree: target: C2x (...) function prototypes and va_start relaxation |

Commit Message

Joseph Myers Oct. 21, 2022, 10:15 p.m. UTC
  C2x allows function prototypes to be given as (...), a prototype
meaning a variable-argument function with no named arguments.  To
allow such functions to access their arguments, requirements for
va_start calls are relaxed so it ignores all but its first argument
(i.e. subsequent arguments, if any, can be arbitrary pp-token
sequences).

Implement this feature accordingly.  The va_start relaxation in
<stdarg.h> is itself easy: __builtin_va_start already supports a
second argument of 0 instead of a parameter name, and calls get
converted internally to the form using 0 for that argument, so
<stdarg.h> just needs changing to use a variadic macro that passes 0
as the second argument of __builtin_va_start.  (This is done only in
C2x mode, on the expectation that users of older standard would expect
unsupported uses of va_start to be diagnosed.)

For the (...) functions, it's necessary to distinguish these from
unprototyped functions, whereas previously C++ (...) functions and
unprototyped functions both used NULL TYPE_ARG_TYPES.  A flag is added
to tree_type_common to mark the (...) functions; as discussed on gcc@,
doing things this way is likely to be safer for unchanged code in GCC
than adding a different form of representation in TYPE_ARG_TYPES, or
adding a flag that instead signals that the function is unprototyped.

There was previously an option
-fallow-parameterless-variadic-functions to enable support for (...)
prototypes.  The support was incomplete - it treated the functions as
unprototyped, and only parsed some declarations, not e.g.
"int g (int (...));".  This option is changed into a no-op ignored
option; (...) is always accepted syntactically, with a pedwarn_c11
call to given required diagnostics when appropriate.  The peculiarity
of a parameter list with __attribute__ followed by '...' being
accepted with that option is removed.

Interfaces in tree.cc that create function types are adjusted to set
this flag as appropriate.  It is of course possible that some existing
users of the functions to create variable-argument functions actually
wanted unprototyped functions in the no-named-argument case, rather
than functions with a (...) prototype; some such cases in c-common.cc
(for built-in functions and implicit function declarations) turn out
to need updating for that reason.

I didn't do anything to change how the C++ front end creates (...)
function types.  It's very likely there are unchanged places in the
compiler that in fact turn out to need changes to work properly with
(...) function prototypes.

Target setup_incoming_varargs hooks, where they used the information
passed about the last named argument, needed updating to avoid using
that information in the (...) case.  Note that apart from the x86
changes, I haven't done any testing of those target changes beyond
building cc1 to check for syntax errors.  It's possible further
target-specific fixes will be needed; target maintainers should watch
out for failures of c2x-stdarg-4.c, the execution test, which would
indicate that this feature is not working correctly.

Bootstrapped with no regressions for x86_64-pc-linux-gnu.  OK to commit?

gcc/
	* config/aarch64/aarch64.cc (aarch64_setup_incoming_varargs):
	Check TYPE_NO_NAMED_ARGS_STDARG_P.
	* config/alpha/alpha.cc (alpha_setup_incoming_varargs): Likewise.
	* config/arc/arc.cc (arc_setup_incoming_varargs): Likewise.
	* config/arm/arm.cc (arm_setup_incoming_varargs): Likewise.
	* config/csky/csky.cc (csky_setup_incoming_varargs): Likewise.
	* config/epiphany/epiphany.cc (epiphany_setup_incoming_varargs):
	Likewise.
	* config/fr30/fr30.cc (fr30_setup_incoming_varargs): Likewise.
	* config/frv/frv.cc (frv_setup_incoming_varargs): Likewise.
	* config/ft32/ft32.cc (ft32_setup_incoming_varargs): Likewise.
	* config/i386/i386.cc (ix86_setup_incoming_varargs): Likewise.
	* config/ia64/ia64.cc (ia64_setup_incoming_varargs): Likewise.
	* config/loongarch/loongarch.cc
	(loongarch_setup_incoming_varargs): Likewise.
	* config/m32r/m32r.cc (m32r_setup_incoming_varargs): Likewise.
	* config/mcore/mcore.cc (mcore_setup_incoming_varargs): Likewise.
	* config/mips/mips.cc (mips_setup_incoming_varargs): Likewise.
	* config/mmix/mmix.cc (mmix_setup_incoming_varargs): Likewise.
	* config/nds32/nds32.cc (nds32_setup_incoming_varargs): Likewise.
	* config/nios2/nios2.cc (nios2_setup_incoming_varargs): Likewise.
	* config/riscv/riscv.cc (riscv_setup_incoming_varargs): Likewise.
	* config/rs6000/rs6000-call.cc (setup_incoming_varargs): Likewise.
	* config/sh/sh.cc (sh_setup_incoming_varargs): Likewise.
	* config/visium/visium.cc (visium_setup_incoming_varargs):
	Likewise.
	* config/vms/vms-c.cc (vms_c_common_override_options): Do not set
	flag_allow_parameterless_variadic_functions.
	* doc/invoke.texi (-fallow-parameterless-variadic-functions): Do
	not document option.
	* function.cc (assign_parms): Call assign_parms_setup_varargs for
	TYPE_NO_NAMED_ARGS_STDARG_P case.
	* ginclude/stdarg.h [__STDC_VERSION__ > 201710L] (va_start): Make
	variadic macro.  Pass second argument of 0 to __builtin_va_start.
	* target.def (setup_incoming_varargs): Update documentation.
	* doc/tm.texi: Regenerate.
	* tree-core.h (struct tree_type_common): Add
	no_named_args_stdarg_p.
	* tree.cc (type_cache_hasher::equal): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.
	(build_function_type): Add argument no_named_args_stdarg_p.
	(build_function_type_list_1, build_function_type_array_1)
	(reconstruct_complex_type): Update calls to build_function_type.
	(stdarg_p, prototype_p): Return true for (...) functions.
	(gimple_canonical_types_compatible_p): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.
	* tree.h (TYPE_NO_NAMED_ARGS_STDARG_P): New.
	(build_function_type): Update prototype.

gcc/c-family/
	* c-common.cc (def_fn_type): Call build_function_type for
	zero-argument variable-argument function.
	(c_common_nodes_and_builtins): Build default_function_type with
	build_function_type.
	* c.opt (fallow-parameterless-variadic-functions): Mark as ignored
	option.

gcc/c/
	* c-decl.cc (grokdeclarator): Pass
	arg_info->no_named_args_stdarg_p to build_function_type.
	(grokparms): Check arg_info->no_named_args_stdarg_p before
	converting () to (void).
	(build_arg_info): Initialize no_named_args_stdarg_p.
	(get_parm_info): Set no_named_args_stdarg_p.
	(start_function): Pass TYPE_NO_NAMED_ARGS_STDARG_P to
	build_function_type.
	(store_parm_decls): Count (...) functions as prototyped.
	* c-parser.cc (c_parser_direct_declarator): Allow '...' after open
	parenthesis to start parameter list.
	(c_parser_parms_list_declarator): Always allow '...' with no
	arguments, call pedwarn_c11 and set no_named_args_stdarg_p.
	* c-tree.h (struct c_arg_info): Add field no_named_args_stdarg_p.
	* c-typeck.cc (composite_type): Handle
	TYPE_NO_NAMED_ARGS_STDARG_P.
	(function_types_compatible_p): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.

	gcc/objc/
	* objc-next-runtime-abi-01.cc (build_next_objc_exception_stuff):
	Use build_function_type to build type of objc_setjmp_decl.

gcc/testsuite/
	* gcc.dg/c11-stdarg-1.c, gcc.dg/c11-stdarg-2.c,
	gcc.dg/c11-stdarg-3.c, gcc.dg/c2x-stdarg-1.c,
	gcc.dg/c2x-stdarg-2.c, gcc.dg/c2x-stdarg-3.c,
	gcc.dg/c2x-stdarg-4.c, gcc.dg/gnu2x-stdarg-1.c: New tests.
	* gcc.dg/Wold-style-definition-2.c, gcc.dg/format/sentinel-1.c:
	Update expected diagnostics.
	* gcc.dg/c2x-nullptr-1.c (test5): Cast unused parameter to (void).
	* gcc.dg/diagnostic-token-ranges.c: Use -pedantic.  Expect warning
	in place of error.
  

Comments

Richard Biener Oct. 25, 2022, 11:18 a.m. UTC | #1
On Sat, Oct 22, 2022 at 1:03 AM Joseph Myers <joseph@codesourcery.com> wrote:
>
> C2x allows function prototypes to be given as (...), a prototype
> meaning a variable-argument function with no named arguments.  To
> allow such functions to access their arguments, requirements for
> va_start calls are relaxed so it ignores all but its first argument
> (i.e. subsequent arguments, if any, can be arbitrary pp-token
> sequences).
>
> Implement this feature accordingly.  The va_start relaxation in
> <stdarg.h> is itself easy: __builtin_va_start already supports a
> second argument of 0 instead of a parameter name, and calls get
> converted internally to the form using 0 for that argument, so
> <stdarg.h> just needs changing to use a variadic macro that passes 0
> as the second argument of __builtin_va_start.  (This is done only in
> C2x mode, on the expectation that users of older standard would expect
> unsupported uses of va_start to be diagnosed.)
>
> For the (...) functions, it's necessary to distinguish these from
> unprototyped functions, whereas previously C++ (...) functions and
> unprototyped functions both used NULL TYPE_ARG_TYPES.  A flag is added
> to tree_type_common to mark the (...) functions; as discussed on gcc@,
> doing things this way is likely to be safer for unchanged code in GCC
> than adding a different form of representation in TYPE_ARG_TYPES, or
> adding a flag that instead signals that the function is unprototyped.
>
> There was previously an option
> -fallow-parameterless-variadic-functions to enable support for (...)
> prototypes.  The support was incomplete - it treated the functions as
> unprototyped, and only parsed some declarations, not e.g.
> "int g (int (...));".  This option is changed into a no-op ignored
> option; (...) is always accepted syntactically, with a pedwarn_c11
> call to given required diagnostics when appropriate.  The peculiarity
> of a parameter list with __attribute__ followed by '...' being
> accepted with that option is removed.
>
> Interfaces in tree.cc that create function types are adjusted to set
> this flag as appropriate.  It is of course possible that some existing
> users of the functions to create variable-argument functions actually
> wanted unprototyped functions in the no-named-argument case, rather
> than functions with a (...) prototype; some such cases in c-common.cc
> (for built-in functions and implicit function declarations) turn out
> to need updating for that reason.
>
> I didn't do anything to change how the C++ front end creates (...)
> function types.  It's very likely there are unchanged places in the
> compiler that in fact turn out to need changes to work properly with
> (...) function prototypes.
>
> Target setup_incoming_varargs hooks, where they used the information
> passed about the last named argument, needed updating to avoid using
> that information in the (...) case.  Note that apart from the x86
> changes, I haven't done any testing of those target changes beyond
> building cc1 to check for syntax errors.  It's possible further
> target-specific fixes will be needed; target maintainers should watch
> out for failures of c2x-stdarg-4.c, the execution test, which would
> indicate that this feature is not working correctly.
>
> Bootstrapped with no regressions for x86_64-pc-linux-gnu.  OK to commit?

You are missing to stream the new type flag in tree-streamer-{in,out}.cc
and checking for tree merging in lto-common.cc:compare_tree_sccs_1

Otherwise looks reasonable.  Can you add a (multi TU) runtime testcase to the
torture exercising the feature so we can see any ABI issues?

Thanks,
Richard.

> gcc/
>         * config/aarch64/aarch64.cc (aarch64_setup_incoming_varargs):
>         Check TYPE_NO_NAMED_ARGS_STDARG_P.
>         * config/alpha/alpha.cc (alpha_setup_incoming_varargs): Likewise.
>         * config/arc/arc.cc (arc_setup_incoming_varargs): Likewise.
>         * config/arm/arm.cc (arm_setup_incoming_varargs): Likewise.
>         * config/csky/csky.cc (csky_setup_incoming_varargs): Likewise.
>         * config/epiphany/epiphany.cc (epiphany_setup_incoming_varargs):
>         Likewise.
>         * config/fr30/fr30.cc (fr30_setup_incoming_varargs): Likewise.
>         * config/frv/frv.cc (frv_setup_incoming_varargs): Likewise.
>         * config/ft32/ft32.cc (ft32_setup_incoming_varargs): Likewise.
>         * config/i386/i386.cc (ix86_setup_incoming_varargs): Likewise.
>         * config/ia64/ia64.cc (ia64_setup_incoming_varargs): Likewise.
>         * config/loongarch/loongarch.cc
>         (loongarch_setup_incoming_varargs): Likewise.
>         * config/m32r/m32r.cc (m32r_setup_incoming_varargs): Likewise.
>         * config/mcore/mcore.cc (mcore_setup_incoming_varargs): Likewise.
>         * config/mips/mips.cc (mips_setup_incoming_varargs): Likewise.
>         * config/mmix/mmix.cc (mmix_setup_incoming_varargs): Likewise.
>         * config/nds32/nds32.cc (nds32_setup_incoming_varargs): Likewise.
>         * config/nios2/nios2.cc (nios2_setup_incoming_varargs): Likewise.
>         * config/riscv/riscv.cc (riscv_setup_incoming_varargs): Likewise.
>         * config/rs6000/rs6000-call.cc (setup_incoming_varargs): Likewise.
>         * config/sh/sh.cc (sh_setup_incoming_varargs): Likewise.
>         * config/visium/visium.cc (visium_setup_incoming_varargs):
>         Likewise.
>         * config/vms/vms-c.cc (vms_c_common_override_options): Do not set
>         flag_allow_parameterless_variadic_functions.
>         * doc/invoke.texi (-fallow-parameterless-variadic-functions): Do
>         not document option.
>         * function.cc (assign_parms): Call assign_parms_setup_varargs for
>         TYPE_NO_NAMED_ARGS_STDARG_P case.
>         * ginclude/stdarg.h [__STDC_VERSION__ > 201710L] (va_start): Make
>         variadic macro.  Pass second argument of 0 to __builtin_va_start.
>         * target.def (setup_incoming_varargs): Update documentation.
>         * doc/tm.texi: Regenerate.
>         * tree-core.h (struct tree_type_common): Add
>         no_named_args_stdarg_p.
>         * tree.cc (type_cache_hasher::equal): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         (build_function_type): Add argument no_named_args_stdarg_p.
>         (build_function_type_list_1, build_function_type_array_1)
>         (reconstruct_complex_type): Update calls to build_function_type.
>         (stdarg_p, prototype_p): Return true for (...) functions.
>         (gimple_canonical_types_compatible_p): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         * tree.h (TYPE_NO_NAMED_ARGS_STDARG_P): New.
>         (build_function_type): Update prototype.
>
> gcc/c-family/
>         * c-common.cc (def_fn_type): Call build_function_type for
>         zero-argument variable-argument function.
>         (c_common_nodes_and_builtins): Build default_function_type with
>         build_function_type.
>         * c.opt (fallow-parameterless-variadic-functions): Mark as ignored
>         option.
>
> gcc/c/
>         * c-decl.cc (grokdeclarator): Pass
>         arg_info->no_named_args_stdarg_p to build_function_type.
>         (grokparms): Check arg_info->no_named_args_stdarg_p before
>         converting () to (void).
>         (build_arg_info): Initialize no_named_args_stdarg_p.
>         (get_parm_info): Set no_named_args_stdarg_p.
>         (start_function): Pass TYPE_NO_NAMED_ARGS_STDARG_P to
>         build_function_type.
>         (store_parm_decls): Count (...) functions as prototyped.
>         * c-parser.cc (c_parser_direct_declarator): Allow '...' after open
>         parenthesis to start parameter list.
>         (c_parser_parms_list_declarator): Always allow '...' with no
>         arguments, call pedwarn_c11 and set no_named_args_stdarg_p.
>         * c-tree.h (struct c_arg_info): Add field no_named_args_stdarg_p.
>         * c-typeck.cc (composite_type): Handle
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         (function_types_compatible_p): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>
>         gcc/objc/
>         * objc-next-runtime-abi-01.cc (build_next_objc_exception_stuff):
>         Use build_function_type to build type of objc_setjmp_decl.
>
> gcc/testsuite/
>         * gcc.dg/c11-stdarg-1.c, gcc.dg/c11-stdarg-2.c,
>         gcc.dg/c11-stdarg-3.c, gcc.dg/c2x-stdarg-1.c,
>         gcc.dg/c2x-stdarg-2.c, gcc.dg/c2x-stdarg-3.c,
>         gcc.dg/c2x-stdarg-4.c, gcc.dg/gnu2x-stdarg-1.c: New tests.
>         * gcc.dg/Wold-style-definition-2.c, gcc.dg/format/sentinel-1.c:
>         Update expected diagnostics.
>         * gcc.dg/c2x-nullptr-1.c (test5): Cast unused parameter to (void).
>         * gcc.dg/diagnostic-token-ranges.c: Use -pedantic.  Expect warning
>         in place of error.
>
> 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 *);
>
> --
> Joseph S. Myers
> joseph@codesourcery.com
  
Joseph Myers Oct. 25, 2022, 7:42 p.m. UTC | #2
On Tue, 25 Oct 2022, Richard Biener wrote:

> You are missing to stream the new type flag in tree-streamer-{in,out}.cc
> and checking for tree merging in lto-common.cc:compare_tree_sccs_1
> 
> Otherwise looks reasonable.  Can you add a (multi TU) runtime testcase to the
> torture exercising the feature so we can see any ABI issues?

I've made those changes.  Those in turn showed up the need for a
change in fortran/trans-types.cc (to avoid using
build_varargs_function_type_vec when what was wanted was an
unprototyped function type rather than a (...) prototype) to avoid a
failure of gfortran.dg/lto/pr40724.

I've added a two-file testcase to gcc.dg/torture/.  I've also made the
execution tests cover the case where there are named arguments but the
last named argument has a declaration that results in undefined
behavior in previous C standard versions, such as a type changed by
the default argument promotions.

Here is the revised patch version.

c: tree: target: C2x (...) function prototypes and va_start relaxation

C2x allows function prototypes to be given as (...), a prototype
meaning a variable-argument function with no named arguments.  To
allow such functions to access their arguments, requirements for
va_start calls are relaxed so it ignores all but its first argument
(i.e. subsequent arguments, if any, can be arbitrary pp-token
sequences).

Implement this feature accordingly.  The va_start relaxation in
<stdarg.h> is itself easy: __builtin_va_start already supports a
second argument of 0 instead of a parameter name, and calls get
converted internally to the form using 0 for that argument, so
<stdarg.h> just needs changing to use a variadic macro that passes 0
as the second argument of __builtin_va_start.  (This is done only in
C2x mode, on the expectation that users of older standard would expect
unsupported uses of va_start to be diagnosed.)

For the (...) functions, it's necessary to distinguish these from
unprototyped functions, whereas previously C++ (...) functions and
unprototyped functions both used NULL TYPE_ARG_TYPES.  A flag is added
to tree_type_common to mark the (...) functions; as discussed on gcc@,
doing things this way is likely to be safer for unchanged code in GCC
than adding a different form of representation in TYPE_ARG_TYPES, or
adding a flag that instead signals that the function is unprototyped.

There was previously an option
-fallow-parameterless-variadic-functions to enable support for (...)
prototypes.  The support was incomplete - it treated the functions as
unprototyped, and only parsed some declarations, not e.g.
"int g (int (...));".  This option is changed into a no-op ignored
option; (...) is always accepted syntactically, with a pedwarn_c11
call to given required diagnostics when appropriate.  The peculiarity
of a parameter list with __attribute__ followed by '...' being
accepted with that option is removed.

Interfaces in tree.cc that create function types are adjusted to set
this flag as appropriate.  It is of course possible that some existing
users of the functions to create variable-argument functions actually
wanted unprototyped functions in the no-named-argument case, rather
than functions with a (...) prototype; some such cases in c-common.cc
(for built-in functions and implicit function declarations) turn out
to need updating for that reason.

I didn't do anything to change how the C++ front end creates (...)
function types.  It's very likely there are unchanged places in the
compiler that in fact turn out to need changes to work properly with
(...) function prototypes.

Target setup_incoming_varargs hooks, where they used the information
passed about the last named argument, needed updating to avoid using
that information in the (...) case.  Note that apart from the x86
changes, I haven't done any testing of those target changes beyond
building cc1 to check for syntax errors.  It's possible further
target-specific fixes will be needed; target maintainers should watch
out for failures of c2x-stdarg-4.c or c2x-stdarg-split-1a.c, the
execution tests, which would indicate that this feature is not working
correctly.  Those tests also verify the case where there are named
arguments but the last named argument has a declaration that results
in undefined behavior in previous C standard versions, such as a type
changed by the default argument promotions.

Bootstrapped with no regressions for x86_64-pc-linux-gnu.

gcc/
	* config/aarch64/aarch64.cc (aarch64_setup_incoming_varargs):
	Check TYPE_NO_NAMED_ARGS_STDARG_P.
	* config/alpha/alpha.cc (alpha_setup_incoming_varargs): Likewise.
	* config/arc/arc.cc (arc_setup_incoming_varargs): Likewise.
	* config/arm/arm.cc (arm_setup_incoming_varargs): Likewise.
	* config/csky/csky.cc (csky_setup_incoming_varargs): Likewise.
	* config/epiphany/epiphany.cc (epiphany_setup_incoming_varargs):
	Likewise.
	* config/fr30/fr30.cc (fr30_setup_incoming_varargs): Likewise.
	* config/frv/frv.cc (frv_setup_incoming_varargs): Likewise.
	* config/ft32/ft32.cc (ft32_setup_incoming_varargs): Likewise.
	* config/i386/i386.cc (ix86_setup_incoming_varargs): Likewise.
	* config/ia64/ia64.cc (ia64_setup_incoming_varargs): Likewise.
	* config/loongarch/loongarch.cc
	(loongarch_setup_incoming_varargs): Likewise.
	* config/m32r/m32r.cc (m32r_setup_incoming_varargs): Likewise.
	* config/mcore/mcore.cc (mcore_setup_incoming_varargs): Likewise.
	* config/mips/mips.cc (mips_setup_incoming_varargs): Likewise.
	* config/mmix/mmix.cc (mmix_setup_incoming_varargs): Likewise.
	* config/nds32/nds32.cc (nds32_setup_incoming_varargs): Likewise.
	* config/nios2/nios2.cc (nios2_setup_incoming_varargs): Likewise.
	* config/riscv/riscv.cc (riscv_setup_incoming_varargs): Likewise.
	* config/rs6000/rs6000-call.cc (setup_incoming_varargs): Likewise.
	* config/sh/sh.cc (sh_setup_incoming_varargs): Likewise.
	* config/visium/visium.cc (visium_setup_incoming_varargs):
	Likewise.
	* config/vms/vms-c.cc (vms_c_common_override_options): Do not set
	flag_allow_parameterless_variadic_functions.
	* doc/invoke.texi (-fallow-parameterless-variadic-functions): Do
	not document option.
	* function.cc (assign_parms): Call assign_parms_setup_varargs for
	TYPE_NO_NAMED_ARGS_STDARG_P case.
	* ginclude/stdarg.h [__STDC_VERSION__ > 201710L] (va_start): Make
	variadic macro.  Pass second argument of 0 to __builtin_va_start.
	* target.def (setup_incoming_varargs): Update documentation.
	* doc/tm.texi: Regenerate.
	* tree-core.h (struct tree_type_common): Add
	no_named_args_stdarg_p.
	* tree-streamer-in.cc (unpack_ts_type_common_value_fields): Unpack
	TYPE_NO_NAMED_ARGS_STDARG_P.
	* tree-streamer-out.cc (pack_ts_type_common_value_fields): Pack
	TYPE_NO_NAMED_ARGS_STDARG_P.
	* tree.cc (type_cache_hasher::equal): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.
	(build_function_type): Add argument no_named_args_stdarg_p.
	(build_function_type_list_1, build_function_type_array_1)
	(reconstruct_complex_type): Update calls to build_function_type.
	(stdarg_p, prototype_p): Return true for (...) functions.
	(gimple_canonical_types_compatible_p): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.
	* tree.h (TYPE_NO_NAMED_ARGS_STDARG_P): New.
	(build_function_type): Update prototype.

gcc/c-family/
	* c-common.cc (def_fn_type): Call build_function_type for
	zero-argument variable-argument function.
	(c_common_nodes_and_builtins): Build default_function_type with
	build_function_type.
	* c.opt (fallow-parameterless-variadic-functions): Mark as ignored
	option.

gcc/c/
	* c-decl.cc (grokdeclarator): Pass
	arg_info->no_named_args_stdarg_p to build_function_type.
	(grokparms): Check arg_info->no_named_args_stdarg_p before
	converting () to (void).
	(build_arg_info): Initialize no_named_args_stdarg_p.
	(get_parm_info): Set no_named_args_stdarg_p.
	(start_function): Pass TYPE_NO_NAMED_ARGS_STDARG_P to
	build_function_type.
	(store_parm_decls): Count (...) functions as prototyped.
	* c-parser.cc (c_parser_direct_declarator): Allow '...' after open
	parenthesis to start parameter list.
	(c_parser_parms_list_declarator): Always allow '...' with no
	arguments, call pedwarn_c11 and set no_named_args_stdarg_p.
	* c-tree.h (struct c_arg_info): Add field no_named_args_stdarg_p.
	* c-typeck.cc (composite_type): Handle
	TYPE_NO_NAMED_ARGS_STDARG_P.
	(function_types_compatible_p): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.

gcc/fortran/
	* trans-types.cc (gfc_get_function_type): Do not use
	build_varargs_function_type_vec for unprototyped function.

gcc/lto/
	* lto-common.cc (compare_tree_sccs_1): Compare
	TYPE_NO_NAMED_ARGS_STDARG_P.

gcc/objc/
	* objc-next-runtime-abi-01.cc (build_next_objc_exception_stuff):
	Use build_function_type to build type of objc_setjmp_decl.

gcc/testsuite/
	* gcc.dg/c11-stdarg-1.c, gcc.dg/c11-stdarg-2.c,
	gcc.dg/c11-stdarg-3.c, gcc.dg/c2x-stdarg-1.c,
	gcc.dg/c2x-stdarg-2.c, gcc.dg/c2x-stdarg-3.c,
	gcc.dg/c2x-stdarg-4.c, gcc.dg/gnu2x-stdarg-1.c,
	gcc.dg/torture/c2x-stdarg-split-1a.c,
	gcc.dg/torture/c2x-stdarg-split-1b.c: New tests.
	* gcc.dg/Wold-style-definition-2.c, gcc.dg/format/sentinel-1.c:
	Update expected diagnostics.
	* gcc.dg/c2x-nullptr-1.c (test5): Cast unused parameter to (void).
	* gcc.dg/diagnostic-token-ranges.c: Use -pedantic.  Expect warning
	in place of error.

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 4746e310d2d..6a4d6bc4313 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 92f3afc5857..64244c3d80d 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 5d1ab5aa42b..4d51d800e59 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -19891,7 +19891,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 aeea26ef4be..e775ba4ad6c 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 699ea6cc128..0ac0248fb9e 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 4e18a43539a..748a472cb2d 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -3737,7 +3737,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 64f77e8367a..eddc62c2008 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/fortran/trans-types.cc b/gcc/fortran/trans-types.cc
index fdce56defec..def7552ac67 100644
--- a/gcc/fortran/trans-types.cc
+++ b/gcc/fortran/trans-types.cc
@@ -3297,7 +3297,9 @@ arg_type_list_done:
     type = gfc_sym_type (sym);
 
   if (is_varargs)
-    type = build_varargs_function_type_vec (type, typelist);
+    /* This should be represented as an unprototyped type, not a type
+       with (...) prototype.  */
+    type = build_function_type (type, NULL_TREE);
   else
     type = build_function_type_vec (type, typelist);
 
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/lto/lto-common.cc b/gcc/lto/lto-common.cc
index d8d0404c54e..f64309731fa 100644
--- a/gcc/lto/lto-common.cc
+++ b/gcc/lto/lto-common.cc
@@ -1270,6 +1270,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
       if (AGGREGATE_TYPE_P (t1))
 	compare_values (TYPE_TYPELESS_STORAGE);
       compare_values (TYPE_EMPTY_P);
+      compare_values (TYPE_NO_NAMED_ARGS_STDARG_P);
       compare_values (TYPE_PACKED);
       compare_values (TYPE_RESTRICT);
       compare_values (TYPE_USER_ALIGN);
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..1f8718dec68
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c2x-stdarg-4.c
@@ -0,0 +1,164 @@
+/* Test C2x variadic functions with no named parameters, or last named
+   parameter with a declaration not allowed in C17.  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);
+}
+
+void
+h1 (register int x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h2 (int x(), ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h3 (int x[10], ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h4 (char x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h5 (float x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h6 (volatile long x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+struct s { char c[1000]; };
+
+void
+h7 (volatile struct s x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != 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);
+  h1 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h2 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h3 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h4 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h5 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h6 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h7 ((struct s) {}, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  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/testsuite/gcc.dg/torture/c2x-stdarg-split-1a.c b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1a.c
new file mode 100644
index 00000000000..f527b823287
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1a.c
@@ -0,0 +1,37 @@
+/* Test C2x variadic functions with no named parameters, or last named
+   parameter with a declaration not allowed in C17.  Execution tests split
+   between source files.  */
+/* { dg-do run } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+/* { dg-additional-sources "c2x-stdarg-split-1b.c" } */
+
+extern void abort (void);
+extern void exit (int);
+
+double f (...);
+void g (...);
+void h1 (register int x, ...);
+void h2 (int x(), ...);
+void h3 (int x[10], ...);
+void h4 (char x, ...);
+void h5 (float x, ...);
+void h6 (volatile long x, ...);
+struct s { char c[1000]; };
+void h7 (volatile struct s x, ...);
+
+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);
+  h1 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h2 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h3 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h4 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h5 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h6 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  h7 ((struct s) {}, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
+  exit (0);
+}
diff --git a/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1b.c b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1b.c
new file mode 100644
index 00000000000..55a01b20beb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1b.c
@@ -0,0 +1,147 @@
+/* Test C2x variadic functions with no named parameters, or last named
+   parameter with a declaration not allowed in C17.  Execution tests split
+   between source files.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c2x -pedantic-errors" } */
+
+#include <stdarg.h>
+
+extern void abort (void);
+
+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);
+}
+
+void
+h1 (register int x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h2 (int x(), ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h3 (int x[10], ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h4 (char x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h5 (float x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+void
+h6 (volatile long x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
+
+struct s { char c[1000]; };
+
+void
+h7 (volatile struct s x, ...)
+{
+  va_list ap;
+  va_start (ap);
+  for (int i = 0; i < 10; i++)
+    {
+      if (va_arg (ap, double) != i)
+	abort ();
+      i++;
+      if (va_arg (ap, int) != i)
+	abort ();
+    }
+  va_end (ap);
+}
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-streamer-in.cc b/gcc/tree-streamer-in.cc
index 57923da3741..0fa1eba86ab 100644
--- a/gcc/tree-streamer-in.cc
+++ b/gcc/tree-streamer-in.cc
@@ -398,6 +398,7 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
   if (AGGREGATE_TYPE_P (expr))
     TYPE_TYPELESS_STORAGE (expr) = (unsigned) bp_unpack_value (bp, 1);
   TYPE_EMPTY_P (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_NO_NAMED_ARGS_STDARG_P (expr) = (unsigned) bp_unpack_value (bp, 1);
   TYPE_PRECISION (expr) = bp_unpack_var_len_unsigned (bp);
   SET_TYPE_ALIGN (expr, bp_unpack_var_len_unsigned (bp));
 #ifdef ACCEL_COMPILER
diff --git a/gcc/tree-streamer-out.cc b/gcc/tree-streamer-out.cc
index 68a2818a9f9..3bf95ffd66c 100644
--- a/gcc/tree-streamer-out.cc
+++ b/gcc/tree-streamer-out.cc
@@ -365,6 +365,7 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
   if (AGGREGATE_TYPE_P (expr))
     bp_pack_value (bp, TYPE_TYPELESS_STORAGE (expr), 1);
   bp_pack_value (bp, TYPE_EMPTY_P (expr), 1);
+  bp_pack_value (bp, TYPE_NO_NAMED_ARGS_STDARG_P (expr), 1);
   bp_pack_var_len_unsigned (bp, TYPE_PRECISION (expr));
   bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
 }
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 04603c8c902..172098787dd 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 a50f7b2be9d..d6a5fdf6d81 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) \
@@ -4734,7 +4740,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 *);
  
Richard Biener Oct. 28, 2022, 8:34 a.m. UTC | #3
On Tue, Oct 25, 2022 at 9:42 PM Joseph Myers <joseph@codesourcery.com> wrote:
>
> On Tue, 25 Oct 2022, Richard Biener wrote:
>
> > You are missing to stream the new type flag in tree-streamer-{in,out}.cc
> > and checking for tree merging in lto-common.cc:compare_tree_sccs_1
> >
> > Otherwise looks reasonable.  Can you add a (multi TU) runtime testcase to the
> > torture exercising the feature so we can see any ABI issues?
>
> I've made those changes.  Those in turn showed up the need for a
> change in fortran/trans-types.cc (to avoid using
> build_varargs_function_type_vec when what was wanted was an
> unprototyped function type rather than a (...) prototype) to avoid a
> failure of gfortran.dg/lto/pr40724.
>
> I've added a two-file testcase to gcc.dg/torture/.  I've also made the
> execution tests cover the case where there are named arguments but the
> last named argument has a declaration that results in undefined
> behavior in previous C standard versions, such as a type changed by
> the default argument promotions.
>
> Here is the revised patch version.

OK.

Thanks,
Richard.

> c: tree: target: C2x (...) function prototypes and va_start relaxation
>
> C2x allows function prototypes to be given as (...), a prototype
> meaning a variable-argument function with no named arguments.  To
> allow such functions to access their arguments, requirements for
> va_start calls are relaxed so it ignores all but its first argument
> (i.e. subsequent arguments, if any, can be arbitrary pp-token
> sequences).
>
> Implement this feature accordingly.  The va_start relaxation in
> <stdarg.h> is itself easy: __builtin_va_start already supports a
> second argument of 0 instead of a parameter name, and calls get
> converted internally to the form using 0 for that argument, so
> <stdarg.h> just needs changing to use a variadic macro that passes 0
> as the second argument of __builtin_va_start.  (This is done only in
> C2x mode, on the expectation that users of older standard would expect
> unsupported uses of va_start to be diagnosed.)
>
> For the (...) functions, it's necessary to distinguish these from
> unprototyped functions, whereas previously C++ (...) functions and
> unprototyped functions both used NULL TYPE_ARG_TYPES.  A flag is added
> to tree_type_common to mark the (...) functions; as discussed on gcc@,
> doing things this way is likely to be safer for unchanged code in GCC
> than adding a different form of representation in TYPE_ARG_TYPES, or
> adding a flag that instead signals that the function is unprototyped.
>
> There was previously an option
> -fallow-parameterless-variadic-functions to enable support for (...)
> prototypes.  The support was incomplete - it treated the functions as
> unprototyped, and only parsed some declarations, not e.g.
> "int g (int (...));".  This option is changed into a no-op ignored
> option; (...) is always accepted syntactically, with a pedwarn_c11
> call to given required diagnostics when appropriate.  The peculiarity
> of a parameter list with __attribute__ followed by '...' being
> accepted with that option is removed.
>
> Interfaces in tree.cc that create function types are adjusted to set
> this flag as appropriate.  It is of course possible that some existing
> users of the functions to create variable-argument functions actually
> wanted unprototyped functions in the no-named-argument case, rather
> than functions with a (...) prototype; some such cases in c-common.cc
> (for built-in functions and implicit function declarations) turn out
> to need updating for that reason.
>
> I didn't do anything to change how the C++ front end creates (...)
> function types.  It's very likely there are unchanged places in the
> compiler that in fact turn out to need changes to work properly with
> (...) function prototypes.
>
> Target setup_incoming_varargs hooks, where they used the information
> passed about the last named argument, needed updating to avoid using
> that information in the (...) case.  Note that apart from the x86
> changes, I haven't done any testing of those target changes beyond
> building cc1 to check for syntax errors.  It's possible further
> target-specific fixes will be needed; target maintainers should watch
> out for failures of c2x-stdarg-4.c or c2x-stdarg-split-1a.c, the
> execution tests, which would indicate that this feature is not working
> correctly.  Those tests also verify the case where there are named
> arguments but the last named argument has a declaration that results
> in undefined behavior in previous C standard versions, such as a type
> changed by the default argument promotions.
>
> Bootstrapped with no regressions for x86_64-pc-linux-gnu.
>
> gcc/
>         * config/aarch64/aarch64.cc (aarch64_setup_incoming_varargs):
>         Check TYPE_NO_NAMED_ARGS_STDARG_P.
>         * config/alpha/alpha.cc (alpha_setup_incoming_varargs): Likewise.
>         * config/arc/arc.cc (arc_setup_incoming_varargs): Likewise.
>         * config/arm/arm.cc (arm_setup_incoming_varargs): Likewise.
>         * config/csky/csky.cc (csky_setup_incoming_varargs): Likewise.
>         * config/epiphany/epiphany.cc (epiphany_setup_incoming_varargs):
>         Likewise.
>         * config/fr30/fr30.cc (fr30_setup_incoming_varargs): Likewise.
>         * config/frv/frv.cc (frv_setup_incoming_varargs): Likewise.
>         * config/ft32/ft32.cc (ft32_setup_incoming_varargs): Likewise.
>         * config/i386/i386.cc (ix86_setup_incoming_varargs): Likewise.
>         * config/ia64/ia64.cc (ia64_setup_incoming_varargs): Likewise.
>         * config/loongarch/loongarch.cc
>         (loongarch_setup_incoming_varargs): Likewise.
>         * config/m32r/m32r.cc (m32r_setup_incoming_varargs): Likewise.
>         * config/mcore/mcore.cc (mcore_setup_incoming_varargs): Likewise.
>         * config/mips/mips.cc (mips_setup_incoming_varargs): Likewise.
>         * config/mmix/mmix.cc (mmix_setup_incoming_varargs): Likewise.
>         * config/nds32/nds32.cc (nds32_setup_incoming_varargs): Likewise.
>         * config/nios2/nios2.cc (nios2_setup_incoming_varargs): Likewise.
>         * config/riscv/riscv.cc (riscv_setup_incoming_varargs): Likewise.
>         * config/rs6000/rs6000-call.cc (setup_incoming_varargs): Likewise.
>         * config/sh/sh.cc (sh_setup_incoming_varargs): Likewise.
>         * config/visium/visium.cc (visium_setup_incoming_varargs):
>         Likewise.
>         * config/vms/vms-c.cc (vms_c_common_override_options): Do not set
>         flag_allow_parameterless_variadic_functions.
>         * doc/invoke.texi (-fallow-parameterless-variadic-functions): Do
>         not document option.
>         * function.cc (assign_parms): Call assign_parms_setup_varargs for
>         TYPE_NO_NAMED_ARGS_STDARG_P case.
>         * ginclude/stdarg.h [__STDC_VERSION__ > 201710L] (va_start): Make
>         variadic macro.  Pass second argument of 0 to __builtin_va_start.
>         * target.def (setup_incoming_varargs): Update documentation.
>         * doc/tm.texi: Regenerate.
>         * tree-core.h (struct tree_type_common): Add
>         no_named_args_stdarg_p.
>         * tree-streamer-in.cc (unpack_ts_type_common_value_fields): Unpack
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         * tree-streamer-out.cc (pack_ts_type_common_value_fields): Pack
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         * tree.cc (type_cache_hasher::equal): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         (build_function_type): Add argument no_named_args_stdarg_p.
>         (build_function_type_list_1, build_function_type_array_1)
>         (reconstruct_complex_type): Update calls to build_function_type.
>         (stdarg_p, prototype_p): Return true for (...) functions.
>         (gimple_canonical_types_compatible_p): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         * tree.h (TYPE_NO_NAMED_ARGS_STDARG_P): New.
>         (build_function_type): Update prototype.
>
> gcc/c-family/
>         * c-common.cc (def_fn_type): Call build_function_type for
>         zero-argument variable-argument function.
>         (c_common_nodes_and_builtins): Build default_function_type with
>         build_function_type.
>         * c.opt (fallow-parameterless-variadic-functions): Mark as ignored
>         option.
>
> gcc/c/
>         * c-decl.cc (grokdeclarator): Pass
>         arg_info->no_named_args_stdarg_p to build_function_type.
>         (grokparms): Check arg_info->no_named_args_stdarg_p before
>         converting () to (void).
>         (build_arg_info): Initialize no_named_args_stdarg_p.
>         (get_parm_info): Set no_named_args_stdarg_p.
>         (start_function): Pass TYPE_NO_NAMED_ARGS_STDARG_P to
>         build_function_type.
>         (store_parm_decls): Count (...) functions as prototyped.
>         * c-parser.cc (c_parser_direct_declarator): Allow '...' after open
>         parenthesis to start parameter list.
>         (c_parser_parms_list_declarator): Always allow '...' with no
>         arguments, call pedwarn_c11 and set no_named_args_stdarg_p.
>         * c-tree.h (struct c_arg_info): Add field no_named_args_stdarg_p.
>         * c-typeck.cc (composite_type): Handle
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>         (function_types_compatible_p): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>
> gcc/fortran/
>         * trans-types.cc (gfc_get_function_type): Do not use
>         build_varargs_function_type_vec for unprototyped function.
>
> gcc/lto/
>         * lto-common.cc (compare_tree_sccs_1): Compare
>         TYPE_NO_NAMED_ARGS_STDARG_P.
>
> gcc/objc/
>         * objc-next-runtime-abi-01.cc (build_next_objc_exception_stuff):
>         Use build_function_type to build type of objc_setjmp_decl.
>
> gcc/testsuite/
>         * gcc.dg/c11-stdarg-1.c, gcc.dg/c11-stdarg-2.c,
>         gcc.dg/c11-stdarg-3.c, gcc.dg/c2x-stdarg-1.c,
>         gcc.dg/c2x-stdarg-2.c, gcc.dg/c2x-stdarg-3.c,
>         gcc.dg/c2x-stdarg-4.c, gcc.dg/gnu2x-stdarg-1.c,
>         gcc.dg/torture/c2x-stdarg-split-1a.c,
>         gcc.dg/torture/c2x-stdarg-split-1b.c: New tests.
>         * gcc.dg/Wold-style-definition-2.c, gcc.dg/format/sentinel-1.c:
>         Update expected diagnostics.
>         * gcc.dg/c2x-nullptr-1.c (test5): Cast unused parameter to (void).
>         * gcc.dg/diagnostic-token-ranges.c: Use -pedantic.  Expect warning
>         in place of error.
>
> 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 4746e310d2d..6a4d6bc4313 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 92f3afc5857..64244c3d80d 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 5d1ab5aa42b..4d51d800e59 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -19891,7 +19891,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 aeea26ef4be..e775ba4ad6c 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 699ea6cc128..0ac0248fb9e 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 4e18a43539a..748a472cb2d 100644
> --- a/gcc/config/riscv/riscv.cc
> +++ b/gcc/config/riscv/riscv.cc
> @@ -3737,7 +3737,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 64f77e8367a..eddc62c2008 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/fortran/trans-types.cc b/gcc/fortran/trans-types.cc
> index fdce56defec..def7552ac67 100644
> --- a/gcc/fortran/trans-types.cc
> +++ b/gcc/fortran/trans-types.cc
> @@ -3297,7 +3297,9 @@ arg_type_list_done:
>      type = gfc_sym_type (sym);
>
>    if (is_varargs)
> -    type = build_varargs_function_type_vec (type, typelist);
> +    /* This should be represented as an unprototyped type, not a type
> +       with (...) prototype.  */
> +    type = build_function_type (type, NULL_TREE);
>    else
>      type = build_function_type_vec (type, typelist);
>
> 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/lto/lto-common.cc b/gcc/lto/lto-common.cc
> index d8d0404c54e..f64309731fa 100644
> --- a/gcc/lto/lto-common.cc
> +++ b/gcc/lto/lto-common.cc
> @@ -1270,6 +1270,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>        if (AGGREGATE_TYPE_P (t1))
>         compare_values (TYPE_TYPELESS_STORAGE);
>        compare_values (TYPE_EMPTY_P);
> +      compare_values (TYPE_NO_NAMED_ARGS_STDARG_P);
>        compare_values (TYPE_PACKED);
>        compare_values (TYPE_RESTRICT);
>        compare_values (TYPE_USER_ALIGN);
> 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..1f8718dec68
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/c2x-stdarg-4.c
> @@ -0,0 +1,164 @@
> +/* Test C2x variadic functions with no named parameters, or last named
> +   parameter with a declaration not allowed in C17.  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);
> +}
> +
> +void
> +h1 (register int x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h2 (int x(), ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h3 (int x[10], ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h4 (char x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h5 (float x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h6 (volatile long x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +struct s { char c[1000]; };
> +
> +void
> +h7 (volatile struct s x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != 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);
> +  h1 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h2 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h3 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h4 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h5 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h6 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h7 ((struct s) {}, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  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/testsuite/gcc.dg/torture/c2x-stdarg-split-1a.c b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1a.c
> new file mode 100644
> index 00000000000..f527b823287
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1a.c
> @@ -0,0 +1,37 @@
> +/* Test C2x variadic functions with no named parameters, or last named
> +   parameter with a declaration not allowed in C17.  Execution tests split
> +   between source files.  */
> +/* { dg-do run } */
> +/* { dg-options "-std=c2x -pedantic-errors" } */
> +/* { dg-additional-sources "c2x-stdarg-split-1b.c" } */
> +
> +extern void abort (void);
> +extern void exit (int);
> +
> +double f (...);
> +void g (...);
> +void h1 (register int x, ...);
> +void h2 (int x(), ...);
> +void h3 (int x[10], ...);
> +void h4 (char x, ...);
> +void h5 (float x, ...);
> +void h6 (volatile long x, ...);
> +struct s { char c[1000]; };
> +void h7 (volatile struct s x, ...);
> +
> +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);
> +  h1 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h2 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h3 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h4 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h5 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h6 (0, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  h7 ((struct s) {}, 0.0, 1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9);
> +  exit (0);
> +}
> diff --git a/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1b.c b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1b.c
> new file mode 100644
> index 00000000000..55a01b20beb
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/c2x-stdarg-split-1b.c
> @@ -0,0 +1,147 @@
> +/* Test C2x variadic functions with no named parameters, or last named
> +   parameter with a declaration not allowed in C17.  Execution tests split
> +   between source files.  */
> +/* { dg-do compile } */
> +/* { dg-options "-std=c2x -pedantic-errors" } */
> +
> +#include <stdarg.h>
> +
> +extern void abort (void);
> +
> +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);
> +}
> +
> +void
> +h1 (register int x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h2 (int x(), ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h3 (int x[10], ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h4 (char x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h5 (float x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +void
> +h6 (volatile long x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> +
> +struct s { char c[1000]; };
> +
> +void
> +h7 (volatile struct s x, ...)
> +{
> +  va_list ap;
> +  va_start (ap);
> +  for (int i = 0; i < 10; i++)
> +    {
> +      if (va_arg (ap, double) != i)
> +       abort ();
> +      i++;
> +      if (va_arg (ap, int) != i)
> +       abort ();
> +    }
> +  va_end (ap);
> +}
> 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-streamer-in.cc b/gcc/tree-streamer-in.cc
> index 57923da3741..0fa1eba86ab 100644
> --- a/gcc/tree-streamer-in.cc
> +++ b/gcc/tree-streamer-in.cc
> @@ -398,6 +398,7 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>    if (AGGREGATE_TYPE_P (expr))
>      TYPE_TYPELESS_STORAGE (expr) = (unsigned) bp_unpack_value (bp, 1);
>    TYPE_EMPTY_P (expr) = (unsigned) bp_unpack_value (bp, 1);
> +  TYPE_NO_NAMED_ARGS_STDARG_P (expr) = (unsigned) bp_unpack_value (bp, 1);
>    TYPE_PRECISION (expr) = bp_unpack_var_len_unsigned (bp);
>    SET_TYPE_ALIGN (expr, bp_unpack_var_len_unsigned (bp));
>  #ifdef ACCEL_COMPILER
> diff --git a/gcc/tree-streamer-out.cc b/gcc/tree-streamer-out.cc
> index 68a2818a9f9..3bf95ffd66c 100644
> --- a/gcc/tree-streamer-out.cc
> +++ b/gcc/tree-streamer-out.cc
> @@ -365,6 +365,7 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>    if (AGGREGATE_TYPE_P (expr))
>      bp_pack_value (bp, TYPE_TYPELESS_STORAGE (expr), 1);
>    bp_pack_value (bp, TYPE_EMPTY_P (expr), 1);
> +  bp_pack_value (bp, TYPE_NO_NAMED_ARGS_STDARG_P (expr), 1);
>    bp_pack_var_len_unsigned (bp, TYPE_PRECISION (expr));
>    bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
>  }
> diff --git a/gcc/tree.cc b/gcc/tree.cc
> index 04603c8c902..172098787dd 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 a50f7b2be9d..d6a5fdf6d81 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) \
> @@ -4734,7 +4740,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 *);
>
> --
> Joseph S. Myers
> joseph@codesourcery.com
  
Xi Ruoyao Nov. 4, 2022, 5:29 a.m. UTC | #4
On Fri, 2022-10-21 at 22:15 +0000, Joseph Myers wrote:

>         * config/loongarch/loongarch.cc
>         (loongarch_setup_incoming_varargs): Likewise.

Hi,

One test fails on loongarch64-linux-gnu:

FAIL: gcc.dg/c2x-stdarg-4.c execution test

I've not got time to debug the issue yet.
  
Xi Ruoyao Nov. 4, 2022, 6:32 a.m. UTC | #5
On Fri, 2022-11-04 at 13:29 +0800, Xi Ruoyao via Gcc-patches wrote:
> On Fri, 2022-10-21 at 22:15 +0000, Joseph Myers wrote:
> 
> >         * config/loongarch/loongarch.cc
> >         (loongarch_setup_incoming_varargs): Likewise.
> 
> Hi,
> 
> One test fails on loongarch64-linux-gnu:
> 
> FAIL: gcc.dg/c2x-stdarg-4.c execution test

And also:

FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O0  execution test
FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O1  execution test
FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O2  execution test
FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O3 -g  execution test
FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -Os  execution test
FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O2 -flto -fno-use-linker-plugin -flto-partition=none  execution test
FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O2 -flto -fuse-linker-plugin -fno-fat-lto-objects  execution test

> 
> I've not got time to debug the issue yet.
>
  
Eric Botcazou Nov. 4, 2022, 8:19 a.m. UTC | #6
> And also:
> 
> FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O0  execution test
> FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O1  execution test
> FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O2  execution test
> FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O3 -g  execution test
> FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -Os  execution test
> FAIL: gcc.dg/torture/c2x-stdarg-split-1a.c   -O2 -flto
> -fno-use-linker-plugin -flto-partition=none  execution test FAIL:
> gcc.dg/torture/c2x-stdarg-split-1a.c   -O2 -flto -fuse-linker-plugin
> -fno-fat-lto-objects  execution test

The implementation does not work on some targets, see PR target/107453.
  
Gerald Pfeifer Nov. 25, 2023, 2:21 p.m. UTC | #7
On Fri, 21 Oct 2022, Joseph Myers wrote:
> C2x allows function prototypes to be given as (...), a prototype
> meaning a variable-argument function with no named arguments.

I noticed this did not make it into gcc-13/changes.html ? Was that 
intentional?

Gerald
  
Joseph Myers Nov. 28, 2023, 12:50 a.m. UTC | #8
On Sat, 25 Nov 2023, Gerald Pfeifer wrote:

> On Fri, 21 Oct 2022, Joseph Myers wrote:
> > C2x allows function prototypes to be given as (...), a prototype
> > meaning a variable-argument function with no named arguments.
> 
> I noticed this did not make it into gcc-13/changes.html ? Was that 
> intentional?

It's there (N2975, Relax requirements for variadic parameter lists).
  

Patch

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 *);