diff mbox series

Adjust empty class parameter passing ABI (PR c++/60336)

Message ID 20171027100056.GE12792@redhat.com
State New
Headers show
Series Adjust empty class parameter passing ABI (PR c++/60336) | expand

Commit Message

Marek Polacek Oct. 27, 2017, 10 a.m. UTC
This is my attempt at the empty class ABI change.  To recap quickly, the C++
compiler has used a different calling convention for passing empty classes,
because C++ says they have size 1, while the GCC C extension gives them size 0.
But this difference doesn't mean that they need to be passed differently; in
either case there's no actual data involved.

I've made use of all the previous patches:
<https://gcc.gnu.org/ml/gcc-patches/2015-11/msg02064.html>
<https://gcc.gnu.org/ml/gcc-patches/2016-03/msg00183.html>
<https://gcc.gnu.org/ml/gcc-patches/2016-04/msg00559.html>
but this approach uses two target hooks which check whether a type is an empty
type according to the x86_64 psABI, and the second implements the warning for it.
It also uses a lang hook to determine whether to print a -Wabi warning.  The
new passing can be turned back into the old passing using -fabi-version=11.  So
I had to use the new langhook, otherwise I wouldn't be able make it dependent
on the C++ ABI verison.

Some earlier comments from Jason:
> I'm still uneasy about how much this requires generic code to think
> about empty types specifically.  I'd prefer to encapsulate this as
> much as possible.  Rather than places saying (empty ? 0 :
> int_size_in_bytes), I figured that would all be hidden in the target
> code, along with the warning.  Places where you currently emit a
> warning from generic code ought to come from a target hook, either an
> existing one or a new one called something like
> warn_parameter_passing_abi.
 
I hope I've improved this now.  I've introduced two new wrappers,
maybe_empty_type_size, and int_maybe_empty_type_size.  I've moved the
warning to its own target hook with a new field in CUMULATIVE_ARGS; that
seems to work well.

> Note that nothing in gcc/ currently refers to warn_abi or warn_psabi,
> which are both c-common flags; some targets refer to warn_psabi.
 
True.

> > It also uses a lang hook to determine
> > whether to print a -Wabi warning.  The new passing can be turned back into
> > the old passing using -fabi-version=11.  So I had to use a new langhook,
> > otherwise I couldn't make it dependent on the C++ ABI verison.
> 
> Hmm, that's unfortunate.

It is, but the possibility of using -fabi-version=11 to revert to the old
behavior seems useful.

> > The users will get the new empty records passing even with -m32.
> 
> I thought we only wanted to make the change for -m64; -m32 is a legacy
> ABI at this point, it doesn't seem advisable to change it.

Sure thing, done (using the TARGET_64BIT check in ix86_is_empty_record_p).

> > One thing I wasn't sure about is what to do with a struct that contains
> > a zero-length array.  We should consider it an empty type, but my
> > is_empty_record_p won't do that, the reason is that there doesn't seem
> > to be any change regarding passing: older GCCs wouldn't pass such a struct
> > via stack.  So there's no warning for that, either.
> 
> Not warning for that makes sense.  Doing that by making
> is_empty_record_p give the wrong answer seems unwise.
 
is_empty_record_p should now give the correct answer.  As I said yesterday,
I think we shouldn't consider struct S { struct { } a; int b[0]; } as empty,
because [0] is a GNU extension and struct S { struct { } a; int b[]; } is
considered non-empty.  Also, for the record, the struct with the flexible array
member [0] broke struct-layout when considered empty.  Thus the uglyfying of
is_empty_record_p.  empty23.C and empty24.C test that.

> Also, you need to handled unnamed bit-fields, as they aren't data members.

Here's another hiccup: e.g. struct S { unsigned : 15; }; are considered empty,
but they were considered empty even before my change (so we probably shouldn't
warn), with "unsigned : 0;" it's different and there seems to be a change in
how we pass that.  So I kept the warning.  Not sure how important this is.

Any other concerns?

Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.

2017-10-27  Marek Polacek  <polacek@redhat.com>
	    H.J. Lu  <hongjiu.lu@intel.com>
	    Jason Merrill  <jason@redhat.com>

	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_function_arg_advance): Skip empty records.
	(ix86_return_in_memory): Return false for empty types.
	(ix86_gimplify_va_arg): Call int_maybe_empty_type_size instead of
	int_size_in_bytes.
	(ix86_is_empty_record_p): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* explow.c (hard_function_value): Call int_maybe_empty_type_size
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (assign_parm_find_entry_rtl): Call
	warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call maybe_empty_type_size instead
	size_in_bytes.
	* langhooks-def.h (LANG_HOOKS_WARN_EMPTY_CLASS_ABI): Define.
	* langhooks.h (struct lang_hooks): Add warn_empty_class_abi.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hook.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	maybe_empty_type_size instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree.c (array_type_nelts_top): New function.
	(is_empty_type): New function.
	(is_empty_record_p): New function.
	(int_maybe_empty_type_size): New function.
	(maybe_empty_type_size): New function.
	* tree.h (array_type_nelts_top, is_empty_record_p,
	int_maybe_empty_type_size, maybe_empty_type_size): Declare.

	* cp-lang.c: Include "flags.h".
	(LANG_HOOKS_WARN_EMPTY_CLASS_ABI): Redefine.
	(cxx_warn_empty_class_abi): New function.
	* cp-tree.h (array_type_nelts_top): Remove declaration.
	* tree.c (array_type_nelts_top): Move to tree.c.

	* testsuite/g++.dg/abi/empty12.C: New test.
	* testsuite/g++.dg/abi/empty12.h: New test.
	* testsuite/g++.dg/abi/empty12a.c: New test.
	* testsuite/g++.dg/abi/empty13.C: New test.
	* testsuite/g++.dg/abi/empty13.h: New test.
	* testsuite/g++.dg/abi/empty13a.c: New test.
	* testsuite/g++.dg/abi/empty14.C: New test.
	* testsuite/g++.dg/abi/empty14.h: New test.
	* testsuite/g++.dg/abi/empty14a.c: New test.
	* testsuite/g++.dg/abi/empty15.C: New test.
	* testsuite/g++.dg/abi/empty15.h: New test.
	* testsuite/g++.dg/abi/empty15a.c: New test.
	* testsuite/g++.dg/abi/empty16.C: New test.
	* testsuite/g++.dg/abi/empty16.h: New test.
	* testsuite/g++.dg/abi/empty16a.c: New test.
	* testsuite/g++.dg/abi/empty17.C: New test.
	* testsuite/g++.dg/abi/empty17.h: New test.
	* testsuite/g++.dg/abi/empty17a.c: New test.
	* testsuite/g++.dg/abi/empty18.C: New test.
	* testsuite/g++.dg/abi/empty18.h: New test.
	* testsuite/g++.dg/abi/empty18a.c: New test.
	* testsuite/g++.dg/abi/empty19.C: New test.
	* testsuite/g++.dg/abi/empty19.h: New test.
	* testsuite/g++.dg/abi/empty19a.c: New test.
	* testsuite/g++.dg/abi/empty20.C: New test.
	* testsuite/g++.dg/abi/empty21.C: New test.
	* testsuite/g++.dg/abi/empty22.C: New test.
	* testsuite/g++.dg/abi/empty22.h: New test.
	* testsuite/g++.dg/abi/empty22a.c: New test.
	* testsuite/g++.dg/abi/empty23.C: New test.
	* testsuite/g++.dg/abi/empty24.C: New test.
	* testsuite/g++.dg/abi/pr60336-1.C: New test.
	* testsuite/g++.dg/abi/pr60336-10.C: New test.
	* testsuite/g++.dg/abi/pr60336-11.C: New test.
	* testsuite/g++.dg/abi/pr60336-12.C: New test.
	* testsuite/g++.dg/abi/pr60336-2.C: New test.
	* testsuite/g++.dg/abi/pr60336-3.C: New test.
	* testsuite/g++.dg/abi/pr60336-4.C: New test.
	* testsuite/g++.dg/abi/pr60336-5.C: New test.
	* testsuite/g++.dg/abi/pr60336-6.C: New test.
	* testsuite/g++.dg/abi/pr60336-7.C: New test.
	* testsuite/g++.dg/abi/pr60336-8.C: New test.
	* testsuite/g++.dg/abi/pr60336-9.C: New test.
	* testsuite/g++.dg/abi/pr68355.C: New test.


	Marek

Comments

Richard Biener Oct. 27, 2017, 10:31 a.m. UTC | #1
On Fri, 27 Oct 2017, Marek Polacek wrote:

> This is my attempt at the empty class ABI change.  To recap quickly, the C++
> compiler has used a different calling convention for passing empty classes,
> because C++ says they have size 1, while the GCC C extension gives them size 0.
> But this difference doesn't mean that they need to be passed differently; in
> either case there's no actual data involved.
> 
> I've made use of all the previous patches:
> <https://gcc.gnu.org/ml/gcc-patches/2015-11/msg02064.html>
> <https://gcc.gnu.org/ml/gcc-patches/2016-03/msg00183.html>
> <https://gcc.gnu.org/ml/gcc-patches/2016-04/msg00559.html>
> but this approach uses two target hooks which check whether a type is an empty
> type according to the x86_64 psABI, and the second implements the warning for it.
> It also uses a lang hook to determine whether to print a -Wabi warning.  The
> new passing can be turned back into the old passing using -fabi-version=11.  So
> I had to use the new langhook, otherwise I wouldn't be able make it dependent
> on the C++ ABI verison.
> 
> Some earlier comments from Jason:
> > I'm still uneasy about how much this requires generic code to think
> > about empty types specifically.  I'd prefer to encapsulate this as
> > much as possible.  Rather than places saying (empty ? 0 :
> > int_size_in_bytes), I figured that would all be hidden in the target
> > code, along with the warning.  Places where you currently emit a
> > warning from generic code ought to come from a target hook, either an
> > existing one or a new one called something like
> > warn_parameter_passing_abi.
>  
> I hope I've improved this now.  I've introduced two new wrappers,
> maybe_empty_type_size, and int_maybe_empty_type_size.  I've moved the
> warning to its own target hook with a new field in CUMULATIVE_ARGS; that
> seems to work well.
> 
> > Note that nothing in gcc/ currently refers to warn_abi or warn_psabi,
> > which are both c-common flags; some targets refer to warn_psabi.
>  
> True.
> 
> > > It also uses a lang hook to determine
> > > whether to print a -Wabi warning.  The new passing can be turned back into
> > > the old passing using -fabi-version=11.  So I had to use a new langhook,
> > > otherwise I couldn't make it dependent on the C++ ABI verison.
> > 
> > Hmm, that's unfortunate.
> 
> It is, but the possibility of using -fabi-version=11 to revert to the old
> behavior seems useful.
> 
> > > The users will get the new empty records passing even with -m32.
> > 
> > I thought we only wanted to make the change for -m64; -m32 is a legacy
> > ABI at this point, it doesn't seem advisable to change it.
> 
> Sure thing, done (using the TARGET_64BIT check in ix86_is_empty_record_p).
> 
> > > One thing I wasn't sure about is what to do with a struct that contains
> > > a zero-length array.  We should consider it an empty type, but my
> > > is_empty_record_p won't do that, the reason is that there doesn't seem
> > > to be any change regarding passing: older GCCs wouldn't pass such a struct
> > > via stack.  So there's no warning for that, either.
> > 
> > Not warning for that makes sense.  Doing that by making
> > is_empty_record_p give the wrong answer seems unwise.
>  
> is_empty_record_p should now give the correct answer.  As I said yesterday,
> I think we shouldn't consider struct S { struct { } a; int b[0]; } as empty,
> because [0] is a GNU extension and struct S { struct { } a; int b[]; } is
> considered non-empty.  Also, for the record, the struct with the flexible array
> member [0] broke struct-layout when considered empty.  Thus the uglyfying of
> is_empty_record_p.  empty23.C and empty24.C test that.
> 
> > Also, you need to handled unnamed bit-fields, as they aren't data members.
> 
> Here's another hiccup: e.g. struct S { unsigned : 15; }; are considered empty,
> but they were considered empty even before my change (so we probably shouldn't
> warn), with "unsigned : 0;" it's different and there seems to be a change in
> how we pass that.  So I kept the warning.  Not sure how important this is.
> 
> Any other concerns?

I fear it doesn't work at all with LTO (you'll always get the old ABI
if I read the patch correctly).  This is because the function
computing the size looks at flag_abi_version which isn't saved
per function / TU.

Similarly you'll never get the ABI warning with LTO (less of a big
deal of course) because the langhook doesn't reflect things correctly
either.

So...  can we instead compute whether a type is "empty" according
to the ABI early and store the result in the type (thinking of
doing this in layout_type?).  Similarly set a flag whether to
warn.  Why do you warn from backends / code emission and not
from the FEs?  Is that to avoid warnings for calls that got inlined?
Maybe the FE could set a flag on the call itself (ok, somewhat
awkward to funnel through gimple).

Richard.

> Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
> 
> 2017-10-27  Marek Polacek  <polacek@redhat.com>
> 	    H.J. Lu  <hongjiu.lu@intel.com>
> 	    Jason Merrill  <jason@redhat.com>
> 
> 	PR c++/60336
> 	PR middle-end/67239
> 	PR target/68355
> 	* calls.c (initialize_argument_information): Call
> 	warn_parameter_passing_abi target hook.
> 	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
> 	argument onto stack.
> 	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
> 	* common.opt: Update -fabi-version description.
> 	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
> 	(ix86_function_arg_advance): Skip empty records.
> 	(ix86_return_in_memory): Return false for empty types.
> 	(ix86_gimplify_va_arg): Call int_maybe_empty_type_size instead of
> 	int_size_in_bytes.
> 	(ix86_is_empty_record_p): New function.
> 	(ix86_warn_parameter_passing_abi): New function.
> 	(TARGET_EMPTY_RECORD_P): Redefine.
> 	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
> 	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
> 	* doc/tm.texi: Regenerated.
> 	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
> 	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
> 	* explow.c (hard_function_value): Call int_maybe_empty_type_size
> 	instead of int_size_in_bytes.
> 	* expr.c (copy_blkmode_to_reg): Likewise.
> 	* function.c (assign_parm_find_entry_rtl): Call
> 	warn_parameter_passing_abi target hook.
> 	(locate_and_pad_parm): Call maybe_empty_type_size instead
> 	size_in_bytes.
> 	* langhooks-def.h (LANG_HOOKS_WARN_EMPTY_CLASS_ABI): Define.
> 	* langhooks.h (struct lang_hooks): Add warn_empty_class_abi.
> 	* target.def (empty_record_p, warn_parameter_passing_abi): New target
> 	hook.
> 	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
> 	(std_gimplify_va_arg_expr): Skip empty records.  Call
> 	maybe_empty_type_size instead size_in_bytes.
> 	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
> 	* tree.c (array_type_nelts_top): New function.
> 	(is_empty_type): New function.
> 	(is_empty_record_p): New function.
> 	(int_maybe_empty_type_size): New function.
> 	(maybe_empty_type_size): New function.
> 	* tree.h (array_type_nelts_top, is_empty_record_p,
> 	int_maybe_empty_type_size, maybe_empty_type_size): Declare.
> 
> 	* cp-lang.c: Include "flags.h".
> 	(LANG_HOOKS_WARN_EMPTY_CLASS_ABI): Redefine.
> 	(cxx_warn_empty_class_abi): New function.
> 	* cp-tree.h (array_type_nelts_top): Remove declaration.
> 	* tree.c (array_type_nelts_top): Move to tree.c.
> 
> 	* testsuite/g++.dg/abi/empty12.C: New test.
> 	* testsuite/g++.dg/abi/empty12.h: New test.
> 	* testsuite/g++.dg/abi/empty12a.c: New test.
> 	* testsuite/g++.dg/abi/empty13.C: New test.
> 	* testsuite/g++.dg/abi/empty13.h: New test.
> 	* testsuite/g++.dg/abi/empty13a.c: New test.
> 	* testsuite/g++.dg/abi/empty14.C: New test.
> 	* testsuite/g++.dg/abi/empty14.h: New test.
> 	* testsuite/g++.dg/abi/empty14a.c: New test.
> 	* testsuite/g++.dg/abi/empty15.C: New test.
> 	* testsuite/g++.dg/abi/empty15.h: New test.
> 	* testsuite/g++.dg/abi/empty15a.c: New test.
> 	* testsuite/g++.dg/abi/empty16.C: New test.
> 	* testsuite/g++.dg/abi/empty16.h: New test.
> 	* testsuite/g++.dg/abi/empty16a.c: New test.
> 	* testsuite/g++.dg/abi/empty17.C: New test.
> 	* testsuite/g++.dg/abi/empty17.h: New test.
> 	* testsuite/g++.dg/abi/empty17a.c: New test.
> 	* testsuite/g++.dg/abi/empty18.C: New test.
> 	* testsuite/g++.dg/abi/empty18.h: New test.
> 	* testsuite/g++.dg/abi/empty18a.c: New test.
> 	* testsuite/g++.dg/abi/empty19.C: New test.
> 	* testsuite/g++.dg/abi/empty19.h: New test.
> 	* testsuite/g++.dg/abi/empty19a.c: New test.
> 	* testsuite/g++.dg/abi/empty20.C: New test.
> 	* testsuite/g++.dg/abi/empty21.C: New test.
> 	* testsuite/g++.dg/abi/empty22.C: New test.
> 	* testsuite/g++.dg/abi/empty22.h: New test.
> 	* testsuite/g++.dg/abi/empty22a.c: New test.
> 	* testsuite/g++.dg/abi/empty23.C: New test.
> 	* testsuite/g++.dg/abi/empty24.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-1.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-10.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-11.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-12.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-2.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-3.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-4.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-5.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-6.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-7.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-8.C: New test.
> 	* testsuite/g++.dg/abi/pr60336-9.C: New test.
> 	* testsuite/g++.dg/abi/pr68355.C: New test.
> 
> diff --git gcc/calls.c gcc/calls.c
> index 3730f43c7a9..29f547942d8 100644
> --- gcc/calls.c
> +++ gcc/calls.c
> @@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
>        args[i].unsignedp = unsignedp;
>        args[i].mode = mode;
>  
> +      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
> +
>        args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
>  						argpos < n_named_args);
>  
> @@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	 Note that in C the default argument promotions
>  	 will prevent such mismatches.  */
>  
> -      size = GET_MODE_SIZE (arg->mode);
> +      if (targetm.calls.empty_record_p (TREE_TYPE (pval)))
> +	size = 0;
> +      else
> +	size = GET_MODE_SIZE (arg->mode);
> +
>        /* Compute how much space the push instruction will push.
>  	 On many machines, pushing a byte will advance the stack
>  	 pointer by a halfword.  */
> @@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  
>        /* This isn't already where we want it on the stack, so put it there.
>  	 This can either be done with push or copy insns.  */
> -      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
> -		      parm_align, partial, reg, used - size, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
> +      if (used
> +	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
> +			      NULL_RTX, parm_align, partial, reg, used - size,
> +			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
> +			      reg_parm_stack_space,
> +			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
>  	sibcall_failure = 1;
>  
>        /* Unless this is a partially-in-register argument, the argument is now
> @@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
>  	     for BLKmode is careful to avoid it.  */
>  	  excess = (arg->locate.size.constant
> -		    - int_size_in_bytes (TREE_TYPE (pval))
> +		    - int_maybe_empty_type_size (TREE_TYPE (pval))
>  		    + partial);
> -	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
> +	  size_rtx = expand_expr (maybe_empty_type_size (TREE_TYPE (pval)),
>  				  NULL_RTX, TYPE_MODE (sizetype),
>  				  EXPAND_NORMAL);
>  	}
> @@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	    }
>  	}
>  
> -      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> -		      parm_align, partial, reg, excess, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
> +      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
> +	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> +			parm_align, partial, reg, excess, argblock,
> +			ARGS_SIZE_RTX (arg->locate.offset),
> +			reg_parm_stack_space,
> +			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
>  
>        /* Unless this is a partially-in-register argument, the argument is now
>  	 in the stack.
> @@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
>    if (TREE_ADDRESSABLE (type))
>      return true;
>  
> +  if (targetm.calls.empty_record_p (type))
> +    return false;
> +
>    /* If the padding and mode of the type is such that a copy into
>       a register would put it into the wrong part of the register.  */
>    if (mode == BLKmode
> diff --git gcc/common.opt gcc/common.opt
> index 836f05b95a2..41d97ae7475 100644
> --- gcc/common.opt
> +++ gcc/common.opt
> @@ -932,7 +932,7 @@ Driver Undocumented
>  ;     Default in G++ 7.
>  ;
>  ; 12: Corrects the calling convention for classes with only deleted copy/move
> -;     constructors.
> +;     constructors and changes passing/returning of empty records.
>  ;     Default in G++ 8.
>  ;
>  ; Additional positive integers will be assigned as new versions of
> diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
> index 1facf121987..c7058ac5638 100644
> --- gcc/config/i386/i386.c
> +++ gcc/config/i386/i386.c
> @@ -7186,6 +7186,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
>    cum->force_bnd_pass = 0;
>    cum->decl = fndecl;
>  
> +  cum->warn_empty = !warn_abi || cum->stdarg;
> +  if (!cum->warn_empty && fntype)
> +    {
> +      function_args_iterator iter;
> +      tree argtype;
> +      bool seen_empty_type = false;
> +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
> +	{
> +	  if (VOID_TYPE_P (argtype))
> +	    break;
> +	  if (targetm.calls.empty_record_p (argtype))
> +	    seen_empty_type = true;
> +	  else if (seen_empty_type)
> +	    {
> +	      cum->warn_empty = true;
> +	      break;
> +	    }
> +	}
> +    }
> +
>    if (!TARGET_64BIT)
>      {
>        /* If there are variable arguments, then we won't pass anything
> @@ -8327,6 +8347,10 @@ ix86_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
>    if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
>      return;
>  
> +  /* Skip empty records because they won't be passed.  */
> +  if (type && targetm.calls.empty_record_p (type))
> +    return;
> +
>    if (mode == BLKmode)
>      bytes = int_size_in_bytes (type);
>    else
> @@ -9293,6 +9317,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
>    if (POINTER_BOUNDS_TYPE_P (type))
>      return false;
>  
> +  /* Empty records are never passed in memory.  */
> +  if (type && targetm.calls.empty_record_p (type))
> +    return false;
> +
>    if (TARGET_64BIT)
>      {
>        if (ix86_function_type_abi (fntype) == MS_ABI)
> @@ -9873,7 +9901,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
>    indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
>    if (indirect_p)
>      type = build_pointer_type (type);
> -  size = int_size_in_bytes (type);
> +  size = int_maybe_empty_type_size (type);
>    rsize = CEIL (size, UNITS_PER_WORD);
>  
>    nat_mode = type_natural_mode (type, NULL, false);
> @@ -28772,6 +28800,44 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
>    return align;
>  }
>  
> +/* Implement TARGET_EMPTY_RECORD_P.  */
> +
> +static bool
> +ix86_is_empty_record_p (const_tree type)
> +{
> +  if (!TARGET_64BIT)
> +    return false;
> +  return is_empty_record_p (type);
> +}
> +
> +/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
> +
> +static void
> +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
> +{
> +  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
> +
> +  if (!cum->warn_empty)
> +    return;
> +
> +  if (!targetm.calls.empty_record_p (type))
> +    return;
> +
> +  if (!lang_hooks.warn_empty_class_abi ())
> +    return;
> +
> +  /* If the actual size of the type is zero, then there is no change
> +     in how objects of this size are passed.  */
> +  if (int_size_in_bytes (type) == 0)
> +    return;
> +
> +  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
> +	   "changes in -fabi-version=12 (GCC 8)", type);
> +
> +  /* Only warn once.  */
> +  cum->warn_empty = false;
> +}
> +
>  /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
>     the data type, and ALIGN is the alignment that the object would
>     ordinarily have.  */
> @@ -50311,6 +50377,12 @@ ix86_run_selftests (void)
>  #undef TARGET_CONSTANT_ALIGNMENT
>  #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
>  
> +#undef TARGET_EMPTY_RECORD_P
> +#define TARGET_EMPTY_RECORD_P ix86_is_empty_record_p
> +
> +#undef TARGET_WARN_PARAMETER_PASSING_ABI
> +#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
> +
>  #if CHECKING_P
>  #undef TARGET_RUN_TARGET_SELFTESTS
>  #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
> diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
> index 837906b5169..7f5d245b568 100644
> --- gcc/config/i386/i386.h
> +++ gcc/config/i386/i386.h
> @@ -1633,6 +1633,8 @@ typedef struct ix86_args {
>    int warn_avx;			/* True when we want to warn about AVX ABI.  */
>    int warn_sse;			/* True when we want to warn about SSE ABI.  */
>    int warn_mmx;			/* True when we want to warn about MMX ABI.  */
> +  int warn_empty;		/* True when we want to warn about empty classes
> +				   passing ABI change.  */
>    int sse_regno;		/* next available sse register number */
>    int mmx_words;		/* # mmx words passed so far */
>    int mmx_nregs;		/* # mmx registers available for passing */
> diff --git gcc/cp/cp-lang.c gcc/cp/cp-lang.c
> index 805319a4185..78336730a94 100644
> --- gcc/cp/cp-lang.c
> +++ gcc/cp/cp-lang.c
> @@ -26,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "langhooks.h"
>  #include "langhooks-def.h"
>  #include "cp-objcp-common.h"
> +#include "flags.h"
>  
>  enum c_language_kind c_language = clk_cxx;
>  static void cp_init_ts (void);
> @@ -35,6 +36,7 @@ static tree cp_eh_personality (void);
>  static tree get_template_innermost_arguments_folded (const_tree);
>  static tree get_template_argument_pack_elems_folded (const_tree);
>  static tree cxx_enum_underlying_base_type (const_tree);
> +static bool cxx_warn_empty_class_abi (void);
>  
>  /* Lang hooks common to C++ and ObjC++ are declared in cp/cp-objcp-common.h;
>     consequently, there should be very few hooks below.  */
> @@ -78,6 +80,8 @@ static tree cxx_enum_underlying_base_type (const_tree);
>  #define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type
>  #undef LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE
>  #define LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE cxx_enum_underlying_base_type
> +#undef LANG_HOOKS_WARN_EMPTY_CLASS_ABI
> +#define LANG_HOOKS_WARN_EMPTY_CLASS_ABI cxx_warn_empty_class_abi
>  
>  #if CHECKING_P
>  #undef LANG_HOOKS_RUN_LANG_SELFTESTS
> @@ -234,6 +238,15 @@ tree cxx_enum_underlying_base_type (const_tree type)
>    return underlying_type;
>  }
>  
> +/* Return true if we should warn about the change in empty class parameter
> +   passing ABI.  */
> +
> +static bool
> +cxx_warn_empty_class_abi (void)
> +{
> +  return warn_abi && abi_version_crosses (12);
> +}
> +
>  #if CHECKING_P
>  
>  namespace selftest {
> diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h
> index f2570b00386..45f5d82be03 100644
> --- gcc/cp/cp-tree.h
> +++ gcc/cp/cp-tree.h
> @@ -6916,7 +6916,6 @@ extern tree canonical_eh_spec			(tree);
>  extern tree build_exception_variant		(tree, tree);
>  extern tree bind_template_template_parm		(tree, tree);
>  extern tree array_type_nelts_total		(tree);
> -extern tree array_type_nelts_top		(tree);
>  extern tree break_out_target_exprs		(tree);
>  extern tree build_ctor_subob_ref		(tree, tree, tree);
>  extern tree replace_placeholders		(tree, tree, bool * = NULL);
> diff --git gcc/cp/tree.c gcc/cp/tree.c
> index 48d40945af3..8a878d70e34 100644
> --- gcc/cp/tree.c
> +++ gcc/cp/tree.c
> @@ -2834,19 +2834,6 @@ cxx_print_statistics (void)
>  }
>  
>  /* Return, as an INTEGER_CST node, the number of elements for TYPE
> -   (which is an ARRAY_TYPE).  This counts only elements of the top
> -   array.  */
> -
> -tree
> -array_type_nelts_top (tree type)
> -{
> -  return fold_build2_loc (input_location,
> -		      PLUS_EXPR, sizetype,
> -		      array_type_nelts (type),
> -		      size_one_node);
> -}
> -
> -/* Return, as an INTEGER_CST node, the number of elements for TYPE
>     (which is an ARRAY_TYPE).  This one is a recursive count of all
>     ARRAY_TYPEs that are clumped together.  */
>  
> diff --git gcc/doc/tm.texi gcc/doc/tm.texi
> index c02f4d35116..9c4405a4cb5 100644
> --- gcc/doc/tm.texi
> +++ gcc/doc/tm.texi
> @@ -4548,6 +4548,16 @@ This target hook returns the mode to be used when accessing raw return registers
>  This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
>  @end deftypefn
>  
> +@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
> +This target hook returns true if the type is an empty record.  The default
> +is to return @code{false}.
> +@end deftypefn
> +
> +@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
> +This target hook warns about the change in empty class parameter passing
> +ABI.
> +@end deftypefn
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
> index 37308e1e551..8b6b64f542f 100644
> --- gcc/doc/tm.texi.in
> +++ gcc/doc/tm.texi.in
> @@ -3437,6 +3437,10 @@ nothing when you use @option{-freg-struct-return} mode.
>  
>  @hook TARGET_GET_RAW_ARG_MODE
>  
> +@hook TARGET_EMPTY_RECORD_P
> +
> +@hook TARGET_WARN_PARAMETER_PASSING_ABI
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/explow.c gcc/explow.c
> index 662865d2808..5c864fedd1b 100644
> --- gcc/explow.c
> +++ gcc/explow.c
> @@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
>    if (REG_P (val)
>        && GET_MODE (val) == BLKmode)
>      {
> -      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
> +      unsigned HOST_WIDE_INT bytes = int_maybe_empty_type_size (valtype);
>        opt_scalar_int_mode tmpmode;
>  
>        /* int_size_in_bytes can return -1.  We don't need a check here
> diff --git gcc/expr.c gcc/expr.c
> index 496d492c9fa..337bd6de579 100644
> --- gcc/expr.c
> +++ gcc/expr.c
> @@ -2746,7 +2746,7 @@ copy_blkmode_to_reg (machine_mode mode, tree src)
>  
>    x = expand_normal (src);
>  
> -  bytes = int_size_in_bytes (TREE_TYPE (src));
> +  bytes = int_maybe_empty_type_size (TREE_TYPE (src));
>    if (bytes == 0)
>      return NULL_RTX;
>  
> diff --git gcc/function.c gcc/function.c
> index 339419ee1da..b7f97bf0ae5 100644
> --- gcc/function.c
> +++ gcc/function.c
> @@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
>        return;
>      }
>  
> +  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
> +					    data->passed_type);
> +
>    entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
>  						    data->promoted_mode,
>  						    data->passed_type,
> @@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
>  
>    part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
>  
> -  sizetree
> -    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
> +  sizetree = (type
> +	      ? maybe_empty_type_size (type)
> +	      : size_int (GET_MODE_SIZE (passed_mode)));
>    where_pad = targetm.calls.function_arg_padding (passed_mode, type);
>    boundary = targetm.calls.function_arg_boundary (passed_mode, type);
>    round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
> diff --git gcc/langhooks-def.h gcc/langhooks-def.h
> index 61b081bd7cc..4cb31a68d3d 100644
> --- gcc/langhooks-def.h
> +++ gcc/langhooks-def.h
> @@ -132,6 +132,7 @@ extern int lhd_type_dwarf_attribute (const_tree, int);
>  #define LANG_HOOKS_CUSTOM_FUNCTION_DESCRIPTORS	false
>  #define LANG_HOOKS_RUN_LANG_SELFTESTS   lhd_do_nothing
>  #define LANG_HOOKS_GET_SUBSTRING_LOCATION lhd_get_substring_location
> +#define LANG_HOOKS_WARN_EMPTY_CLASS_ABI hook_bool_void_false
>  
>  /* Attribute hooks.  */
>  #define LANG_HOOKS_ATTRIBUTE_TABLE		NULL
> @@ -344,7 +345,8 @@ extern void lhd_end_section (void);
>    LANG_HOOKS_DEEP_UNSHARING, \
>    LANG_HOOKS_CUSTOM_FUNCTION_DESCRIPTORS, \
>    LANG_HOOKS_RUN_LANG_SELFTESTS, \
> -  LANG_HOOKS_GET_SUBSTRING_LOCATION \
> +  LANG_HOOKS_GET_SUBSTRING_LOCATION, \
> +  LANG_HOOKS_WARN_EMPTY_CLASS_ABI \
>  }
>  
>  #endif /* GCC_LANG_HOOKS_DEF_H */
> diff --git gcc/langhooks.h gcc/langhooks.h
> index d1288f1965d..ed79b2d8626 100644
> --- gcc/langhooks.h
> +++ gcc/langhooks.h
> @@ -538,6 +538,9 @@ struct lang_hooks
>    const char *(*get_substring_location) (const substring_loc &,
>  					 location_t *out_loc);
>  
> +  /* Return true when we should warn about the empty class ABI change.  */
> +  bool (*warn_empty_class_abi) (void);
> +
>    /* Whenever you add entries here, make sure you adjust langhooks-def.h
>       and langhooks.c accordingly.  */
>  };
> diff --git gcc/target.def gcc/target.def
> index 6a1cd31f2ac..9445a412689 100644
> --- gcc/target.def
> +++ gcc/target.def
> @@ -5043,6 +5043,22 @@ DEFHOOK
>   machine_mode, (int regno),
>   default_get_reg_raw_mode)
>  
> +/* Return true if a type is an empty record.  */
> +DEFHOOK
> +(empty_record_p,
> + "This target hook returns true if the type is an empty record.  The default\n\
> +is to return @code{false}.",
> + bool, (const_tree type),
> + hook_bool_const_tree_false)
> +
> +/* Warn about the change in empty class parameter passing ABI.  */
> +DEFHOOK
> +(warn_parameter_passing_abi,
> + "This target hook warns about the change in empty class parameter passing\n\
> +ABI.",
> + void, (cumulative_args_t ca, tree type),
> + hook_void_CUMULATIVE_ARGS_tree)
> +
>  HOOK_VECTOR_END (calls)
>  
>  DEFHOOK
> diff --git gcc/targhooks.c gcc/targhooks.c
> index 92ecc90d4d4..f117382d565 100644
> --- gcc/targhooks.c
> +++ gcc/targhooks.c
> @@ -734,6 +734,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
>  }
>  
>  void
> +hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
> +				tree ATTRIBUTE_UNUSED)
> +{
> +}
> +
> +void
>  default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
>  			      machine_mode mode ATTRIBUTE_UNUSED,
>  			      const_tree type ATTRIBUTE_UNUSED,
> @@ -2084,6 +2090,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>    /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
>       requires greater alignment, we must perform dynamic alignment.  */
>    if (boundary > align
> +      && !targetm.calls.empty_record_p (type)
>        && !integer_zerop (TYPE_SIZE (type)))
>      {
>        t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
> @@ -2110,7 +2117,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>      }
>  
>    /* Compute the rounded size of the type.  */
> -  type_size = size_in_bytes (type);
> +  type_size = maybe_empty_type_size (type);
>    rounded_size = round_up (type_size, align);
>  
>    /* Reduce rounded_size so it's sharable with the postqueue.  */
> diff --git gcc/targhooks.h gcc/targhooks.h
> index f60bca257f7..722608f35cc 100644
> --- gcc/targhooks.h
> +++ gcc/targhooks.h
> @@ -134,6 +134,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
>    (cumulative_args_t, machine_mode, const_tree, bool);
>  extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
>    (cumulative_args_t, machine_mode, tree, bool);
> +extern void hook_void_CUMULATIVE_ARGS_tree
> +  (cumulative_args_t, tree);
>  extern const char *hook_invalid_arg_for_unprototyped_fn
>    (const_tree, const_tree, const_tree);
>  extern void default_function_arg_advance
> diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
> index e69de29bb2d..20d85ff873e 100644
> --- gcc/testsuite/g++.dg/abi/empty12.C
> +++ gcc/testsuite/g++.dg/abi/empty12.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty12a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty12.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty12.h
> +++ gcc/testsuite/g++.dg/abi/empty12.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
> index e69de29bb2d..34a25bad75d 100644
> --- gcc/testsuite/g++.dg/abi/empty12a.c
> +++ gcc/testsuite/g++.dg/abi/empty12a.c
> @@ -0,0 +1,6 @@
> +#include "empty12.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
> index e69de29bb2d..0cb9a373e35 100644
> --- gcc/testsuite/g++.dg/abi/empty13.C
> +++ gcc/testsuite/g++.dg/abi/empty13.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-x c -fabi-version=11" }
> +// { dg-additional-sources "empty13a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty13.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty13.h
> +++ gcc/testsuite/g++.dg/abi/empty13.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
> index e69de29bb2d..b4303a63826 100644
> --- gcc/testsuite/g++.dg/abi/empty13a.c
> +++ gcc/testsuite/g++.dg/abi/empty13a.c
> @@ -0,0 +1,6 @@
> +#include "empty13.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 == -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
> index e69de29bb2d..2868d8ad3f3 100644
> --- gcc/testsuite/g++.dg/abi/empty14.C
> +++ gcc/testsuite/g++.dg/abi/empty14.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty14a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty14.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
> index e69de29bb2d..5842279cf37 100644
> --- gcc/testsuite/g++.dg/abi/empty14.h
> +++ gcc/testsuite/g++.dg/abi/empty14.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[140]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
> index e69de29bb2d..8b3d7800c36 100644
> --- gcc/testsuite/g++.dg/abi/empty14a.c
> +++ gcc/testsuite/g++.dg/abi/empty14a.c
> @@ -0,0 +1,6 @@
> +#include "empty14.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
> index e69de29bb2d..12385f78c78 100644
> --- gcc/testsuite/g++.dg/abi/empty15.C
> +++ gcc/testsuite/g++.dg/abi/empty15.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty15a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty15.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
> index e69de29bb2d..1c6f26f5ae8 100644
> --- gcc/testsuite/g++.dg/abi/empty15.h
> +++ gcc/testsuite/g++.dg/abi/empty15.h
> @@ -0,0 +1,30 @@
> +struct A1 {};
> +struct A2 {};
> +struct B1 { struct A1 a; struct A2 b; };
> +struct B2 { struct A1 a; struct A2 b; };
> +struct C1 { struct B1 a; struct B2 b; };
> +struct C2 { struct B1 a; struct B2 b; };
> +struct D1 { struct C1 a; struct C2 b; };
> +struct D2 { struct C1 a; struct C2 b; };
> +struct E1 { struct D1 a; struct D2 b; };
> +struct E2 { struct D1 a; struct D2 b; };
> +struct F1 { struct E1 a; struct E2 b; };
> +struct F2 { struct E1 a; struct E2 b; };
> +struct G1 { struct F1 a; struct F2 b; };
> +struct G2 { struct F1 a; struct F2 b; };
> +struct H1 { struct G1 a; struct G2 b; };
> +struct H2 { struct G1 a; struct G2 b; };
> +struct I1 { struct H1 a; struct H2 b; };
> +struct I2 { struct H1 a; struct H2 b; };
> +struct J1 { struct I1 a; struct I2 b; };
> +struct J2 { struct I1 a; struct I2 b; };
> +struct dummy { struct J1 a; struct J2 b; };
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
> index e69de29bb2d..325b2c5ba09 100644
> --- gcc/testsuite/g++.dg/abi/empty15a.c
> +++ gcc/testsuite/g++.dg/abi/empty15a.c
> @@ -0,0 +1,6 @@
> +#include "empty15.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
> index e69de29bb2d..1ca52f9011e 100644
> --- gcc/testsuite/g++.dg/abi/empty16.C
> +++ gcc/testsuite/g++.dg/abi/empty16.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty16a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty16.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
> index e69de29bb2d..7552ae06576 100644
> --- gcc/testsuite/g++.dg/abi/empty16.h
> +++ gcc/testsuite/g++.dg/abi/empty16.h
> @@ -0,0 +1,16 @@
> +#ifdef __cplusplus
> +struct A1 {};
> +struct A2 {};
> +struct dummy : A1, A2 {} ;
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
> index e69de29bb2d..6cb7fbccecc 100644
> --- gcc/testsuite/g++.dg/abi/empty16a.c
> +++ gcc/testsuite/g++.dg/abi/empty16a.c
> @@ -0,0 +1,6 @@
> +#include "empty16.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
> index e69de29bb2d..d386e5481af 100644
> --- gcc/testsuite/g++.dg/abi/empty17.C
> +++ gcc/testsuite/g++.dg/abi/empty17.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty17a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty17.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
> index e69de29bb2d..9cf72baca2e 100644
> --- gcc/testsuite/g++.dg/abi/empty17.h
> +++ gcc/testsuite/g++.dg/abi/empty17.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 15;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 15;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 15;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
> index e69de29bb2d..24408fde09c 100644
> --- gcc/testsuite/g++.dg/abi/empty17a.c
> +++ gcc/testsuite/g++.dg/abi/empty17a.c
> @@ -0,0 +1,6 @@
> +#include "empty17.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
> index e69de29bb2d..be69c6a2115 100644
> --- gcc/testsuite/g++.dg/abi/empty18.C
> +++ gcc/testsuite/g++.dg/abi/empty18.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty18a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty18.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
> index e69de29bb2d..86e7ecdd211 100644
> --- gcc/testsuite/g++.dg/abi/empty18.h
> +++ gcc/testsuite/g++.dg/abi/empty18.h
> @@ -0,0 +1,9 @@
> +struct dummy { int d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
> index e69de29bb2d..902860bdc01 100644
> --- gcc/testsuite/g++.dg/abi/empty18a.c
> +++ gcc/testsuite/g++.dg/abi/empty18a.c
> @@ -0,0 +1,6 @@
> +#include "empty18.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
> index e69de29bb2d..84f5b75558b 100644
> --- gcc/testsuite/g++.dg/abi/empty19.C
> +++ gcc/testsuite/g++.dg/abi/empty19.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty19a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty19.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
> index e69de29bb2d..616b87bdd93 100644
> --- gcc/testsuite/g++.dg/abi/empty19.h
> +++ gcc/testsuite/g++.dg/abi/empty19.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
> index e69de29bb2d..767b1eb7320 100644
> --- gcc/testsuite/g++.dg/abi/empty19a.c
> +++ gcc/testsuite/g++.dg/abi/empty19a.c
> @@ -0,0 +1,6 @@
> +#include "empty19.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
> index e69de29bb2d..5022033f669 100644
> --- gcc/testsuite/g++.dg/abi/empty20.C
> +++ gcc/testsuite/g++.dg/abi/empty20.C
> @@ -0,0 +1,19 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11 -O0" }
> +
> +struct A { };
> +
> +void f(A, A) { }	// No warning, trailing parms all empty
> +void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +__attribute__ ((always_inline))
> +inline void f(A a, int i) // No warning, always inlined
> +{
> +  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> +int main()
> +{
> +  A a;
> +  f(a,a);
> +  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(a,42);
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
> index e69de29bb2d..3b2e3b836b1 100644
> --- gcc/testsuite/g++.dg/abi/empty21.C
> +++ gcc/testsuite/g++.dg/abi/empty21.C
> @@ -0,0 +1,23 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct A { };
> +
> +void f(int i, ...)
> +{
> +  va_list ap;
> +  va_start (ap, i);
> +  if (i >= 1)
> +    va_arg (ap, A);
> +  if (i >= 2)
> +    va_arg (ap, int);
> +}
> +
> +int main()
> +{
> +  f(0);
> +  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
> index e69de29bb2d..f4f4a02bf31 100644
> --- gcc/testsuite/g++.dg/abi/empty22.C
> +++ gcc/testsuite/g++.dg/abi/empty22.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty22a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty22.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
> index e69de29bb2d..8d54dc74519 100644
> --- gcc/testsuite/g++.dg/abi/empty22.h
> +++ gcc/testsuite/g++.dg/abi/empty22.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 0;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 0;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 0;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
> index e69de29bb2d..7606c524263 100644
> --- gcc/testsuite/g++.dg/abi/empty22a.c
> +++ gcc/testsuite/g++.dg/abi/empty22a.c
> @@ -0,0 +1,6 @@
> +#include "empty22.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
> index e69de29bb2d..b97d2804529 100644
> --- gcc/testsuite/g++.dg/abi/empty23.C
> +++ gcc/testsuite/g++.dg/abi/empty23.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[0];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S)
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]);
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
> index e69de29bb2d..81deb36ff9f 100644
> --- gcc/testsuite/g++.dg/abi/empty24.C
> +++ gcc/testsuite/g++.dg/abi/empty24.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S)
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]);
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
> index e69de29bb2d..59447890cec 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-1.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-1.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
> index e69de29bb2d..960cc2307d1 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-10.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-10.C
> @@ -0,0 +1,50 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0 { };
> +struct dummy1 { };
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
> index e69de29bb2d..14cd6d0ff3d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-11.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-11.C
> @@ -0,0 +1,56 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +  void bar (void);
> +};
> +struct dummy1
> +{
> +  void foo (void);
> +};
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
> index e69de29bb2d..09917547930 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-12.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-12.C
> @@ -0,0 +1,57 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +};
> +struct dummy1
> +{
> +  unsigned : 15;
> +};
> +struct dummy : dummy0, dummy1
> +{
> +};
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
> index e69de29bb2d..1c6c3eb8f01 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-2.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-2.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...) // { dg-warning "empty" }
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
> index e69de29bb2d..4157e553b6b 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-3.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-3.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
> index e69de29bb2d..266f67a537d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-4.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-4.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -fabi-version=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count == 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
> index e69de29bb2d..fe838750f55 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-5.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-5.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; struct dummy j; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
> index e69de29bb2d..6e08c8f06fa 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-6.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-6.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i1; struct dummy i2; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
> index e69de29bb2d..3b8b8ba6f35 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-7.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-7.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i[120]; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
> index e69de29bb2d..a1ffb64ef02 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-8.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-8.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct{} a[7][3]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
> index e69de29bb2d..393f02b62f0 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-9.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-9.C
> @@ -0,0 +1,28 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct A1 {}; struct A2 {};
> +struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
> +struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
> +struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
> +struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
> +struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
> +struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
> +struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
> +struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
> +struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
> +struct dummy { J1 a; J2 b; };
> +
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
> index e69de29bb2d..1354fc497b5 100644
> --- gcc/testsuite/g++.dg/abi/pr68355.C
> +++ gcc/testsuite/g++.dg/abi/pr68355.C
> @@ -0,0 +1,24 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +template<typename _Tp, _Tp __v>
> +struct integral_constant
> +{
> +  static constexpr _Tp value = __v;
> +  typedef _Tp value_type;
> +  typedef integral_constant<_Tp, __v> type;
> +  constexpr operator value_type() const { return value; }
> +};
> +
> +typedef integral_constant<bool, true> true_type;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  true_type y;
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
> diff --git gcc/tree.c gcc/tree.c
> index fa6fcb1da71..a32c238de5d 100644
> --- gcc/tree.c
> +++ gcc/tree.c
> @@ -3135,6 +3135,20 @@ array_type_nelts (const_tree type)
>  	  ? max
>  	  : fold_build2 (MINUS_EXPR, TREE_TYPE (max), max, min));
>  }
> +
> +/* Return, as an INTEGER_CST node, the number of elements for TYPE
> +   (which is an ARRAY_TYPE).  This counts only elements of the top
> +   array.  */
> +
> +tree
> +array_type_nelts_top (const_tree type)
> +{
> +  return fold_build2_loc (input_location,
> +			  PLUS_EXPR, sizetype,
> +			  array_type_nelts (type),
> +			  size_one_node);
> +}
> +
>  
>  /* If arg is static -- a reference to an object in static storage -- then
>     return the object.  This is not the same as the C meaning of `static'.
> @@ -13811,6 +13825,89 @@ get_nonnull_args (const_tree fntype)
>    return argmap;
>  }
>  
> +/* Returns true if TYPE is a type where it and all of its subobjects
> +   (recursively) are of structure, union, or array type.  */
> +
> +static bool
> +is_empty_type (tree type)
> +{
> +  if (RECORD_OR_UNION_TYPE_P (type))
> +    {
> +      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
> +	{
> +	  if (TREE_CODE (field) == FIELD_DECL)
> +	    {
> +	      tree ftype = TREE_TYPE (field);
> +	      /* Don't consider struct S { struct { } a; int b[0]; };
> +		 an empty type.  */
> +	      if (TREE_CODE (ftype) == ARRAY_TYPE
> +		  && integer_zerop (array_type_nelts_top (ftype)))
> +		{
> +		  tree t = DECL_CHAIN (field);
> +		  bool found = false;
> +		  /* See if the zero-length array is followed by another
> +		     FIELD_DECL.  */
> +		  while (t)
> +		    {
> +		      if (TREE_CODE (t) == FIELD_DECL)
> +			{
> +			  found = true;
> +			  break;
> +			}
> +		      t = DECL_CHAIN (t);
> +		    }
> +		  if (!found)
> +		    return false;
> +		}
> +	      if ((DECL_NAME (field) || RECORD_OR_UNION_TYPE_P (ftype))
> +		  && !is_empty_type (ftype))
> +		return false;
> +	    }
> +	}
> +      return true;
> +    }
> +  else if (TREE_CODE (type) == ARRAY_TYPE)
> +    return (integer_zerop (array_type_nelts_top (type))
> +	    || is_empty_type (TREE_TYPE (type)));
> +  return false;
> +}
> +
> +/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
> +   that shouldn't be passed via stack.  */
> +
> +bool
> +is_empty_record_p (const_tree type)
> +{
> +  if (!abi_version_at_least (12))
> +    return false;
> +
> +  if (type == error_mark_node)
> +    return false;
> +
> +  if (TREE_ADDRESSABLE (type))
> +    return false;
> +
> +  return is_empty_type (TYPE_MAIN_VARIANT (type));
> +}
> +
> +/* Like int_size_in_bytes, but handle empty records specially.  */
> +
> +HOST_WIDE_INT
> +int_maybe_empty_type_size (const_tree type)
> +{
> +  return (targetm.calls.empty_record_p (type)
> +	  ? 0 : int_size_in_bytes (type));
> +}
> +
> +/* Like size_in_bytes, but handle empty records specially.  */
> +
> +tree
> +maybe_empty_type_size (const_tree type)
> +{
> +  return (targetm.calls.empty_record_p (type)
> +	  ? size_zero_node : size_in_bytes (type));
> +}
> +
>  /* List of pointer types used to declare builtins before we have seen their
>     real declaration.
>  
> diff --git gcc/tree.h gcc/tree.h
> index 7214ae2275c..fb8af8bac7d 100644
> --- gcc/tree.h
> +++ gcc/tree.h
> @@ -4099,6 +4099,7 @@ extern tree build_method_type (tree, tree);
>  extern tree build_offset_type (tree, tree);
>  extern tree build_complex_type (tree, bool named = false);
>  extern tree array_type_nelts (const_tree);
> +extern tree array_type_nelts_top (const_tree);
>  
>  extern tree value_member (tree, tree);
>  extern tree purpose_member (const_tree, tree);
> @@ -5434,6 +5435,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
>  
>  extern bool nonnull_arg_p (const_tree);
>  extern bool is_redundant_typedef (const_tree);
> +extern bool is_empty_record_p (const_tree);
> +extern HOST_WIDE_INT int_maybe_empty_type_size (const_tree);
> +extern tree maybe_empty_type_size (const_tree);
>  
>  extern location_t
>  set_source_range (tree expr, location_t start, location_t finish);
> 
> 	Marek
> 
>
Jakub Jelinek Oct. 27, 2017, 10:38 a.m. UTC | #2
On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
> I fear it doesn't work at all with LTO (you'll always get the old ABI
> if I read the patch correctly).  This is because the function
> computing the size looks at flag_abi_version which isn't saved
> per function / TU.
> 
> Similarly you'll never get the ABI warning with LTO (less of a big
> deal of course) because the langhook doesn't reflect things correctly
> either.
> 
> So...  can we instead compute whether a type is "empty" according
> to the ABI early and store the result in the type (thinking of
> doing this in layout_type?).  Similarly set a flag whether to
> warn.  Why do you warn from backends / code emission and not
> from the FEs?  Is that to avoid warnings for calls that got inlined?
> Maybe the FE could set a flag on the call itself (ok, somewhat
> awkward to funnel through gimple).

Warning in the FE is too early both because of the inlining, never
emitted functions and because whether an empty struct is passed differently
from the past matters on the backend (whether its psABI says it should be
not passed at all or not).

Perhaps if empty types are rare enough it could be an artificial attribute
on the type if we can't get a spare bit for that.  But computing in the FE
or before free_lang_data and saving on the type whether it is empty or not
seems reasonable to me.

	Jakub
Richard Biener Oct. 27, 2017, 10:46 a.m. UTC | #3
On Fri, 27 Oct 2017, Jakub Jelinek wrote:

> On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
> > I fear it doesn't work at all with LTO (you'll always get the old ABI
> > if I read the patch correctly).  This is because the function
> > computing the size looks at flag_abi_version which isn't saved
> > per function / TU.
> > 
> > Similarly you'll never get the ABI warning with LTO (less of a big
> > deal of course) because the langhook doesn't reflect things correctly
> > either.
> > 
> > So...  can we instead compute whether a type is "empty" according
> > to the ABI early and store the result in the type (thinking of
> > doing this in layout_type?).  Similarly set a flag whether to
> > warn.  Why do you warn from backends / code emission and not
> > from the FEs?  Is that to avoid warnings for calls that got inlined?
> > Maybe the FE could set a flag on the call itself (ok, somewhat
> > awkward to funnel through gimple).
> 
> Warning in the FE is too early both because of the inlining, never
> emitted functions and because whether an empty struct is passed differently
> from the past matters on the backend (whether its psABI says it should be
> not passed at all or not).
> 
> Perhaps if empty types are rare enough it could be an artificial attribute
> on the type if we can't get a spare bit for that.  But computing in the FE
> or before free_lang_data and saving on the type whether it is empty or not
> seems reasonable to me.

There are 18 unused bits in tree_type_common if we don't want to re-use
any.  For the warning I first thought of setting TREE_NO_WARNING on it
but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
I'd move TYPE_ARTIFICIAL somewhere else.

Richard.
Jeff Law Oct. 27, 2017, 4:27 p.m. UTC | #4
On 10/27/2017 04:38 AM, Jakub Jelinek wrote:
> On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
>> I fear it doesn't work at all with LTO (you'll always get the old ABI
>> if I read the patch correctly).  This is because the function
>> computing the size looks at flag_abi_version which isn't saved
>> per function / TU.
>>
>> Similarly you'll never get the ABI warning with LTO (less of a big
>> deal of course) because the langhook doesn't reflect things correctly
>> either.
>>
>> So...  can we instead compute whether a type is "empty" according
>> to the ABI early and store the result in the type (thinking of
>> doing this in layout_type?).  Similarly set a flag whether to
>> warn.  Why do you warn from backends / code emission and not
>> from the FEs?  Is that to avoid warnings for calls that got inlined?
>> Maybe the FE could set a flag on the call itself (ok, somewhat
>> awkward to funnel through gimple).
> 
> Warning in the FE is too early both because of the inlining, never
> emitted functions and because whether an empty struct is passed differently
> from the past matters on the backend (whether its psABI says it should be
> not passed at all or not).
Right.  My recollection was there were tons of warnings when we tried to
do this in the FE and everything we looked at was a false positive.
Moving the warning to be more controlled by the backend was an effort to
reduce the noise so that the result was actually meaningful.

Jeff
Marek Polacek Nov. 1, 2017, 11:42 a.m. UTC | #5
On Fri, Oct 27, 2017 at 12:46:12PM +0200, Richard Biener wrote:
> On Fri, 27 Oct 2017, Jakub Jelinek wrote:
> 
> > On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
> > > I fear it doesn't work at all with LTO (you'll always get the old ABI
> > > if I read the patch correctly).  This is because the function
> > > computing the size looks at flag_abi_version which isn't saved
> > > per function / TU.
> > > 
> > > Similarly you'll never get the ABI warning with LTO (less of a big
> > > deal of course) because the langhook doesn't reflect things correctly
> > > either.
> > > 
> > > So...  can we instead compute whether a type is "empty" according
> > > to the ABI early and store the result in the type (thinking of
> > > doing this in layout_type?).  Similarly set a flag whether to
> > > warn.  Why do you warn from backends / code emission and not
> > > from the FEs?  Is that to avoid warnings for calls that got inlined?
> > > Maybe the FE could set a flag on the call itself (ok, somewhat
> > > awkward to funnel through gimple).
> > 
> > Warning in the FE is too early both because of the inlining, never
> > emitted functions and because whether an empty struct is passed differently
> > from the past matters on the backend (whether its psABI says it should be
> > not passed at all or not).
> > 
> > Perhaps if empty types are rare enough it could be an artificial attribute
> > on the type if we can't get a spare bit for that.  But computing in the FE
> > or before free_lang_data and saving on the type whether it is empty or not
> > seems reasonable to me.
> 
> There are 18 unused bits in tree_type_common if we don't want to re-use
> any.  For the warning I first thought of setting TREE_NO_WARNING on it
> but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
> I'd move TYPE_ARTIFICIAL somewhere else.

All right, should be done in the below.  I've introduced two new flags,
TYPE_EMPTY_P (says whether the type is empty according to the psABI), and
TYPE_WARN_EMPTY_P (whether we should warn).  I've added two new fields to
type_type_common and moved TYPE_ARTIFICIAL there; TYPE_WARN_EMPTY_P is now
mapped to nowarning_flag.  So this should work with LTO, as demonstrated
by g++.dg/lto/pr60336_0.C.  

Regarding LTO and -Wabi warning, I've added Optimization to c.opt so that
we get warnings with LTO.  But as pointed out IRC, this doesn't fully work
with cross-inlining.  I tried to do some flags merging in inline_call, but
that didn't help, one of the problems is that warn_abi_version lives in
c-family only.  Not sure if I'll be able to improve things here though.

Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
Bootstrap-lto passed on x86_64-linux and ppc64-linux.

2017-11-01  Marek Polacek  <polacek@redhat.com>
	    H.J. Lu  <hongjiu.lu@intel.com>
	    Jason Merrill  <jason@redhat.com>

	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* c.opt (Wabi, Wabi=): Add Optimization.

	* class.c (layout_class_type): Set TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.
	* cp-tree.h (array_type_nelts_top): Remove.
	* tree.c (array_type_nelts_top): Move to tree.c.

	* lto.c (compare_tree_sccs_1): Compare TYPE_WARN_EMPTY_P and
	TYPE_EMPTY_P.

	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_function_arg_advance): Skip empty records.
	(ix86_return_in_memory): Return false for empty types.
	(ix86_gimplify_va_arg): Call int_maybe_empty_type_size instead of
	int_size_in_bytes.
	(ix86_is_empty_record_p): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* explow.c (hard_function_value): Call int_maybe_empty_type_size
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (assign_parm_find_entry_rtl): Call
	warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call maybe_empty_type_size instead
	size_in_bytes.
	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and
	TYPE_WARN_EMPTY_P.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hook.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	maybe_empty_type_size instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree-core.h (tree_type_common): Update comment.  Add artificial_flag
	and empty_flag.
	* tree-streamer-in.c (unpack_ts_base_value_fields): Stream
	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
	TYPE_ARTIFICIAL.
	* tree-streamer-out.c (pack_ts_base_value_fields): Stream
	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
	TYPE_ARTIFICIAL.
	* tree.c (array_type_nelts_top): New function.
	(is_empty_type): New function.
	(is_empty_record_p): New function.
	(int_maybe_empty_type_size): New function.
	(maybe_empty_type_size): New function.
	* tree.h: Define TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.  Map
	TYPE_ARTIFICIAL to type_common.artificial_flag.
	(array_type_nelts_top, is_empty_record_p, int_maybe_empty_type_size,
	maybe_empty_type_size): Declare.

	* g++.dg/abi/empty12.C: New test.
	* g++.dg/abi/empty12.h: New test.
	* g++.dg/abi/empty12a.c: New test.
	* g++.dg/abi/empty13.C: New test.
	* g++.dg/abi/empty13.h: New test.
	* g++.dg/abi/empty13a.c: New test.
	* g++.dg/abi/empty14.C: New test.
	* g++.dg/abi/empty14.h: New test.
	* g++.dg/abi/empty14a.c: New test.
	* g++.dg/abi/empty15.C: New test.
	* g++.dg/abi/empty15.h: New test.
	* g++.dg/abi/empty15a.c: New test.
	* g++.dg/abi/empty16.C: New test.
	* g++.dg/abi/empty16.h: New test.
	* g++.dg/abi/empty16a.c: New test.
	* g++.dg/abi/empty17.C: New test.
	* g++.dg/abi/empty17.h: New test.
	* g++.dg/abi/empty17a.c: New test.
	* g++.dg/abi/empty18.C: New test.
	* g++.dg/abi/empty18.h: New test.
	* g++.dg/abi/empty18a.c: New test.
	* g++.dg/abi/empty19.C: New test.
	* g++.dg/abi/empty19.h: New test.
	* g++.dg/abi/empty19a.c: New test.
	* g++.dg/abi/empty20.C: New test.
	* g++.dg/abi/empty21.C: New test.
	* g++.dg/abi/empty22.C: New test.
	* g++.dg/abi/empty22.h: New test.
	* g++.dg/abi/empty22a.c: New test.
	* g++.dg/abi/empty23.C: New test.
	* g++.dg/abi/empty24.C: New test.
	* g++.dg/abi/pr60336-1.C: New test.
	* g++.dg/abi/pr60336-10.C: New test.
	* g++.dg/abi/pr60336-11.C: New test.
	* g++.dg/abi/pr60336-12.C: New test.
	* g++.dg/abi/pr60336-2.C: New test.
	* g++.dg/abi/pr60336-3.C: New test.
	* g++.dg/abi/pr60336-4.C: New test.
	* g++.dg/abi/pr60336-5.C: New test.
	* g++.dg/abi/pr60336-6.C: New test.
	* g++.dg/abi/pr60336-7.C: New test.
	* g++.dg/abi/pr60336-8.C: New test.
	* g++.dg/abi/pr60336-9.C: New test.
	* g++.dg/abi/pr68355.C: New test.
	* g++.dg/lto/pr60336_0.C: New test.

diff --git gcc/c-family/c.opt gcc/c-family/c.opt
index dae124ac1c2..5c45d1b5062 100644
--- gcc/c-family/c.opt
+++ gcc/c-family/c.opt
@@ -257,11 +257,11 @@ C ObjC C++ ObjC++ Joined Separate MissingArgError(macro name missing after %qs)
 -U<macro>	Undefine <macro>.
 
 Wabi
-C ObjC C++ ObjC++ LTO Var(warn_abi) Warning
+C ObjC C++ ObjC++ LTO Optimization Var(warn_abi) Warning
 Warn about things that will change when compiling with an ABI-compliant compiler.
 
 Wabi=
-C ObjC C++ ObjC++ LTO Joined RejectNegative UInteger Warning
+C ObjC C++ ObjC++ LTO Optimization Joined RejectNegative UInteger Warning
 Warn about things that change between the current -fabi-version and the specified version.
 
 Wabi-tag
diff --git gcc/calls.c gcc/calls.c
index 3730f43c7a9..aefa4b561e6 100644
--- gcc/calls.c
+++ gcc/calls.c
@@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       args[i].unsignedp = unsignedp;
       args[i].mode = mode;
 
+      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
+
       args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
 						argpos < n_named_args);
 
@@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	 Note that in C the default argument promotions
 	 will prevent such mismatches.  */
 
-      size = GET_MODE_SIZE (arg->mode);
+      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
+	size = 0;
+      else
+	size = GET_MODE_SIZE (arg->mode);
+
       /* Compute how much space the push instruction will push.
 	 On many machines, pushing a byte will advance the stack
 	 pointer by a halfword.  */
@@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 
       /* This isn't already where we want it on the stack, so put it there.
 	 This can either be done with push or copy insns.  */
-      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
-		      parm_align, partial, reg, used - size, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
+      if (used
+	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
+			      NULL_RTX, parm_align, partial, reg, used - size,
+			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
+			      reg_parm_stack_space,
+			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
 	sibcall_failure = 1;
 
       /* Unless this is a partially-in-register argument, the argument is now
@@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
 	     for BLKmode is careful to avoid it.  */
 	  excess = (arg->locate.size.constant
-		    - int_size_in_bytes (TREE_TYPE (pval))
+		    - int_maybe_empty_type_size (TREE_TYPE (pval))
 		    + partial);
-	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
+	  size_rtx = expand_expr (maybe_empty_type_size (TREE_TYPE (pval)),
 				  NULL_RTX, TYPE_MODE (sizetype),
 				  EXPAND_NORMAL);
 	}
@@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	    }
 	}
 
-      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
-		      parm_align, partial, reg, excess, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
+      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
+	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
+			parm_align, partial, reg, excess, argblock,
+			ARGS_SIZE_RTX (arg->locate.offset),
+			reg_parm_stack_space,
+			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
 
       /* Unless this is a partially-in-register argument, the argument is now
 	 in the stack.
@@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
   if (TREE_ADDRESSABLE (type))
     return true;
 
+  if (TYPE_EMPTY_P (type))
+    return false;
+
   /* If the padding and mode of the type is such that a copy into
      a register would put it into the wrong part of the register.  */
   if (mode == BLKmode
diff --git gcc/common.opt gcc/common.opt
index f8f2ed3db8a..28a0185f0cf 100644
--- gcc/common.opt
+++ gcc/common.opt
@@ -936,7 +936,7 @@ Driver Undocumented
 ;     Default in G++ 7.
 ;
 ; 12: Corrects the calling convention for classes with only deleted copy/move
-;     constructors.
+;     constructors and changes passing/returning of empty records.
 ;     Default in G++ 8.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
index 382635f4fc7..67dcf8aa38c 100644
--- gcc/config/i386/i386.c
+++ gcc/config/i386/i386.c
@@ -7186,6 +7186,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->force_bnd_pass = 0;
   cum->decl = fndecl;
 
+  cum->warn_empty = !warn_abi || cum->stdarg;
+  if (!cum->warn_empty && fntype)
+    {
+      function_args_iterator iter;
+      tree argtype;
+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+	{
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  if (TYPE_EMPTY_P (argtype))
+	    seen_empty_type = true;
+	  else if (seen_empty_type)
+	    {
+	      cum->warn_empty = true;
+	      break;
+	    }
+	}
+    }
+
   if (!TARGET_64BIT)
     {
       /* If there are variable arguments, then we won't pass anything
@@ -8327,6 +8347,10 @@ ix86_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
   if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
     return;
 
+  /* Skip empty records because they won't be passed.  */
+  if (type && TYPE_EMPTY_P (type))
+    return;
+
   if (mode == BLKmode)
     bytes = int_size_in_bytes (type);
   else
@@ -9293,6 +9317,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
   if (POINTER_BOUNDS_TYPE_P (type))
     return false;
 
+  /* Empty records are never passed in memory.  */
+  if (type && TYPE_EMPTY_P (type))
+    return false;
+
   if (TARGET_64BIT)
     {
       if (ix86_function_type_abi (fntype) == MS_ABI)
@@ -9873,7 +9901,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
   indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
   if (indirect_p)
     type = build_pointer_type (type);
-  size = int_size_in_bytes (type);
+  size = int_maybe_empty_type_size (type);
   rsize = CEIL (size, UNITS_PER_WORD);
 
   nat_mode = type_natural_mode (type, NULL, false);
@@ -28759,6 +28787,44 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Implement TARGET_EMPTY_RECORD_P.  */
+
+static bool
+ix86_is_empty_record_p (const_tree type)
+{
+  if (!TARGET_64BIT)
+    return false;
+  return is_empty_record_p (type);
+}
+
+/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
+
+static void
+ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
+{
+  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
+
+  if (!cum->warn_empty)
+    return;
+
+  if (!TYPE_EMPTY_P (type))
+    return;
+
+  if (!TYPE_WARN_EMPTY_P (type))
+    return;
+
+  /* If the actual size of the type is zero, then there is no change
+     in how objects of this size are passed.  */
+  if (int_size_in_bytes (type) == 0)
+    return;
+
+  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
+	   "changes in -fabi-version=12 (GCC 8)", type);
+
+  /* Only warn once.  */
+  cum->warn_empty = false;
+}
+
 /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
    the data type, and ALIGN is the alignment that the object would
    ordinarily have.  */
@@ -50308,6 +50374,12 @@ ix86_run_selftests (void)
 #undef TARGET_CONSTANT_ALIGNMENT
 #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
 
+#undef TARGET_EMPTY_RECORD_P
+#define TARGET_EMPTY_RECORD_P ix86_is_empty_record_p
+
+#undef TARGET_WARN_PARAMETER_PASSING_ABI
+#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
+
 #if CHECKING_P
 #undef TARGET_RUN_TARGET_SELFTESTS
 #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
index 837906b5169..7f5d245b568 100644
--- gcc/config/i386/i386.h
+++ gcc/config/i386/i386.h
@@ -1633,6 +1633,8 @@ typedef struct ix86_args {
   int warn_avx;			/* True when we want to warn about AVX ABI.  */
   int warn_sse;			/* True when we want to warn about SSE ABI.  */
   int warn_mmx;			/* True when we want to warn about MMX ABI.  */
+  int warn_empty;		/* True when we want to warn about empty classes
+				   passing ABI change.  */
   int sse_regno;		/* next available sse register number */
   int mmx_words;		/* # mmx words passed so far */
   int mmx_nregs;		/* # mmx registers available for passing */
diff --git gcc/cp/class.c gcc/cp/class.c
index 9ef50657cae..7d09a06dd24 100644
--- gcc/cp/class.c
+++ gcc/cp/class.c
@@ -6324,6 +6324,10 @@ layout_class_type (tree t, tree *virtuals_p)
       && tree_int_cst_lt (sizeof_biggest_empty_class,
 			  TYPE_SIZE_UNIT (t)))
     sizeof_biggest_empty_class = TYPE_SIZE_UNIT (t);
+
+  /* Handle empty records as per the x86-64 psABI.  */
+  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
+  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
 }
 
 /* Determine the "key method" for the class type indicated by TYPE,
diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h
index 3aefd7e40f4..984f496a459 100644
--- gcc/cp/cp-tree.h
+++ gcc/cp/cp-tree.h
@@ -6916,7 +6916,6 @@ extern tree canonical_eh_spec			(tree);
 extern tree build_exception_variant		(tree, tree);
 extern tree bind_template_template_parm		(tree, tree);
 extern tree array_type_nelts_total		(tree);
-extern tree array_type_nelts_top		(tree);
 extern tree break_out_target_exprs		(tree);
 extern tree build_ctor_subob_ref		(tree, tree, tree);
 extern tree replace_placeholders		(tree, tree, bool * = NULL);
diff --git gcc/cp/tree.c gcc/cp/tree.c
index 48d40945af3..8a878d70e34 100644
--- gcc/cp/tree.c
+++ gcc/cp/tree.c
@@ -2834,19 +2834,6 @@ cxx_print_statistics (void)
 }
 
 /* Return, as an INTEGER_CST node, the number of elements for TYPE
-   (which is an ARRAY_TYPE).  This counts only elements of the top
-   array.  */
-
-tree
-array_type_nelts_top (tree type)
-{
-  return fold_build2_loc (input_location,
-		      PLUS_EXPR, sizetype,
-		      array_type_nelts (type),
-		      size_one_node);
-}
-
-/* Return, as an INTEGER_CST node, the number of elements for TYPE
    (which is an ARRAY_TYPE).  This one is a recursive count of all
    ARRAY_TYPEs that are clumped together.  */
 
diff --git gcc/doc/tm.texi gcc/doc/tm.texi
index be8b3620684..f8ff820383b 100644
--- gcc/doc/tm.texi
+++ gcc/doc/tm.texi
@@ -4548,6 +4548,16 @@ This target hook returns the mode to be used when accessing raw return registers
 This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
+This target hook returns true if the type is an empty record.  The default
+is to return @code{false}.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
+This target hook warns about the change in empty class parameter passing
+ABI.
+@end deftypefn
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
index d433e3a9c6b..1e896c70cee 100644
--- gcc/doc/tm.texi.in
+++ gcc/doc/tm.texi.in
@@ -3437,6 +3437,10 @@ nothing when you use @option{-freg-struct-return} mode.
 
 @hook TARGET_GET_RAW_ARG_MODE
 
+@hook TARGET_EMPTY_RECORD_P
+
+@hook TARGET_WARN_PARAMETER_PASSING_ABI
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/explow.c gcc/explow.c
index 662865d2808..5c864fedd1b 100644
--- gcc/explow.c
+++ gcc/explow.c
@@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
   if (REG_P (val)
       && GET_MODE (val) == BLKmode)
     {
-      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
+      unsigned HOST_WIDE_INT bytes = int_maybe_empty_type_size (valtype);
       opt_scalar_int_mode tmpmode;
 
       /* int_size_in_bytes can return -1.  We don't need a check here
diff --git gcc/expr.c gcc/expr.c
index 496d492c9fa..337bd6de579 100644
--- gcc/expr.c
+++ gcc/expr.c
@@ -2746,7 +2746,7 @@ copy_blkmode_to_reg (machine_mode mode, tree src)
 
   x = expand_normal (src);
 
-  bytes = int_size_in_bytes (TREE_TYPE (src));
+  bytes = int_maybe_empty_type_size (TREE_TYPE (src));
   if (bytes == 0)
     return NULL_RTX;
 
diff --git gcc/function.c gcc/function.c
index fe3d9c1bbf3..dd227993050 100644
--- gcc/function.c
+++ gcc/function.c
@@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
       return;
     }
 
+  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
+					    data->passed_type);
+
   entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
 						    data->promoted_mode,
 						    data->passed_type,
@@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
 
   part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
 
-  sizetree
-    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
+  sizetree = (type
+	      ? maybe_empty_type_size (type)
+	      : size_int (GET_MODE_SIZE (passed_mode)));
   where_pad = targetm.calls.function_arg_padding (passed_mode, type);
   boundary = targetm.calls.function_arg_boundary (passed_mode, type);
   round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
index 554f9cc9f01..83ade46ab07 100644
--- gcc/lto-streamer-out.c
+++ gcc/lto-streamer-out.c
@@ -1008,7 +1008,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
   else if (TYPE_P (t))
     hstate.add_flag (TYPE_UNSIGNED (t));
   if (TYPE_P (t))
-    hstate.add_flag (TYPE_ARTIFICIAL (t));
+    hstate.add_flag (TYPE_WARN_EMPTY_P (t));
   else
     hstate.add_flag (TREE_NO_WARNING (t));
   hstate.add_flag (TREE_NOTHROW (t));
@@ -1166,6 +1166,8 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
       hstate.commit_flag ();
       hstate.add_int (TYPE_PRECISION (t));
       hstate.add_int (TYPE_ALIGN (t));
+      hstate.add_int (TYPE_EMPTY_P (t));
+      hstate.add_int (TYPE_ARTIFICIAL (t));
     }
 
   if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
diff --git gcc/lto/lto.c gcc/lto/lto.c
index 63ba73c0dbf..011f07fd593 100644
--- gcc/lto/lto.c
+++ gcc/lto/lto.c
@@ -1017,7 +1017,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
   else if (TYPE_P (t1))
     compare_values (TYPE_UNSIGNED);
   if (TYPE_P (t1))
-    compare_values (TYPE_ARTIFICIAL);
+    compare_values (TYPE_WARN_EMPTY_P);
   else
     compare_values (TREE_NO_WARNING);
   compare_values (TREE_NOTHROW);
@@ -1165,6 +1165,8 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	compare_values (TYPE_NONALIASED_COMPONENT);
       if (AGGREGATE_TYPE_P (t1))
 	compare_values (TYPE_TYPELESS_STORAGE);
+      compare_values (TYPE_ARTIFICIAL);
+      compare_values (TYPE_EMPTY_P);
       compare_values (TYPE_PACKED);
       compare_values (TYPE_RESTRICT);
       compare_values (TYPE_USER_ALIGN);
diff --git gcc/target.def gcc/target.def
index 7bddb8b170a..d86607db3c4 100644
--- gcc/target.def
+++ gcc/target.def
@@ -5042,6 +5042,22 @@ DEFHOOK
  machine_mode, (int regno),
  default_get_reg_raw_mode)
 
+/* Return true if a type is an empty record.  */
+DEFHOOK
+(empty_record_p,
+ "This target hook returns true if the type is an empty record.  The default\n\
+is to return @code{false}.",
+ bool, (const_tree type),
+ hook_bool_const_tree_false)
+
+/* Warn about the change in empty class parameter passing ABI.  */
+DEFHOOK
+(warn_parameter_passing_abi,
+ "This target hook warns about the change in empty class parameter passing\n\
+ABI.",
+ void, (cumulative_args_t ca, tree type),
+ hook_void_CUMULATIVE_ARGS_tree)
+
 HOOK_VECTOR_END (calls)
 
 DEFHOOK
diff --git gcc/targhooks.c gcc/targhooks.c
index 92ecc90d4d4..2e7137bae38 100644
--- gcc/targhooks.c
+++ gcc/targhooks.c
@@ -734,6 +734,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
 }
 
 void
+hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
+				tree ATTRIBUTE_UNUSED)
+{
+}
+
+void
 default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
 			      machine_mode mode ATTRIBUTE_UNUSED,
 			      const_tree type ATTRIBUTE_UNUSED,
@@ -2084,6 +2090,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
   /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
      requires greater alignment, we must perform dynamic alignment.  */
   if (boundary > align
+      && !TYPE_EMPTY_P (type)
       && !integer_zerop (TYPE_SIZE (type)))
     {
       t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
@@ -2110,7 +2117,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
     }
 
   /* Compute the rounded size of the type.  */
-  type_size = size_in_bytes (type);
+  type_size = maybe_empty_type_size (type);
   rounded_size = round_up (type_size, align);
 
   /* Reduce rounded_size so it's sharable with the postqueue.  */
diff --git gcc/targhooks.h gcc/targhooks.h
index f60bca257f7..722608f35cc 100644
--- gcc/targhooks.h
+++ gcc/targhooks.h
@@ -134,6 +134,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
   (cumulative_args_t, machine_mode, const_tree, bool);
 extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
   (cumulative_args_t, machine_mode, tree, bool);
+extern void hook_void_CUMULATIVE_ARGS_tree
+  (cumulative_args_t, tree);
 extern const char *hook_invalid_arg_for_unprototyped_fn
   (const_tree, const_tree, const_tree);
 extern void default_function_arg_advance
diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
index e69de29bb2d..20d85ff873e 100644
--- gcc/testsuite/g++.dg/abi/empty12.C
+++ gcc/testsuite/g++.dg/abi/empty12.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty12a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty12.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty12.h
+++ gcc/testsuite/g++.dg/abi/empty12.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
index e69de29bb2d..34a25bad75d 100644
--- gcc/testsuite/g++.dg/abi/empty12a.c
+++ gcc/testsuite/g++.dg/abi/empty12a.c
@@ -0,0 +1,6 @@
+#include "empty12.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
index e69de29bb2d..0cb9a373e35 100644
--- gcc/testsuite/g++.dg/abi/empty13.C
+++ gcc/testsuite/g++.dg/abi/empty13.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-x c -fabi-version=11" }
+// { dg-additional-sources "empty13a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty13.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty13.h
+++ gcc/testsuite/g++.dg/abi/empty13.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
index e69de29bb2d..b4303a63826 100644
--- gcc/testsuite/g++.dg/abi/empty13a.c
+++ gcc/testsuite/g++.dg/abi/empty13a.c
@@ -0,0 +1,6 @@
+#include "empty13.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 == -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
index e69de29bb2d..2868d8ad3f3 100644
--- gcc/testsuite/g++.dg/abi/empty14.C
+++ gcc/testsuite/g++.dg/abi/empty14.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty14a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty14.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
index e69de29bb2d..5842279cf37 100644
--- gcc/testsuite/g++.dg/abi/empty14.h
+++ gcc/testsuite/g++.dg/abi/empty14.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[140]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
index e69de29bb2d..8b3d7800c36 100644
--- gcc/testsuite/g++.dg/abi/empty14a.c
+++ gcc/testsuite/g++.dg/abi/empty14a.c
@@ -0,0 +1,6 @@
+#include "empty14.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
index e69de29bb2d..12385f78c78 100644
--- gcc/testsuite/g++.dg/abi/empty15.C
+++ gcc/testsuite/g++.dg/abi/empty15.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty15a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty15.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
index e69de29bb2d..1c6f26f5ae8 100644
--- gcc/testsuite/g++.dg/abi/empty15.h
+++ gcc/testsuite/g++.dg/abi/empty15.h
@@ -0,0 +1,30 @@
+struct A1 {};
+struct A2 {};
+struct B1 { struct A1 a; struct A2 b; };
+struct B2 { struct A1 a; struct A2 b; };
+struct C1 { struct B1 a; struct B2 b; };
+struct C2 { struct B1 a; struct B2 b; };
+struct D1 { struct C1 a; struct C2 b; };
+struct D2 { struct C1 a; struct C2 b; };
+struct E1 { struct D1 a; struct D2 b; };
+struct E2 { struct D1 a; struct D2 b; };
+struct F1 { struct E1 a; struct E2 b; };
+struct F2 { struct E1 a; struct E2 b; };
+struct G1 { struct F1 a; struct F2 b; };
+struct G2 { struct F1 a; struct F2 b; };
+struct H1 { struct G1 a; struct G2 b; };
+struct H2 { struct G1 a; struct G2 b; };
+struct I1 { struct H1 a; struct H2 b; };
+struct I2 { struct H1 a; struct H2 b; };
+struct J1 { struct I1 a; struct I2 b; };
+struct J2 { struct I1 a; struct I2 b; };
+struct dummy { struct J1 a; struct J2 b; };
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
index e69de29bb2d..325b2c5ba09 100644
--- gcc/testsuite/g++.dg/abi/empty15a.c
+++ gcc/testsuite/g++.dg/abi/empty15a.c
@@ -0,0 +1,6 @@
+#include "empty15.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
index e69de29bb2d..1ca52f9011e 100644
--- gcc/testsuite/g++.dg/abi/empty16.C
+++ gcc/testsuite/g++.dg/abi/empty16.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty16a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty16.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
index e69de29bb2d..7552ae06576 100644
--- gcc/testsuite/g++.dg/abi/empty16.h
+++ gcc/testsuite/g++.dg/abi/empty16.h
@@ -0,0 +1,16 @@
+#ifdef __cplusplus
+struct A1 {};
+struct A2 {};
+struct dummy : A1, A2 {} ;
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
index e69de29bb2d..6cb7fbccecc 100644
--- gcc/testsuite/g++.dg/abi/empty16a.c
+++ gcc/testsuite/g++.dg/abi/empty16a.c
@@ -0,0 +1,6 @@
+#include "empty16.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
index e69de29bb2d..d386e5481af 100644
--- gcc/testsuite/g++.dg/abi/empty17.C
+++ gcc/testsuite/g++.dg/abi/empty17.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty17a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty17.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
index e69de29bb2d..9cf72baca2e 100644
--- gcc/testsuite/g++.dg/abi/empty17.h
+++ gcc/testsuite/g++.dg/abi/empty17.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 15;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 15;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
index e69de29bb2d..24408fde09c 100644
--- gcc/testsuite/g++.dg/abi/empty17a.c
+++ gcc/testsuite/g++.dg/abi/empty17a.c
@@ -0,0 +1,6 @@
+#include "empty17.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
index e69de29bb2d..be69c6a2115 100644
--- gcc/testsuite/g++.dg/abi/empty18.C
+++ gcc/testsuite/g++.dg/abi/empty18.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty18a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty18.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
index e69de29bb2d..86e7ecdd211 100644
--- gcc/testsuite/g++.dg/abi/empty18.h
+++ gcc/testsuite/g++.dg/abi/empty18.h
@@ -0,0 +1,9 @@
+struct dummy { int d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
index e69de29bb2d..902860bdc01 100644
--- gcc/testsuite/g++.dg/abi/empty18a.c
+++ gcc/testsuite/g++.dg/abi/empty18a.c
@@ -0,0 +1,6 @@
+#include "empty18.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
index e69de29bb2d..84f5b75558b 100644
--- gcc/testsuite/g++.dg/abi/empty19.C
+++ gcc/testsuite/g++.dg/abi/empty19.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty19a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty19.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
index e69de29bb2d..616b87bdd93 100644
--- gcc/testsuite/g++.dg/abi/empty19.h
+++ gcc/testsuite/g++.dg/abi/empty19.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
index e69de29bb2d..767b1eb7320 100644
--- gcc/testsuite/g++.dg/abi/empty19a.c
+++ gcc/testsuite/g++.dg/abi/empty19a.c
@@ -0,0 +1,6 @@
+#include "empty19.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
index e69de29bb2d..5022033f669 100644
--- gcc/testsuite/g++.dg/abi/empty20.C
+++ gcc/testsuite/g++.dg/abi/empty20.C
@@ -0,0 +1,19 @@
+// PR c++/60336
+// { dg-options "-Wabi=11 -O0" }
+
+struct A { };
+
+void f(A, A) { }	// No warning, trailing parms all empty
+void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+__attribute__ ((always_inline))
+inline void f(A a, int i) // No warning, always inlined
+{
+  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
+int main()
+{
+  A a;
+  f(a,a);
+  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(a,42);
+}
diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
index e69de29bb2d..3b2e3b836b1 100644
--- gcc/testsuite/g++.dg/abi/empty21.C
+++ gcc/testsuite/g++.dg/abi/empty21.C
@@ -0,0 +1,23 @@
+// PR c++/60336
+// { dg-options "-Wabi=11" }
+
+#include <stdarg.h>
+
+struct A { };
+
+void f(int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (i >= 1)
+    va_arg (ap, A);
+  if (i >= 2)
+    va_arg (ap, int);
+}
+
+int main()
+{
+  f(0);
+  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
index e69de29bb2d..f4f4a02bf31 100644
--- gcc/testsuite/g++.dg/abi/empty22.C
+++ gcc/testsuite/g++.dg/abi/empty22.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty22a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty22.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty22.h
+++ gcc/testsuite/g++.dg/abi/empty22.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
index e69de29bb2d..7606c524263 100644
--- gcc/testsuite/g++.dg/abi/empty22a.c
+++ gcc/testsuite/g++.dg/abi/empty22a.c
@@ -0,0 +1,6 @@
+#include "empty22.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
index e69de29bb2d..b97d2804529 100644
--- gcc/testsuite/g++.dg/abi/empty23.C
+++ gcc/testsuite/g++.dg/abi/empty23.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[0];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S)
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]);
+}
diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
index e69de29bb2d..81deb36ff9f 100644
--- gcc/testsuite/g++.dg/abi/empty24.C
+++ gcc/testsuite/g++.dg/abi/empty24.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S)
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]);
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
index e69de29bb2d..59447890cec 100644
--- gcc/testsuite/g++.dg/abi/pr60336-1.C
+++ gcc/testsuite/g++.dg/abi/pr60336-1.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
index e69de29bb2d..960cc2307d1 100644
--- gcc/testsuite/g++.dg/abi/pr60336-10.C
+++ gcc/testsuite/g++.dg/abi/pr60336-10.C
@@ -0,0 +1,50 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0 { };
+struct dummy1 { };
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
index e69de29bb2d..14cd6d0ff3d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-11.C
+++ gcc/testsuite/g++.dg/abi/pr60336-11.C
@@ -0,0 +1,56 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+  void bar (void);
+};
+struct dummy1
+{
+  void foo (void);
+};
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
index e69de29bb2d..09917547930 100644
--- gcc/testsuite/g++.dg/abi/pr60336-12.C
+++ gcc/testsuite/g++.dg/abi/pr60336-12.C
@@ -0,0 +1,57 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+};
+struct dummy1
+{
+  unsigned : 15;
+};
+struct dummy : dummy0, dummy1
+{
+};
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
index e69de29bb2d..1c6c3eb8f01 100644
--- gcc/testsuite/g++.dg/abi/pr60336-2.C
+++ gcc/testsuite/g++.dg/abi/pr60336-2.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -Wabi=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...) // { dg-warning "empty" }
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
index e69de29bb2d..4157e553b6b 100644
--- gcc/testsuite/g++.dg/abi/pr60336-3.C
+++ gcc/testsuite/g++.dg/abi/pr60336-3.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
index e69de29bb2d..266f67a537d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-4.C
+++ gcc/testsuite/g++.dg/abi/pr60336-4.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -fabi-version=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count == 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
index e69de29bb2d..fe838750f55 100644
--- gcc/testsuite/g++.dg/abi/pr60336-5.C
+++ gcc/testsuite/g++.dg/abi/pr60336-5.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; struct dummy j; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
index e69de29bb2d..6e08c8f06fa 100644
--- gcc/testsuite/g++.dg/abi/pr60336-6.C
+++ gcc/testsuite/g++.dg/abi/pr60336-6.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i1; struct dummy i2; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
index e69de29bb2d..3b8b8ba6f35 100644
--- gcc/testsuite/g++.dg/abi/pr60336-7.C
+++ gcc/testsuite/g++.dg/abi/pr60336-7.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i[120]; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
index e69de29bb2d..a1ffb64ef02 100644
--- gcc/testsuite/g++.dg/abi/pr60336-8.C
+++ gcc/testsuite/g++.dg/abi/pr60336-8.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct{} a[7][3]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
index e69de29bb2d..393f02b62f0 100644
--- gcc/testsuite/g++.dg/abi/pr60336-9.C
+++ gcc/testsuite/g++.dg/abi/pr60336-9.C
@@ -0,0 +1,28 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct A1 {}; struct A2 {};
+struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
+struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
+struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
+struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
+struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
+struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
+struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
+struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
+struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
+struct dummy { J1 a; J2 b; };
+
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
index e69de29bb2d..1354fc497b5 100644
--- gcc/testsuite/g++.dg/abi/pr68355.C
+++ gcc/testsuite/g++.dg/abi/pr68355.C
@@ -0,0 +1,24 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+template<typename _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr _Tp value = __v;
+  typedef _Tp value_type;
+  typedef integral_constant<_Tp, __v> type;
+  constexpr operator value_type() const { return value; }
+};
+
+typedef integral_constant<bool, true> true_type;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  true_type y;
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
index e69de29bb2d..a0a598c0029 100644
--- gcc/testsuite/g++.dg/lto/pr60336_0.C
+++ gcc/testsuite/g++.dg/lto/pr60336_0.C
@@ -0,0 +1,47 @@
+// { dg-lto-do run }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/tree-core.h gcc/tree-core.h
index f74f1453de6..aaad8d48bcf 100644
--- gcc/tree-core.h
+++ gcc/tree-core.h
@@ -1265,8 +1265,8 @@ struct GTY(()) tree_base {
            all expressions
            all decls
 
-       TYPE_ARTIFICIAL in
-           all types
+       TYPE_WARN_EMPTY_P in
+	   all types
 
    default_def_flag:
 
@@ -1531,7 +1531,9 @@ struct GTY(()) tree_type_common {
   unsigned align : 6;
   unsigned warn_if_not_align : 6;
   unsigned typeless_storage : 1;
-  unsigned spare : 18;
+  unsigned artificial_flag : 1;
+  unsigned empty_flag : 1;
+  unsigned spare : 16;
 
   alias_set_type alias_set;
   tree pointer_to;
diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
index baf0c5bf837..7087186b9a8 100644
--- gcc/tree-streamer-in.c
+++ gcc/tree-streamer-in.c
@@ -130,7 +130,7 @@ unpack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
     bp_unpack_value (bp, 1);
   TREE_ASM_WRITTEN (expr) = (unsigned) bp_unpack_value (bp, 1);
   if (TYPE_P (expr))
-    TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
+    TYPE_WARN_EMPTY_P (expr) = (unsigned) bp_unpack_value (bp, 1);
   else
     TREE_NO_WARNING (expr) = (unsigned) bp_unpack_value (bp, 1);
   TREE_NOTHROW (expr) = (unsigned) bp_unpack_value (bp, 1);
@@ -381,6 +381,8 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
   if (AGGREGATE_TYPE_P (expr))
     TYPE_TYPELESS_STORAGE (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_EMPTY_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 gcc/tree-streamer-out.c gcc/tree-streamer-out.c
index 7f52d455f5e..622a414d533 100644
--- gcc/tree-streamer-out.c
+++ gcc/tree-streamer-out.c
@@ -100,7 +100,7 @@ pack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
   bp_pack_value (bp, (TREE_CODE (expr) != SSA_NAME
 		      ? 0 : TREE_ASM_WRITTEN (expr)), 1);
   if (TYPE_P (expr))
-    bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
+    bp_pack_value (bp, TYPE_WARN_EMPTY_P (expr), 1);
   else
     bp_pack_value (bp, TREE_NO_WARNING (expr), 1);
   bp_pack_value (bp, TREE_NOTHROW (expr), 1);
@@ -330,6 +330,8 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
   if (AGGREGATE_TYPE_P (expr))
     bp_pack_value (bp, TYPE_TYPELESS_STORAGE (expr), 1);
+  bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
+  bp_pack_value (bp, TYPE_EMPTY_P (expr), 1);
   bp_pack_var_len_unsigned (bp, TYPE_PRECISION (expr));
   bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
 }
diff --git gcc/tree.c gcc/tree.c
index 28e157f5fd2..73a6ec394bc 100644
--- gcc/tree.c
+++ gcc/tree.c
@@ -3135,6 +3135,20 @@ array_type_nelts (const_tree type)
 	  ? max
 	  : fold_build2 (MINUS_EXPR, TREE_TYPE (max), max, min));
 }
+
+/* Return, as an INTEGER_CST node, the number of elements for TYPE
+   (which is an ARRAY_TYPE).  This counts only elements of the top
+   array.  */
+
+tree
+array_type_nelts_top (const_tree type)
+{
+  return fold_build2_loc (input_location,
+			  PLUS_EXPR, sizetype,
+			  array_type_nelts (type),
+			  size_one_node);
+}
+
 
 /* If arg is static -- a reference to an object in static storage -- then
    return the object.  This is not the same as the C meaning of `static'.
@@ -13811,6 +13825,87 @@ get_nonnull_args (const_tree fntype)
   return argmap;
 }
 
+/* Returns true if TYPE is a type where it and all of its subobjects
+   (recursively) are of structure, union, or array type.  */
+
+static bool
+is_empty_type (tree type)
+{
+  if (RECORD_OR_UNION_TYPE_P (type))
+    {
+      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+	{
+	  if (TREE_CODE (field) == FIELD_DECL)
+	    {
+	      tree ftype = TREE_TYPE (field);
+	      /* Don't consider struct S { struct { } a; int b[0]; };
+		 an empty type.  */
+	      if (TREE_CODE (ftype) == ARRAY_TYPE
+		  && integer_zerop (array_type_nelts_top (ftype)))
+		{
+		  tree t = DECL_CHAIN (field);
+		  bool found = false;
+		  /* See if the zero-length array is followed by another
+		     FIELD_DECL.  */
+		  while (t)
+		    {
+		      if (TREE_CODE (t) == FIELD_DECL)
+			{
+			  found = true;
+			  break;
+			}
+		      t = DECL_CHAIN (t);
+		    }
+		  if (!found)
+		    return false;
+		}
+	      if ((DECL_NAME (field) || RECORD_OR_UNION_TYPE_P (ftype))
+		  && !is_empty_type (ftype))
+		return false;
+	    }
+	}
+      return true;
+    }
+  else if (TREE_CODE (type) == ARRAY_TYPE)
+    return (integer_zerop (array_type_nelts_top (type))
+	    || is_empty_type (TREE_TYPE (type)));
+  return false;
+}
+
+/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
+   that shouldn't be passed via stack.  */
+
+bool
+is_empty_record_p (const_tree type)
+{
+  if (!abi_version_at_least (12))
+    return false;
+
+  if (type == error_mark_node)
+    return false;
+
+  if (TREE_ADDRESSABLE (type))
+    return false;
+
+  return is_empty_type (TYPE_MAIN_VARIANT (type));
+}
+
+/* Like int_size_in_bytes, but handle empty records specially.  */
+
+HOST_WIDE_INT
+int_maybe_empty_type_size (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
+}
+
+/* Like size_in_bytes, but handle empty records specially.  */
+
+tree
+maybe_empty_type_size (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
+}
+
 /* List of pointer types used to declare builtins before we have seen their
    real declaration.
 
diff --git gcc/tree.h gcc/tree.h
index 277aa919780..616f56eba95 100644
--- gcc/tree.h
+++ gcc/tree.h
@@ -696,8 +696,16 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
    emitted.  */
 #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
 
+/* Nonzero if we should warn about the change in empty class parameter
+   passing ABI.  */
+#define TYPE_WARN_EMPTY_P(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
+
+/* Nonzero if this type is "empty" according to the particular psABI.  */
+#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
+
 /* Used to indicate that this TYPE represents a compiler-generated entity.  */
-#define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
+#define TYPE_ARTIFICIAL(NODE) \
+  (TYPE_CHECK (NODE)->type_common.artificial_flag)
 
 /* In an IDENTIFIER_NODE, this means that assemble_name was called with
    this string as an argument.  */
@@ -4093,6 +4101,7 @@ extern tree build_method_type (tree, tree);
 extern tree build_offset_type (tree, tree);
 extern tree build_complex_type (tree, bool named = false);
 extern tree array_type_nelts (const_tree);
+extern tree array_type_nelts_top (const_tree);
 
 extern tree value_member (tree, tree);
 extern tree purpose_member (const_tree, tree);
@@ -5428,6 +5437,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
 
 extern bool nonnull_arg_p (const_tree);
 extern bool is_redundant_typedef (const_tree);
+extern bool is_empty_record_p (const_tree);
+extern HOST_WIDE_INT int_maybe_empty_type_size (const_tree);
+extern tree maybe_empty_type_size (const_tree);
 
 extern location_t
 set_source_range (tree expr, location_t start, location_t finish);

	Marek
Richard Biener Nov. 2, 2017, 12:21 p.m. UTC | #6
On Wed, 1 Nov 2017, Marek Polacek wrote:

> On Fri, Oct 27, 2017 at 12:46:12PM +0200, Richard Biener wrote:
> > On Fri, 27 Oct 2017, Jakub Jelinek wrote:
> > 
> > > On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
> > > > I fear it doesn't work at all with LTO (you'll always get the old ABI
> > > > if I read the patch correctly).  This is because the function
> > > > computing the size looks at flag_abi_version which isn't saved
> > > > per function / TU.
> > > > 
> > > > Similarly you'll never get the ABI warning with LTO (less of a big
> > > > deal of course) because the langhook doesn't reflect things correctly
> > > > either.
> > > > 
> > > > So...  can we instead compute whether a type is "empty" according
> > > > to the ABI early and store the result in the type (thinking of
> > > > doing this in layout_type?).  Similarly set a flag whether to
> > > > warn.  Why do you warn from backends / code emission and not
> > > > from the FEs?  Is that to avoid warnings for calls that got inlined?
> > > > Maybe the FE could set a flag on the call itself (ok, somewhat
> > > > awkward to funnel through gimple).
> > > 
> > > Warning in the FE is too early both because of the inlining, never
> > > emitted functions and because whether an empty struct is passed differently
> > > from the past matters on the backend (whether its psABI says it should be
> > > not passed at all or not).
> > > 
> > > Perhaps if empty types are rare enough it could be an artificial attribute
> > > on the type if we can't get a spare bit for that.  But computing in the FE
> > > or before free_lang_data and saving on the type whether it is empty or not
> > > seems reasonable to me.
> > 
> > There are 18 unused bits in tree_type_common if we don't want to re-use
> > any.  For the warning I first thought of setting TREE_NO_WARNING on it
> > but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
> > I'd move TYPE_ARTIFICIAL somewhere else.
> 
> All right, should be done in the below.  I've introduced two new flags,
> TYPE_EMPTY_P (says whether the type is empty according to the psABI), and
> TYPE_WARN_EMPTY_P (whether we should warn).  I've added two new fields to
> type_type_common and moved TYPE_ARTIFICIAL there; TYPE_WARN_EMPTY_P is now
> mapped to nowarning_flag.  So this should work with LTO, as demonstrated
> by g++.dg/lto/pr60336_0.C.  
> 
> Regarding LTO and -Wabi warning, I've added Optimization to c.opt so that
> we get warnings with LTO.  But as pointed out IRC, this doesn't fully work
> with cross-inlining.  I tried to do some flags merging in inline_call, but
> that didn't help, one of the problems is that warn_abi_version lives in
> c-family only.  Not sure if I'll be able to improve things here though.
> 
> Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
> Bootstrap-lto passed on x86_64-linux and ppc64-linux.

To me the tree.c stuff is_empty_type looks awfully ABI dependent
and should thus reside in i386.c near the target hook implementation?

What goes wrong if we do not introduce new int_maybe_empty_type_size
and maybe_empty_type_size but instead change int_size_in_bytes and
size_in_bytes to return 0 if TYPE_EMPTY_P ()?  If the ABI can omit
passing things assuming the size is zero should work as well, no?
Otherwise I'd really prefer seeing explicit TYPE_EMPTY_P checks
which would reduce the number of "indirect" greps one has to do when
looking for effects of TYPE_EMPTY_P.

Otherwise the middle-end/LTO parts look ok.

I'd omit the 'Optimization' change on the Wabi warning flag if it
doesn't fully give us what we want and address this as a followup.

I think 'Optimization' is also used for -help reporting and thus
could be confusing at first.

Still needs FE and target maintainer approval -- the target maintainer
wants to look at the seemingly ABI independent functions in tree.c.

Thanks,
Richard.

> 2017-11-01  Marek Polacek  <polacek@redhat.com>
> 	    H.J. Lu  <hongjiu.lu@intel.com>
> 	    Jason Merrill  <jason@redhat.com>
> 
> 	PR c++/60336
> 	PR middle-end/67239
> 	PR target/68355
> 	* c.opt (Wabi, Wabi=): Add Optimization.
> 
> 	* class.c (layout_class_type): Set TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.
> 	* cp-tree.h (array_type_nelts_top): Remove.
> 	* tree.c (array_type_nelts_top): Move to tree.c.
> 
> 	* lto.c (compare_tree_sccs_1): Compare TYPE_WARN_EMPTY_P and
> 	TYPE_EMPTY_P.
> 
> 	* calls.c (initialize_argument_information): Call
> 	warn_parameter_passing_abi target hook.
> 	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
> 	argument onto stack.
> 	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
> 	* common.opt: Update -fabi-version description.
> 	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
> 	(ix86_function_arg_advance): Skip empty records.
> 	(ix86_return_in_memory): Return false for empty types.
> 	(ix86_gimplify_va_arg): Call int_maybe_empty_type_size instead of
> 	int_size_in_bytes.
> 	(ix86_is_empty_record_p): New function.
> 	(ix86_warn_parameter_passing_abi): New function.
> 	(TARGET_EMPTY_RECORD_P): Redefine.
> 	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
> 	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
> 	* doc/tm.texi: Regenerated.
> 	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
> 	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
> 	* explow.c (hard_function_value): Call int_maybe_empty_type_size
> 	instead of int_size_in_bytes.
> 	* expr.c (copy_blkmode_to_reg): Likewise.
> 	* function.c (assign_parm_find_entry_rtl): Call
> 	warn_parameter_passing_abi target hook.
> 	(locate_and_pad_parm): Call maybe_empty_type_size instead
> 	size_in_bytes.
> 	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and
> 	TYPE_WARN_EMPTY_P.
> 	* target.def (empty_record_p, warn_parameter_passing_abi): New target
> 	hook.
> 	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
> 	(std_gimplify_va_arg_expr): Skip empty records.  Call
> 	maybe_empty_type_size instead size_in_bytes.
> 	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
> 	* tree-core.h (tree_type_common): Update comment.  Add artificial_flag
> 	and empty_flag.
> 	* tree-streamer-in.c (unpack_ts_base_value_fields): Stream
> 	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
> 	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
> 	TYPE_ARTIFICIAL.
> 	* tree-streamer-out.c (pack_ts_base_value_fields): Stream
> 	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
> 	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
> 	TYPE_ARTIFICIAL.
> 	* tree.c (array_type_nelts_top): New function.
> 	(is_empty_type): New function.
> 	(is_empty_record_p): New function.
> 	(int_maybe_empty_type_size): New function.
> 	(maybe_empty_type_size): New function.
> 	* tree.h: Define TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.  Map
> 	TYPE_ARTIFICIAL to type_common.artificial_flag.
> 	(array_type_nelts_top, is_empty_record_p, int_maybe_empty_type_size,
> 	maybe_empty_type_size): Declare.
> 
> 	* g++.dg/abi/empty12.C: New test.
> 	* g++.dg/abi/empty12.h: New test.
> 	* g++.dg/abi/empty12a.c: New test.
> 	* g++.dg/abi/empty13.C: New test.
> 	* g++.dg/abi/empty13.h: New test.
> 	* g++.dg/abi/empty13a.c: New test.
> 	* g++.dg/abi/empty14.C: New test.
> 	* g++.dg/abi/empty14.h: New test.
> 	* g++.dg/abi/empty14a.c: New test.
> 	* g++.dg/abi/empty15.C: New test.
> 	* g++.dg/abi/empty15.h: New test.
> 	* g++.dg/abi/empty15a.c: New test.
> 	* g++.dg/abi/empty16.C: New test.
> 	* g++.dg/abi/empty16.h: New test.
> 	* g++.dg/abi/empty16a.c: New test.
> 	* g++.dg/abi/empty17.C: New test.
> 	* g++.dg/abi/empty17.h: New test.
> 	* g++.dg/abi/empty17a.c: New test.
> 	* g++.dg/abi/empty18.C: New test.
> 	* g++.dg/abi/empty18.h: New test.
> 	* g++.dg/abi/empty18a.c: New test.
> 	* g++.dg/abi/empty19.C: New test.
> 	* g++.dg/abi/empty19.h: New test.
> 	* g++.dg/abi/empty19a.c: New test.
> 	* g++.dg/abi/empty20.C: New test.
> 	* g++.dg/abi/empty21.C: New test.
> 	* g++.dg/abi/empty22.C: New test.
> 	* g++.dg/abi/empty22.h: New test.
> 	* g++.dg/abi/empty22a.c: New test.
> 	* g++.dg/abi/empty23.C: New test.
> 	* g++.dg/abi/empty24.C: New test.
> 	* g++.dg/abi/pr60336-1.C: New test.
> 	* g++.dg/abi/pr60336-10.C: New test.
> 	* g++.dg/abi/pr60336-11.C: New test.
> 	* g++.dg/abi/pr60336-12.C: New test.
> 	* g++.dg/abi/pr60336-2.C: New test.
> 	* g++.dg/abi/pr60336-3.C: New test.
> 	* g++.dg/abi/pr60336-4.C: New test.
> 	* g++.dg/abi/pr60336-5.C: New test.
> 	* g++.dg/abi/pr60336-6.C: New test.
> 	* g++.dg/abi/pr60336-7.C: New test.
> 	* g++.dg/abi/pr60336-8.C: New test.
> 	* g++.dg/abi/pr60336-9.C: New test.
> 	* g++.dg/abi/pr68355.C: New test.
> 	* g++.dg/lto/pr60336_0.C: New test.
> 
> diff --git gcc/c-family/c.opt gcc/c-family/c.opt
> index dae124ac1c2..5c45d1b5062 100644
> --- gcc/c-family/c.opt
> +++ gcc/c-family/c.opt
> @@ -257,11 +257,11 @@ C ObjC C++ ObjC++ Joined Separate MissingArgError(macro name missing after %qs)
>  -U<macro>	Undefine <macro>.
>  
>  Wabi
> -C ObjC C++ ObjC++ LTO Var(warn_abi) Warning
> +C ObjC C++ ObjC++ LTO Optimization Var(warn_abi) Warning
>  Warn about things that will change when compiling with an ABI-compliant compiler.
>  
>  Wabi=
> -C ObjC C++ ObjC++ LTO Joined RejectNegative UInteger Warning
> +C ObjC C++ ObjC++ LTO Optimization Joined RejectNegative UInteger Warning
>  Warn about things that change between the current -fabi-version and the specified version.
>  
>  Wabi-tag
> diff --git gcc/calls.c gcc/calls.c
> index 3730f43c7a9..aefa4b561e6 100644
> --- gcc/calls.c
> +++ gcc/calls.c
> @@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
>        args[i].unsignedp = unsignedp;
>        args[i].mode = mode;
>  
> +      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
> +
>        args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
>  						argpos < n_named_args);
>  
> @@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	 Note that in C the default argument promotions
>  	 will prevent such mismatches.  */
>  
> -      size = GET_MODE_SIZE (arg->mode);
> +      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
> +	size = 0;
> +      else
> +	size = GET_MODE_SIZE (arg->mode);
> +
>        /* Compute how much space the push instruction will push.
>  	 On many machines, pushing a byte will advance the stack
>  	 pointer by a halfword.  */
> @@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  
>        /* This isn't already where we want it on the stack, so put it there.
>  	 This can either be done with push or copy insns.  */
> -      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
> -		      parm_align, partial, reg, used - size, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
> +      if (used
> +	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
> +			      NULL_RTX, parm_align, partial, reg, used - size,
> +			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
> +			      reg_parm_stack_space,
> +			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
>  	sibcall_failure = 1;
>  
>        /* Unless this is a partially-in-register argument, the argument is now
> @@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
>  	     for BLKmode is careful to avoid it.  */
>  	  excess = (arg->locate.size.constant
> -		    - int_size_in_bytes (TREE_TYPE (pval))
> +		    - int_maybe_empty_type_size (TREE_TYPE (pval))
>  		    + partial);
> -	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
> +	  size_rtx = expand_expr (maybe_empty_type_size (TREE_TYPE (pval)),
>  				  NULL_RTX, TYPE_MODE (sizetype),
>  				  EXPAND_NORMAL);
>  	}
> @@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	    }
>  	}
>  
> -      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> -		      parm_align, partial, reg, excess, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
> +      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
> +	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> +			parm_align, partial, reg, excess, argblock,
> +			ARGS_SIZE_RTX (arg->locate.offset),
> +			reg_parm_stack_space,
> +			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
>  
>        /* Unless this is a partially-in-register argument, the argument is now
>  	 in the stack.
> @@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
>    if (TREE_ADDRESSABLE (type))
>      return true;
>  
> +  if (TYPE_EMPTY_P (type))
> +    return false;
> +
>    /* If the padding and mode of the type is such that a copy into
>       a register would put it into the wrong part of the register.  */
>    if (mode == BLKmode
> diff --git gcc/common.opt gcc/common.opt
> index f8f2ed3db8a..28a0185f0cf 100644
> --- gcc/common.opt
> +++ gcc/common.opt
> @@ -936,7 +936,7 @@ Driver Undocumented
>  ;     Default in G++ 7.
>  ;
>  ; 12: Corrects the calling convention for classes with only deleted copy/move
> -;     constructors.
> +;     constructors and changes passing/returning of empty records.
>  ;     Default in G++ 8.
>  ;
>  ; Additional positive integers will be assigned as new versions of
> diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
> index 382635f4fc7..67dcf8aa38c 100644
> --- gcc/config/i386/i386.c
> +++ gcc/config/i386/i386.c
> @@ -7186,6 +7186,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
>    cum->force_bnd_pass = 0;
>    cum->decl = fndecl;
>  
> +  cum->warn_empty = !warn_abi || cum->stdarg;
> +  if (!cum->warn_empty && fntype)
> +    {
> +      function_args_iterator iter;
> +      tree argtype;
> +      bool seen_empty_type = false;
> +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
> +	{
> +	  if (VOID_TYPE_P (argtype))
> +	    break;
> +	  if (TYPE_EMPTY_P (argtype))
> +	    seen_empty_type = true;
> +	  else if (seen_empty_type)
> +	    {
> +	      cum->warn_empty = true;
> +	      break;
> +	    }
> +	}
> +    }
> +
>    if (!TARGET_64BIT)
>      {
>        /* If there are variable arguments, then we won't pass anything
> @@ -8327,6 +8347,10 @@ ix86_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
>    if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
>      return;
>  
> +  /* Skip empty records because they won't be passed.  */
> +  if (type && TYPE_EMPTY_P (type))
> +    return;
> +
>    if (mode == BLKmode)
>      bytes = int_size_in_bytes (type);
>    else
> @@ -9293,6 +9317,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
>    if (POINTER_BOUNDS_TYPE_P (type))
>      return false;
>  
> +  /* Empty records are never passed in memory.  */
> +  if (type && TYPE_EMPTY_P (type))
> +    return false;
> +
>    if (TARGET_64BIT)
>      {
>        if (ix86_function_type_abi (fntype) == MS_ABI)
> @@ -9873,7 +9901,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
>    indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
>    if (indirect_p)
>      type = build_pointer_type (type);
> -  size = int_size_in_bytes (type);
> +  size = int_maybe_empty_type_size (type);
>    rsize = CEIL (size, UNITS_PER_WORD);
>  
>    nat_mode = type_natural_mode (type, NULL, false);
> @@ -28759,6 +28787,44 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
>    return align;
>  }
>  
> +/* Implement TARGET_EMPTY_RECORD_P.  */
> +
> +static bool
> +ix86_is_empty_record_p (const_tree type)
> +{
> +  if (!TARGET_64BIT)
> +    return false;
> +  return is_empty_record_p (type);
> +}
> +
> +/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
> +
> +static void
> +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
> +{
> +  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
> +
> +  if (!cum->warn_empty)
> +    return;
> +
> +  if (!TYPE_EMPTY_P (type))
> +    return;
> +
> +  if (!TYPE_WARN_EMPTY_P (type))
> +    return;
> +
> +  /* If the actual size of the type is zero, then there is no change
> +     in how objects of this size are passed.  */
> +  if (int_size_in_bytes (type) == 0)
> +    return;
> +
> +  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
> +	   "changes in -fabi-version=12 (GCC 8)", type);
> +
> +  /* Only warn once.  */
> +  cum->warn_empty = false;
> +}
> +
>  /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
>     the data type, and ALIGN is the alignment that the object would
>     ordinarily have.  */
> @@ -50308,6 +50374,12 @@ ix86_run_selftests (void)
>  #undef TARGET_CONSTANT_ALIGNMENT
>  #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
>  
> +#undef TARGET_EMPTY_RECORD_P
> +#define TARGET_EMPTY_RECORD_P ix86_is_empty_record_p
> +
> +#undef TARGET_WARN_PARAMETER_PASSING_ABI
> +#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
> +
>  #if CHECKING_P
>  #undef TARGET_RUN_TARGET_SELFTESTS
>  #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
> diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
> index 837906b5169..7f5d245b568 100644
> --- gcc/config/i386/i386.h
> +++ gcc/config/i386/i386.h
> @@ -1633,6 +1633,8 @@ typedef struct ix86_args {
>    int warn_avx;			/* True when we want to warn about AVX ABI.  */
>    int warn_sse;			/* True when we want to warn about SSE ABI.  */
>    int warn_mmx;			/* True when we want to warn about MMX ABI.  */
> +  int warn_empty;		/* True when we want to warn about empty classes
> +				   passing ABI change.  */
>    int sse_regno;		/* next available sse register number */
>    int mmx_words;		/* # mmx words passed so far */
>    int mmx_nregs;		/* # mmx registers available for passing */
> diff --git gcc/cp/class.c gcc/cp/class.c
> index 9ef50657cae..7d09a06dd24 100644
> --- gcc/cp/class.c
> +++ gcc/cp/class.c
> @@ -6324,6 +6324,10 @@ layout_class_type (tree t, tree *virtuals_p)
>        && tree_int_cst_lt (sizeof_biggest_empty_class,
>  			  TYPE_SIZE_UNIT (t)))
>      sizeof_biggest_empty_class = TYPE_SIZE_UNIT (t);
> +
> +  /* Handle empty records as per the x86-64 psABI.  */
> +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
> +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
>  }
>  
>  /* Determine the "key method" for the class type indicated by TYPE,
> diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h
> index 3aefd7e40f4..984f496a459 100644
> --- gcc/cp/cp-tree.h
> +++ gcc/cp/cp-tree.h
> @@ -6916,7 +6916,6 @@ extern tree canonical_eh_spec			(tree);
>  extern tree build_exception_variant		(tree, tree);
>  extern tree bind_template_template_parm		(tree, tree);
>  extern tree array_type_nelts_total		(tree);
> -extern tree array_type_nelts_top		(tree);
>  extern tree break_out_target_exprs		(tree);
>  extern tree build_ctor_subob_ref		(tree, tree, tree);
>  extern tree replace_placeholders		(tree, tree, bool * = NULL);
> diff --git gcc/cp/tree.c gcc/cp/tree.c
> index 48d40945af3..8a878d70e34 100644
> --- gcc/cp/tree.c
> +++ gcc/cp/tree.c
> @@ -2834,19 +2834,6 @@ cxx_print_statistics (void)
>  }
>  
>  /* Return, as an INTEGER_CST node, the number of elements for TYPE
> -   (which is an ARRAY_TYPE).  This counts only elements of the top
> -   array.  */
> -
> -tree
> -array_type_nelts_top (tree type)
> -{
> -  return fold_build2_loc (input_location,
> -		      PLUS_EXPR, sizetype,
> -		      array_type_nelts (type),
> -		      size_one_node);
> -}
> -
> -/* Return, as an INTEGER_CST node, the number of elements for TYPE
>     (which is an ARRAY_TYPE).  This one is a recursive count of all
>     ARRAY_TYPEs that are clumped together.  */
>  
> diff --git gcc/doc/tm.texi gcc/doc/tm.texi
> index be8b3620684..f8ff820383b 100644
> --- gcc/doc/tm.texi
> +++ gcc/doc/tm.texi
> @@ -4548,6 +4548,16 @@ This target hook returns the mode to be used when accessing raw return registers
>  This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
>  @end deftypefn
>  
> +@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
> +This target hook returns true if the type is an empty record.  The default
> +is to return @code{false}.
> +@end deftypefn
> +
> +@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
> +This target hook warns about the change in empty class parameter passing
> +ABI.
> +@end deftypefn
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
> index d433e3a9c6b..1e896c70cee 100644
> --- gcc/doc/tm.texi.in
> +++ gcc/doc/tm.texi.in
> @@ -3437,6 +3437,10 @@ nothing when you use @option{-freg-struct-return} mode.
>  
>  @hook TARGET_GET_RAW_ARG_MODE
>  
> +@hook TARGET_EMPTY_RECORD_P
> +
> +@hook TARGET_WARN_PARAMETER_PASSING_ABI
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/explow.c gcc/explow.c
> index 662865d2808..5c864fedd1b 100644
> --- gcc/explow.c
> +++ gcc/explow.c
> @@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
>    if (REG_P (val)
>        && GET_MODE (val) == BLKmode)
>      {
> -      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
> +      unsigned HOST_WIDE_INT bytes = int_maybe_empty_type_size (valtype);
>        opt_scalar_int_mode tmpmode;
>  
>        /* int_size_in_bytes can return -1.  We don't need a check here
> diff --git gcc/expr.c gcc/expr.c
> index 496d492c9fa..337bd6de579 100644
> --- gcc/expr.c
> +++ gcc/expr.c
> @@ -2746,7 +2746,7 @@ copy_blkmode_to_reg (machine_mode mode, tree src)
>  
>    x = expand_normal (src);
>  
> -  bytes = int_size_in_bytes (TREE_TYPE (src));
> +  bytes = int_maybe_empty_type_size (TREE_TYPE (src));
>    if (bytes == 0)
>      return NULL_RTX;
>  
> diff --git gcc/function.c gcc/function.c
> index fe3d9c1bbf3..dd227993050 100644
> --- gcc/function.c
> +++ gcc/function.c
> @@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
>        return;
>      }
>  
> +  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
> +					    data->passed_type);
> +
>    entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
>  						    data->promoted_mode,
>  						    data->passed_type,
> @@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
>  
>    part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
>  
> -  sizetree
> -    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
> +  sizetree = (type
> +	      ? maybe_empty_type_size (type)
> +	      : size_int (GET_MODE_SIZE (passed_mode)));
>    where_pad = targetm.calls.function_arg_padding (passed_mode, type);
>    boundary = targetm.calls.function_arg_boundary (passed_mode, type);
>    round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
> diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
> index 554f9cc9f01..83ade46ab07 100644
> --- gcc/lto-streamer-out.c
> +++ gcc/lto-streamer-out.c
> @@ -1008,7 +1008,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
>    else if (TYPE_P (t))
>      hstate.add_flag (TYPE_UNSIGNED (t));
>    if (TYPE_P (t))
> -    hstate.add_flag (TYPE_ARTIFICIAL (t));
> +    hstate.add_flag (TYPE_WARN_EMPTY_P (t));
>    else
>      hstate.add_flag (TREE_NO_WARNING (t));
>    hstate.add_flag (TREE_NOTHROW (t));
> @@ -1166,6 +1166,8 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
>        hstate.commit_flag ();
>        hstate.add_int (TYPE_PRECISION (t));
>        hstate.add_int (TYPE_ALIGN (t));
> +      hstate.add_int (TYPE_EMPTY_P (t));
> +      hstate.add_int (TYPE_ARTIFICIAL (t));
>      }
>  
>    if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
> diff --git gcc/lto/lto.c gcc/lto/lto.c
> index 63ba73c0dbf..011f07fd593 100644
> --- gcc/lto/lto.c
> +++ gcc/lto/lto.c
> @@ -1017,7 +1017,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>    else if (TYPE_P (t1))
>      compare_values (TYPE_UNSIGNED);
>    if (TYPE_P (t1))
> -    compare_values (TYPE_ARTIFICIAL);
> +    compare_values (TYPE_WARN_EMPTY_P);
>    else
>      compare_values (TREE_NO_WARNING);
>    compare_values (TREE_NOTHROW);
> @@ -1165,6 +1165,8 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>  	compare_values (TYPE_NONALIASED_COMPONENT);
>        if (AGGREGATE_TYPE_P (t1))
>  	compare_values (TYPE_TYPELESS_STORAGE);
> +      compare_values (TYPE_ARTIFICIAL);
> +      compare_values (TYPE_EMPTY_P);
>        compare_values (TYPE_PACKED);
>        compare_values (TYPE_RESTRICT);
>        compare_values (TYPE_USER_ALIGN);
> diff --git gcc/target.def gcc/target.def
> index 7bddb8b170a..d86607db3c4 100644
> --- gcc/target.def
> +++ gcc/target.def
> @@ -5042,6 +5042,22 @@ DEFHOOK
>   machine_mode, (int regno),
>   default_get_reg_raw_mode)
>  
> +/* Return true if a type is an empty record.  */
> +DEFHOOK
> +(empty_record_p,
> + "This target hook returns true if the type is an empty record.  The default\n\
> +is to return @code{false}.",
> + bool, (const_tree type),
> + hook_bool_const_tree_false)
> +
> +/* Warn about the change in empty class parameter passing ABI.  */
> +DEFHOOK
> +(warn_parameter_passing_abi,
> + "This target hook warns about the change in empty class parameter passing\n\
> +ABI.",
> + void, (cumulative_args_t ca, tree type),
> + hook_void_CUMULATIVE_ARGS_tree)
> +
>  HOOK_VECTOR_END (calls)
>  
>  DEFHOOK
> diff --git gcc/targhooks.c gcc/targhooks.c
> index 92ecc90d4d4..2e7137bae38 100644
> --- gcc/targhooks.c
> +++ gcc/targhooks.c
> @@ -734,6 +734,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
>  }
>  
>  void
> +hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
> +				tree ATTRIBUTE_UNUSED)
> +{
> +}
> +
> +void
>  default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
>  			      machine_mode mode ATTRIBUTE_UNUSED,
>  			      const_tree type ATTRIBUTE_UNUSED,
> @@ -2084,6 +2090,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>    /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
>       requires greater alignment, we must perform dynamic alignment.  */
>    if (boundary > align
> +      && !TYPE_EMPTY_P (type)
>        && !integer_zerop (TYPE_SIZE (type)))
>      {
>        t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
> @@ -2110,7 +2117,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>      }
>  
>    /* Compute the rounded size of the type.  */
> -  type_size = size_in_bytes (type);
> +  type_size = maybe_empty_type_size (type);
>    rounded_size = round_up (type_size, align);
>  
>    /* Reduce rounded_size so it's sharable with the postqueue.  */
> diff --git gcc/targhooks.h gcc/targhooks.h
> index f60bca257f7..722608f35cc 100644
> --- gcc/targhooks.h
> +++ gcc/targhooks.h
> @@ -134,6 +134,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
>    (cumulative_args_t, machine_mode, const_tree, bool);
>  extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
>    (cumulative_args_t, machine_mode, tree, bool);
> +extern void hook_void_CUMULATIVE_ARGS_tree
> +  (cumulative_args_t, tree);
>  extern const char *hook_invalid_arg_for_unprototyped_fn
>    (const_tree, const_tree, const_tree);
>  extern void default_function_arg_advance
> diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
> index e69de29bb2d..20d85ff873e 100644
> --- gcc/testsuite/g++.dg/abi/empty12.C
> +++ gcc/testsuite/g++.dg/abi/empty12.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty12a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty12.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty12.h
> +++ gcc/testsuite/g++.dg/abi/empty12.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
> index e69de29bb2d..34a25bad75d 100644
> --- gcc/testsuite/g++.dg/abi/empty12a.c
> +++ gcc/testsuite/g++.dg/abi/empty12a.c
> @@ -0,0 +1,6 @@
> +#include "empty12.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
> index e69de29bb2d..0cb9a373e35 100644
> --- gcc/testsuite/g++.dg/abi/empty13.C
> +++ gcc/testsuite/g++.dg/abi/empty13.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-x c -fabi-version=11" }
> +// { dg-additional-sources "empty13a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty13.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty13.h
> +++ gcc/testsuite/g++.dg/abi/empty13.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
> index e69de29bb2d..b4303a63826 100644
> --- gcc/testsuite/g++.dg/abi/empty13a.c
> +++ gcc/testsuite/g++.dg/abi/empty13a.c
> @@ -0,0 +1,6 @@
> +#include "empty13.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 == -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
> index e69de29bb2d..2868d8ad3f3 100644
> --- gcc/testsuite/g++.dg/abi/empty14.C
> +++ gcc/testsuite/g++.dg/abi/empty14.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty14a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty14.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
> index e69de29bb2d..5842279cf37 100644
> --- gcc/testsuite/g++.dg/abi/empty14.h
> +++ gcc/testsuite/g++.dg/abi/empty14.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[140]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
> index e69de29bb2d..8b3d7800c36 100644
> --- gcc/testsuite/g++.dg/abi/empty14a.c
> +++ gcc/testsuite/g++.dg/abi/empty14a.c
> @@ -0,0 +1,6 @@
> +#include "empty14.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
> index e69de29bb2d..12385f78c78 100644
> --- gcc/testsuite/g++.dg/abi/empty15.C
> +++ gcc/testsuite/g++.dg/abi/empty15.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty15a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty15.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
> index e69de29bb2d..1c6f26f5ae8 100644
> --- gcc/testsuite/g++.dg/abi/empty15.h
> +++ gcc/testsuite/g++.dg/abi/empty15.h
> @@ -0,0 +1,30 @@
> +struct A1 {};
> +struct A2 {};
> +struct B1 { struct A1 a; struct A2 b; };
> +struct B2 { struct A1 a; struct A2 b; };
> +struct C1 { struct B1 a; struct B2 b; };
> +struct C2 { struct B1 a; struct B2 b; };
> +struct D1 { struct C1 a; struct C2 b; };
> +struct D2 { struct C1 a; struct C2 b; };
> +struct E1 { struct D1 a; struct D2 b; };
> +struct E2 { struct D1 a; struct D2 b; };
> +struct F1 { struct E1 a; struct E2 b; };
> +struct F2 { struct E1 a; struct E2 b; };
> +struct G1 { struct F1 a; struct F2 b; };
> +struct G2 { struct F1 a; struct F2 b; };
> +struct H1 { struct G1 a; struct G2 b; };
> +struct H2 { struct G1 a; struct G2 b; };
> +struct I1 { struct H1 a; struct H2 b; };
> +struct I2 { struct H1 a; struct H2 b; };
> +struct J1 { struct I1 a; struct I2 b; };
> +struct J2 { struct I1 a; struct I2 b; };
> +struct dummy { struct J1 a; struct J2 b; };
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
> index e69de29bb2d..325b2c5ba09 100644
> --- gcc/testsuite/g++.dg/abi/empty15a.c
> +++ gcc/testsuite/g++.dg/abi/empty15a.c
> @@ -0,0 +1,6 @@
> +#include "empty15.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
> index e69de29bb2d..1ca52f9011e 100644
> --- gcc/testsuite/g++.dg/abi/empty16.C
> +++ gcc/testsuite/g++.dg/abi/empty16.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty16a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty16.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
> index e69de29bb2d..7552ae06576 100644
> --- gcc/testsuite/g++.dg/abi/empty16.h
> +++ gcc/testsuite/g++.dg/abi/empty16.h
> @@ -0,0 +1,16 @@
> +#ifdef __cplusplus
> +struct A1 {};
> +struct A2 {};
> +struct dummy : A1, A2 {} ;
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
> index e69de29bb2d..6cb7fbccecc 100644
> --- gcc/testsuite/g++.dg/abi/empty16a.c
> +++ gcc/testsuite/g++.dg/abi/empty16a.c
> @@ -0,0 +1,6 @@
> +#include "empty16.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
> index e69de29bb2d..d386e5481af 100644
> --- gcc/testsuite/g++.dg/abi/empty17.C
> +++ gcc/testsuite/g++.dg/abi/empty17.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty17a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty17.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
> index e69de29bb2d..9cf72baca2e 100644
> --- gcc/testsuite/g++.dg/abi/empty17.h
> +++ gcc/testsuite/g++.dg/abi/empty17.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 15;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 15;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 15;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
> index e69de29bb2d..24408fde09c 100644
> --- gcc/testsuite/g++.dg/abi/empty17a.c
> +++ gcc/testsuite/g++.dg/abi/empty17a.c
> @@ -0,0 +1,6 @@
> +#include "empty17.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
> index e69de29bb2d..be69c6a2115 100644
> --- gcc/testsuite/g++.dg/abi/empty18.C
> +++ gcc/testsuite/g++.dg/abi/empty18.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty18a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty18.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
> index e69de29bb2d..86e7ecdd211 100644
> --- gcc/testsuite/g++.dg/abi/empty18.h
> +++ gcc/testsuite/g++.dg/abi/empty18.h
> @@ -0,0 +1,9 @@
> +struct dummy { int d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
> index e69de29bb2d..902860bdc01 100644
> --- gcc/testsuite/g++.dg/abi/empty18a.c
> +++ gcc/testsuite/g++.dg/abi/empty18a.c
> @@ -0,0 +1,6 @@
> +#include "empty18.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
> index e69de29bb2d..84f5b75558b 100644
> --- gcc/testsuite/g++.dg/abi/empty19.C
> +++ gcc/testsuite/g++.dg/abi/empty19.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty19a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty19.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
> index e69de29bb2d..616b87bdd93 100644
> --- gcc/testsuite/g++.dg/abi/empty19.h
> +++ gcc/testsuite/g++.dg/abi/empty19.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
> index e69de29bb2d..767b1eb7320 100644
> --- gcc/testsuite/g++.dg/abi/empty19a.c
> +++ gcc/testsuite/g++.dg/abi/empty19a.c
> @@ -0,0 +1,6 @@
> +#include "empty19.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
> index e69de29bb2d..5022033f669 100644
> --- gcc/testsuite/g++.dg/abi/empty20.C
> +++ gcc/testsuite/g++.dg/abi/empty20.C
> @@ -0,0 +1,19 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11 -O0" }
> +
> +struct A { };
> +
> +void f(A, A) { }	// No warning, trailing parms all empty
> +void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +__attribute__ ((always_inline))
> +inline void f(A a, int i) // No warning, always inlined
> +{
> +  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> +int main()
> +{
> +  A a;
> +  f(a,a);
> +  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(a,42);
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
> index e69de29bb2d..3b2e3b836b1 100644
> --- gcc/testsuite/g++.dg/abi/empty21.C
> +++ gcc/testsuite/g++.dg/abi/empty21.C
> @@ -0,0 +1,23 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct A { };
> +
> +void f(int i, ...)
> +{
> +  va_list ap;
> +  va_start (ap, i);
> +  if (i >= 1)
> +    va_arg (ap, A);
> +  if (i >= 2)
> +    va_arg (ap, int);
> +}
> +
> +int main()
> +{
> +  f(0);
> +  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
> index e69de29bb2d..f4f4a02bf31 100644
> --- gcc/testsuite/g++.dg/abi/empty22.C
> +++ gcc/testsuite/g++.dg/abi/empty22.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty22a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty22.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
> index e69de29bb2d..8d54dc74519 100644
> --- gcc/testsuite/g++.dg/abi/empty22.h
> +++ gcc/testsuite/g++.dg/abi/empty22.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 0;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 0;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 0;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
> index e69de29bb2d..7606c524263 100644
> --- gcc/testsuite/g++.dg/abi/empty22a.c
> +++ gcc/testsuite/g++.dg/abi/empty22a.c
> @@ -0,0 +1,6 @@
> +#include "empty22.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
> index e69de29bb2d..b97d2804529 100644
> --- gcc/testsuite/g++.dg/abi/empty23.C
> +++ gcc/testsuite/g++.dg/abi/empty23.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[0];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S)
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]);
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
> index e69de29bb2d..81deb36ff9f 100644
> --- gcc/testsuite/g++.dg/abi/empty24.C
> +++ gcc/testsuite/g++.dg/abi/empty24.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S)
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]);
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
> index e69de29bb2d..59447890cec 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-1.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-1.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
> index e69de29bb2d..960cc2307d1 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-10.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-10.C
> @@ -0,0 +1,50 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0 { };
> +struct dummy1 { };
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
> index e69de29bb2d..14cd6d0ff3d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-11.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-11.C
> @@ -0,0 +1,56 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +  void bar (void);
> +};
> +struct dummy1
> +{
> +  void foo (void);
> +};
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
> index e69de29bb2d..09917547930 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-12.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-12.C
> @@ -0,0 +1,57 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +};
> +struct dummy1
> +{
> +  unsigned : 15;
> +};
> +struct dummy : dummy0, dummy1
> +{
> +};
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
> index e69de29bb2d..1c6c3eb8f01 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-2.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-2.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...) // { dg-warning "empty" }
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
> index e69de29bb2d..4157e553b6b 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-3.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-3.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
> index e69de29bb2d..266f67a537d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-4.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-4.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -fabi-version=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count == 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
> index e69de29bb2d..fe838750f55 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-5.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-5.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; struct dummy j; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
> index e69de29bb2d..6e08c8f06fa 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-6.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-6.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i1; struct dummy i2; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
> index e69de29bb2d..3b8b8ba6f35 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-7.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-7.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i[120]; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
> index e69de29bb2d..a1ffb64ef02 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-8.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-8.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct{} a[7][3]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
> index e69de29bb2d..393f02b62f0 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-9.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-9.C
> @@ -0,0 +1,28 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct A1 {}; struct A2 {};
> +struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
> +struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
> +struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
> +struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
> +struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
> +struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
> +struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
> +struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
> +struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
> +struct dummy { J1 a; J2 b; };
> +
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
> index e69de29bb2d..1354fc497b5 100644
> --- gcc/testsuite/g++.dg/abi/pr68355.C
> +++ gcc/testsuite/g++.dg/abi/pr68355.C
> @@ -0,0 +1,24 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +template<typename _Tp, _Tp __v>
> +struct integral_constant
> +{
> +  static constexpr _Tp value = __v;
> +  typedef _Tp value_type;
> +  typedef integral_constant<_Tp, __v> type;
> +  constexpr operator value_type() const { return value; }
> +};
> +
> +typedef integral_constant<bool, true> true_type;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  true_type y;
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
> diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
> index e69de29bb2d..a0a598c0029 100644
> --- gcc/testsuite/g++.dg/lto/pr60336_0.C
> +++ gcc/testsuite/g++.dg/lto/pr60336_0.C
> @@ -0,0 +1,47 @@
> +// { dg-lto-do run }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/tree-core.h gcc/tree-core.h
> index f74f1453de6..aaad8d48bcf 100644
> --- gcc/tree-core.h
> +++ gcc/tree-core.h
> @@ -1265,8 +1265,8 @@ struct GTY(()) tree_base {
>             all expressions
>             all decls
>  
> -       TYPE_ARTIFICIAL in
> -           all types
> +       TYPE_WARN_EMPTY_P in
> +	   all types
>  
>     default_def_flag:
>  
> @@ -1531,7 +1531,9 @@ struct GTY(()) tree_type_common {
>    unsigned align : 6;
>    unsigned warn_if_not_align : 6;
>    unsigned typeless_storage : 1;
> -  unsigned spare : 18;
> +  unsigned artificial_flag : 1;
> +  unsigned empty_flag : 1;
> +  unsigned spare : 16;
>  
>    alias_set_type alias_set;
>    tree pointer_to;
> diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
> index baf0c5bf837..7087186b9a8 100644
> --- gcc/tree-streamer-in.c
> +++ gcc/tree-streamer-in.c
> @@ -130,7 +130,7 @@ unpack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
>      bp_unpack_value (bp, 1);
>    TREE_ASM_WRITTEN (expr) = (unsigned) bp_unpack_value (bp, 1);
>    if (TYPE_P (expr))
> -    TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
> +    TYPE_WARN_EMPTY_P (expr) = (unsigned) bp_unpack_value (bp, 1);
>    else
>      TREE_NO_WARNING (expr) = (unsigned) bp_unpack_value (bp, 1);
>    TREE_NOTHROW (expr) = (unsigned) bp_unpack_value (bp, 1);
> @@ -381,6 +381,8 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>      TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
>    if (AGGREGATE_TYPE_P (expr))
>      TYPE_TYPELESS_STORAGE (expr) = (unsigned) bp_unpack_value (bp, 1);
> +  TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
> +  TYPE_EMPTY_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 gcc/tree-streamer-out.c gcc/tree-streamer-out.c
> index 7f52d455f5e..622a414d533 100644
> --- gcc/tree-streamer-out.c
> +++ gcc/tree-streamer-out.c
> @@ -100,7 +100,7 @@ pack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
>    bp_pack_value (bp, (TREE_CODE (expr) != SSA_NAME
>  		      ? 0 : TREE_ASM_WRITTEN (expr)), 1);
>    if (TYPE_P (expr))
> -    bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
> +    bp_pack_value (bp, TYPE_WARN_EMPTY_P (expr), 1);
>    else
>      bp_pack_value (bp, TREE_NO_WARNING (expr), 1);
>    bp_pack_value (bp, TREE_NOTHROW (expr), 1);
> @@ -330,6 +330,8 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>      bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
>    if (AGGREGATE_TYPE_P (expr))
>      bp_pack_value (bp, TYPE_TYPELESS_STORAGE (expr), 1);
> +  bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
> +  bp_pack_value (bp, TYPE_EMPTY_P (expr), 1);
>    bp_pack_var_len_unsigned (bp, TYPE_PRECISION (expr));
>    bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
>  }
> diff --git gcc/tree.c gcc/tree.c
> index 28e157f5fd2..73a6ec394bc 100644
> --- gcc/tree.c
> +++ gcc/tree.c
> @@ -3135,6 +3135,20 @@ array_type_nelts (const_tree type)
>  	  ? max
>  	  : fold_build2 (MINUS_EXPR, TREE_TYPE (max), max, min));
>  }
> +
> +/* Return, as an INTEGER_CST node, the number of elements for TYPE
> +   (which is an ARRAY_TYPE).  This counts only elements of the top
> +   array.  */
> +
> +tree
> +array_type_nelts_top (const_tree type)
> +{
> +  return fold_build2_loc (input_location,
> +			  PLUS_EXPR, sizetype,
> +			  array_type_nelts (type),
> +			  size_one_node);
> +}
> +
>  
>  /* If arg is static -- a reference to an object in static storage -- then
>     return the object.  This is not the same as the C meaning of `static'.
> @@ -13811,6 +13825,87 @@ get_nonnull_args (const_tree fntype)
>    return argmap;
>  }
>  
> +/* Returns true if TYPE is a type where it and all of its subobjects
> +   (recursively) are of structure, union, or array type.  */
> +
> +static bool
> +is_empty_type (tree type)
> +{
> +  if (RECORD_OR_UNION_TYPE_P (type))
> +    {
> +      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
> +	{
> +	  if (TREE_CODE (field) == FIELD_DECL)
> +	    {
> +	      tree ftype = TREE_TYPE (field);
> +	      /* Don't consider struct S { struct { } a; int b[0]; };
> +		 an empty type.  */
> +	      if (TREE_CODE (ftype) == ARRAY_TYPE
> +		  && integer_zerop (array_type_nelts_top (ftype)))
> +		{
> +		  tree t = DECL_CHAIN (field);
> +		  bool found = false;
> +		  /* See if the zero-length array is followed by another
> +		     FIELD_DECL.  */
> +		  while (t)
> +		    {
> +		      if (TREE_CODE (t) == FIELD_DECL)
> +			{
> +			  found = true;
> +			  break;
> +			}
> +		      t = DECL_CHAIN (t);
> +		    }
> +		  if (!found)
> +		    return false;
> +		}
> +	      if ((DECL_NAME (field) || RECORD_OR_UNION_TYPE_P (ftype))
> +		  && !is_empty_type (ftype))
> +		return false;
> +	    }
> +	}
> +      return true;
> +    }
> +  else if (TREE_CODE (type) == ARRAY_TYPE)
> +    return (integer_zerop (array_type_nelts_top (type))
> +	    || is_empty_type (TREE_TYPE (type)));
> +  return false;
> +}
> +
> +/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
> +   that shouldn't be passed via stack.  */
> +
> +bool
> +is_empty_record_p (const_tree type)
> +{
> +  if (!abi_version_at_least (12))
> +    return false;
> +
> +  if (type == error_mark_node)
> +    return false;
> +
> +  if (TREE_ADDRESSABLE (type))
> +    return false;
> +
> +  return is_empty_type (TYPE_MAIN_VARIANT (type));
> +}
> +
> +/* Like int_size_in_bytes, but handle empty records specially.  */
> +
> +HOST_WIDE_INT
> +int_maybe_empty_type_size (const_tree type)
> +{
> +  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
> +}
> +
> +/* Like size_in_bytes, but handle empty records specially.  */
> +
> +tree
> +maybe_empty_type_size (const_tree type)
> +{
> +  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
> +}
> +
>  /* List of pointer types used to declare builtins before we have seen their
>     real declaration.
>  
> diff --git gcc/tree.h gcc/tree.h
> index 277aa919780..616f56eba95 100644
> --- gcc/tree.h
> +++ gcc/tree.h
> @@ -696,8 +696,16 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
>     emitted.  */
>  #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
>  
> +/* Nonzero if we should warn about the change in empty class parameter
> +   passing ABI.  */
> +#define TYPE_WARN_EMPTY_P(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
> +
> +/* Nonzero if this type is "empty" according to the particular psABI.  */
> +#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
> +
>  /* Used to indicate that this TYPE represents a compiler-generated entity.  */
> -#define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
> +#define TYPE_ARTIFICIAL(NODE) \
> +  (TYPE_CHECK (NODE)->type_common.artificial_flag)
>  
>  /* In an IDENTIFIER_NODE, this means that assemble_name was called with
>     this string as an argument.  */
> @@ -4093,6 +4101,7 @@ extern tree build_method_type (tree, tree);
>  extern tree build_offset_type (tree, tree);
>  extern tree build_complex_type (tree, bool named = false);
>  extern tree array_type_nelts (const_tree);
> +extern tree array_type_nelts_top (const_tree);
>  
>  extern tree value_member (tree, tree);
>  extern tree purpose_member (const_tree, tree);
> @@ -5428,6 +5437,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
>  
>  extern bool nonnull_arg_p (const_tree);
>  extern bool is_redundant_typedef (const_tree);
> +extern bool is_empty_record_p (const_tree);
> +extern HOST_WIDE_INT int_maybe_empty_type_size (const_tree);
> +extern tree maybe_empty_type_size (const_tree);
>  
>  extern location_t
>  set_source_range (tree expr, location_t start, location_t finish);
> 
> 	Marek
> 
>
Jason Merrill Nov. 2, 2017, 1:53 p.m. UTC | #7
On Thu, Nov 2, 2017 at 8:21 AM, Richard Biener <rguenther@suse.de> wrote:
> On Wed, 1 Nov 2017, Marek Polacek wrote:
>
>> On Fri, Oct 27, 2017 at 12:46:12PM +0200, Richard Biener wrote:
>> > On Fri, 27 Oct 2017, Jakub Jelinek wrote:
>> >
>> > > On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
>> > > > I fear it doesn't work at all with LTO (you'll always get the old ABI
>> > > > if I read the patch correctly).  This is because the function
>> > > > computing the size looks at flag_abi_version which isn't saved
>> > > > per function / TU.
>> > > >
>> > > > Similarly you'll never get the ABI warning with LTO (less of a big
>> > > > deal of course) because the langhook doesn't reflect things correctly
>> > > > either.
>> > > >
>> > > > So...  can we instead compute whether a type is "empty" according
>> > > > to the ABI early and store the result in the type (thinking of
>> > > > doing this in layout_type?).  Similarly set a flag whether to
>> > > > warn.  Why do you warn from backends / code emission and not
>> > > > from the FEs?  Is that to avoid warnings for calls that got inlined?
>> > > > Maybe the FE could set a flag on the call itself (ok, somewhat
>> > > > awkward to funnel through gimple).
>> > >
>> > > Warning in the FE is too early both because of the inlining, never
>> > > emitted functions and because whether an empty struct is passed differently
>> > > from the past matters on the backend (whether its psABI says it should be
>> > > not passed at all or not).
>> > >
>> > > Perhaps if empty types are rare enough it could be an artificial attribute
>> > > on the type if we can't get a spare bit for that.  But computing in the FE
>> > > or before free_lang_data and saving on the type whether it is empty or not
>> > > seems reasonable to me.
>> >
>> > There are 18 unused bits in tree_type_common if we don't want to re-use
>> > any.  For the warning I first thought of setting TREE_NO_WARNING on it
>> > but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
>> > I'd move TYPE_ARTIFICIAL somewhere else.
>>
>> All right, should be done in the below.  I've introduced two new flags,
>> TYPE_EMPTY_P (says whether the type is empty according to the psABI), and
>> TYPE_WARN_EMPTY_P (whether we should warn).  I've added two new fields to
>> type_type_common and moved TYPE_ARTIFICIAL there; TYPE_WARN_EMPTY_P is now
>> mapped to nowarning_flag.  So this should work with LTO, as demonstrated
>> by g++.dg/lto/pr60336_0.C.
>>
>> Regarding LTO and -Wabi warning, I've added Optimization to c.opt so that
>> we get warnings with LTO.  But as pointed out IRC, this doesn't fully work
>> with cross-inlining.  I tried to do some flags merging in inline_call, but
>> that didn't help, one of the problems is that warn_abi_version lives in
>> c-family only.  Not sure if I'll be able to improve things here though.
>>
>> Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
>> Bootstrap-lto passed on x86_64-linux and ppc64-linux.
>
> To me the tree.c stuff is_empty_type looks awfully ABI dependent
> and should thus reside in i386.c near the target hook implementation?

I think there should be a default version in common code, to hopefully
be shared by all targets that want this behavior.

> What goes wrong if we do not introduce new int_maybe_empty_type_size
> and maybe_empty_type_size but instead change int_size_in_bytes and
> size_in_bytes to return 0 if TYPE_EMPTY_P ()?  If the ABI can omit
> passing things assuming the size is zero should work as well, no?

We need to distinguish between size in general and size for calling
convention purposes, but the function names should mention the calling
convention rather than "maybe_empty".  Maybe something like
"arg_size_in_bytes"?

> Otherwise I'd really prefer seeing explicit TYPE_EMPTY_P checks
> which would reduce the number of "indirect" greps one has to do when
> looking for effects of TYPE_EMPTY_P.

Hmm, yes, I was hoping we could encapsulate this in target code, but
needing these flags for LTO messes that up; if we can't have full
encapsulation, maybe we want less?

> Still needs FE and target maintainer approval -- the target maintainer
> wants to look at the seemingly ABI independent functions in tree.c.

Instead of moving array_type_nelts_top to tree.c, you can use
integer_minus_onep (array_type_nelts (ftype)).

I'm still not sure why you want to consider a type with a flexible
array member non-empty.  Is this to avoid changing the C ABI?  I'm
surprised it's even allowed to pass/return by value a struct with a
flexible array member, since that doesn't copy the contents of the
array.

Jason
Marek Polacek Nov. 2, 2017, 2:53 p.m. UTC | #8
On Thu, Nov 02, 2017 at 01:21:17PM +0100, Richard Biener wrote:
> On Wed, 1 Nov 2017, Marek Polacek wrote:
> 
> > On Fri, Oct 27, 2017 at 12:46:12PM +0200, Richard Biener wrote:
> > > On Fri, 27 Oct 2017, Jakub Jelinek wrote:
> > > 
> > > > On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
> > > > > I fear it doesn't work at all with LTO (you'll always get the old ABI
> > > > > if I read the patch correctly).  This is because the function
> > > > > computing the size looks at flag_abi_version which isn't saved
> > > > > per function / TU.
> > > > > 
> > > > > Similarly you'll never get the ABI warning with LTO (less of a big
> > > > > deal of course) because the langhook doesn't reflect things correctly
> > > > > either.
> > > > > 
> > > > > So...  can we instead compute whether a type is "empty" according
> > > > > to the ABI early and store the result in the type (thinking of
> > > > > doing this in layout_type?).  Similarly set a flag whether to
> > > > > warn.  Why do you warn from backends / code emission and not
> > > > > from the FEs?  Is that to avoid warnings for calls that got inlined?
> > > > > Maybe the FE could set a flag on the call itself (ok, somewhat
> > > > > awkward to funnel through gimple).
> > > > 
> > > > Warning in the FE is too early both because of the inlining, never
> > > > emitted functions and because whether an empty struct is passed differently
> > > > from the past matters on the backend (whether its psABI says it should be
> > > > not passed at all or not).
> > > > 
> > > > Perhaps if empty types are rare enough it could be an artificial attribute
> > > > on the type if we can't get a spare bit for that.  But computing in the FE
> > > > or before free_lang_data and saving on the type whether it is empty or not
> > > > seems reasonable to me.
> > > 
> > > There are 18 unused bits in tree_type_common if we don't want to re-use
> > > any.  For the warning I first thought of setting TREE_NO_WARNING on it
> > > but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
> > > I'd move TYPE_ARTIFICIAL somewhere else.
> > 
> > All right, should be done in the below.  I've introduced two new flags,
> > TYPE_EMPTY_P (says whether the type is empty according to the psABI), and
> > TYPE_WARN_EMPTY_P (whether we should warn).  I've added two new fields to
> > type_type_common and moved TYPE_ARTIFICIAL there; TYPE_WARN_EMPTY_P is now
> > mapped to nowarning_flag.  So this should work with LTO, as demonstrated
> > by g++.dg/lto/pr60336_0.C.  
> > 
> > Regarding LTO and -Wabi warning, I've added Optimization to c.opt so that
> > we get warnings with LTO.  But as pointed out IRC, this doesn't fully work
> > with cross-inlining.  I tried to do some flags merging in inline_call, but
> > that didn't help, one of the problems is that warn_abi_version lives in
> > c-family only.  Not sure if I'll be able to improve things here though.
> > 
> > Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
> > Bootstrap-lto passed on x86_64-linux and ppc64-linux.
> 
> To me the tree.c stuff is_empty_type looks awfully ABI dependent
> and should thus reside in i386.c near the target hook implementation?
> 
> What goes wrong if we do not introduce new int_maybe_empty_type_size
> and maybe_empty_type_size but instead change int_size_in_bytes and
> size_in_bytes to return 0 if TYPE_EMPTY_P ()?  If the ABI can omit
> passing things assuming the size is zero should work as well, no?
> Otherwise I'd really prefer seeing explicit TYPE_EMPTY_P checks
> which would reduce the number of "indirect" greps one has to do when
> looking for effects of TYPE_EMPTY_P.
 
More on this in another mail.

> Otherwise the middle-end/LTO parts look ok.

Thanks.

> I'd omit the 'Optimization' change on the Wabi warning flag if it
> doesn't fully give us what we want and address this as a followup.
>
> I think 'Optimization' is also used for -help reporting and thus
> could be confusing at first.

Done.  Alternatively we could change lto_handle_option to handle 
even OPT_Wabi_ case.  The current code looks dubious anyway: in
the OPT_Wabi case we change warn_psabi instead of warn_abi...

	Marek
Marek Polacek Nov. 2, 2017, 5:08 p.m. UTC | #9
On Thu, Nov 02, 2017 at 09:53:33AM -0400, Jason Merrill wrote:
> On Thu, Nov 2, 2017 at 8:21 AM, Richard Biener <rguenther@suse.de> wrote:
> > On Wed, 1 Nov 2017, Marek Polacek wrote:
> >
> >> On Fri, Oct 27, 2017 at 12:46:12PM +0200, Richard Biener wrote:
> >> > On Fri, 27 Oct 2017, Jakub Jelinek wrote:
> >> >
> >> > > On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
> >> > > > I fear it doesn't work at all with LTO (you'll always get the old ABI
> >> > > > if I read the patch correctly).  This is because the function
> >> > > > computing the size looks at flag_abi_version which isn't saved
> >> > > > per function / TU.
> >> > > >
> >> > > > Similarly you'll never get the ABI warning with LTO (less of a big
> >> > > > deal of course) because the langhook doesn't reflect things correctly
> >> > > > either.
> >> > > >
> >> > > > So...  can we instead compute whether a type is "empty" according
> >> > > > to the ABI early and store the result in the type (thinking of
> >> > > > doing this in layout_type?).  Similarly set a flag whether to
> >> > > > warn.  Why do you warn from backends / code emission and not
> >> > > > from the FEs?  Is that to avoid warnings for calls that got inlined?
> >> > > > Maybe the FE could set a flag on the call itself (ok, somewhat
> >> > > > awkward to funnel through gimple).
> >> > >
> >> > > Warning in the FE is too early both because of the inlining, never
> >> > > emitted functions and because whether an empty struct is passed differently
> >> > > from the past matters on the backend (whether its psABI says it should be
> >> > > not passed at all or not).
> >> > >
> >> > > Perhaps if empty types are rare enough it could be an artificial attribute
> >> > > on the type if we can't get a spare bit for that.  But computing in the FE
> >> > > or before free_lang_data and saving on the type whether it is empty or not
> >> > > seems reasonable to me.
> >> >
> >> > There are 18 unused bits in tree_type_common if we don't want to re-use
> >> > any.  For the warning I first thought of setting TREE_NO_WARNING on it
> >> > but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
> >> > I'd move TYPE_ARTIFICIAL somewhere else.
> >>
> >> All right, should be done in the below.  I've introduced two new flags,
> >> TYPE_EMPTY_P (says whether the type is empty according to the psABI), and
> >> TYPE_WARN_EMPTY_P (whether we should warn).  I've added two new fields to
> >> type_type_common and moved TYPE_ARTIFICIAL there; TYPE_WARN_EMPTY_P is now
> >> mapped to nowarning_flag.  So this should work with LTO, as demonstrated
> >> by g++.dg/lto/pr60336_0.C.
> >>
> >> Regarding LTO and -Wabi warning, I've added Optimization to c.opt so that
> >> we get warnings with LTO.  But as pointed out IRC, this doesn't fully work
> >> with cross-inlining.  I tried to do some flags merging in inline_call, but
> >> that didn't help, one of the problems is that warn_abi_version lives in
> >> c-family only.  Not sure if I'll be able to improve things here though.
> >>
> >> Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
> >> Bootstrap-lto passed on x86_64-linux and ppc64-linux.
> >
> > To me the tree.c stuff is_empty_type looks awfully ABI dependent
> > and should thus reside in i386.c near the target hook implementation?
> 
> I think there should be a default version in common code, to hopefully
> be shared by all targets that want this behavior.

That was my thinking too.  Also, I don't see anything target-specific in
is_empty_type.

> > What goes wrong if we do not introduce new int_maybe_empty_type_size
> > and maybe_empty_type_size but instead change int_size_in_bytes and
> > size_in_bytes to return 0 if TYPE_EMPTY_P ()?  If the ABI can omit
> > passing things assuming the size is zero should work as well, no?
> 
> We need to distinguish between size in general and size for calling
> convention purposes, but the function names should mention the calling
> convention rather than "maybe_empty".  Maybe something like
> "arg_size_in_bytes"?

Sure, that works for me.  Changed.

> > Otherwise I'd really prefer seeing explicit TYPE_EMPTY_P checks
> > which would reduce the number of "indirect" greps one has to do when
> > looking for effects of TYPE_EMPTY_P.
> 
> Hmm, yes, I was hoping we could encapsulate this in target code, but
> needing these flags for LTO messes that up; if we can't have full
> encapsulation, maybe we want less?

So I don't know what to do here, I could go either way.  The explicit
checks with ?: striked me as ugly but I can go back on that.

> > Still needs FE and target maintainer approval -- the target maintainer
> > wants to look at the seemingly ABI independent functions in tree.c.
> 
> Instead of moving array_type_nelts_top to tree.c, you can use
> integer_minus_onep (array_type_nelts (ftype)).

Okay, done.

> I'm still not sure why you want to consider a type with a flexible
> array member non-empty.  Is this to avoid changing the C ABI?  I'm
> surprised it's even allowed to pass/return by value a struct with a
> flexible array member, since that doesn't copy the contents of the
> array.

For one thing I thought we should be consistent in treating these two
structs, regarding being empty:

struct S { struct { } a; int a[0]; };
struct T { struct { } a; int a[]; };

Without the (ugly, I know) special handling in is_empty_type, only T would be
considered empty.  And that would mean that the g++.dg/abi/empty23.C test
crashes (the abort triggers).  The problem seems to be that when we're passing
a pointer to an empty struct, it got turned into a null pointer.  Having an array
of empty structs is weird, but it's part of struct-layout-1 test.

And yeah, when passing such a struct by value, the flexible array member is
ignored.  The ABI of passing struct with a flexible array member has changed in
GCC 4.4.

Various testing pending.

2017-11-02  Marek Polacek  <polacek@redhat.com>
	    H.J. Lu  <hongjiu.lu@intel.com>
	    Jason Merrill  <jason@redhat.com>

	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* class.c (layout_class_type): Set TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.

	* lto.c (compare_tree_sccs_1): Compare TYPE_WARN_EMPTY_P and
	TYPE_EMPTY_P.

	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_function_arg_advance): Skip empty records.
	(ix86_return_in_memory): Return false for empty types.
	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
	int_size_in_bytes.
	(ix86_is_empty_record_p): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* explow.c (hard_function_value): Call arg_int_size_in_bytes
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (assign_parm_find_entry_rtl): Call
	warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call arg size_in_bytes instead
	size_in_bytes.
	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and
	TYPE_WARN_EMPTY_P.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hook.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	arg_size_in_bytes instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree-core.h (tree_type_common): Update comment.  Add artificial_flag
	and empty_flag.
	* tree-streamer-in.c (unpack_ts_base_value_fields): Stream
	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
	TYPE_ARTIFICIAL.
	* tree-streamer-out.c (pack_ts_base_value_fields): Stream
	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
	TYPE_ARTIFICIAL.
	* tree.c (is_empty_type): New function.
	(is_empty_record_p): New function.
	(arg_int_size_in_bytes): New function.
	(arg_size_in_bytes): New function.
	* tree.h: Define TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.  Map
	TYPE_ARTIFICIAL to type_common.artificial_flag.
	(is_empty_record_p, int_maybe_empty_type_size,
	maybe_empty_type_size): Declare.

	* g++.dg/abi/empty12.C: New test.
	* g++.dg/abi/empty12.h: New test.
	* g++.dg/abi/empty12a.c: New test.
	* g++.dg/abi/empty13.C: New test.
	* g++.dg/abi/empty13.h: New test.
	* g++.dg/abi/empty13a.c: New test.
	* g++.dg/abi/empty14.C: New test.
	* g++.dg/abi/empty14.h: New test.
	* g++.dg/abi/empty14a.c: New test.
	* g++.dg/abi/empty15.C: New test.
	* g++.dg/abi/empty15.h: New test.
	* g++.dg/abi/empty15a.c: New test.
	* g++.dg/abi/empty16.C: New test.
	* g++.dg/abi/empty16.h: New test.
	* g++.dg/abi/empty16a.c: New test.
	* g++.dg/abi/empty17.C: New test.
	* g++.dg/abi/empty17.h: New test.
	* g++.dg/abi/empty17a.c: New test.
	* g++.dg/abi/empty18.C: New test.
	* g++.dg/abi/empty18.h: New test.
	* g++.dg/abi/empty18a.c: New test.
	* g++.dg/abi/empty19.C: New test.
	* g++.dg/abi/empty19.h: New test.
	* g++.dg/abi/empty19a.c: New test.
	* g++.dg/abi/empty20.C: New test.
	* g++.dg/abi/empty21.C: New test.
	* g++.dg/abi/empty22.C: New test.
	* g++.dg/abi/empty22.h: New test.
	* g++.dg/abi/empty22a.c: New test.
	* g++.dg/abi/empty23.C: New test.
	* g++.dg/abi/empty24.C: New test.
	* g++.dg/abi/pr60336-1.C: New test.
	* g++.dg/abi/pr60336-10.C: New test.
	* g++.dg/abi/pr60336-11.C: New test.
	* g++.dg/abi/pr60336-12.C: New test.
	* g++.dg/abi/pr60336-2.C: New test.
	* g++.dg/abi/pr60336-3.C: New test.
	* g++.dg/abi/pr60336-4.C: New test.
	* g++.dg/abi/pr60336-5.C: New test.
	* g++.dg/abi/pr60336-6.C: New test.
	* g++.dg/abi/pr60336-7.C: New test.
	* g++.dg/abi/pr60336-8.C: New test.
	* g++.dg/abi/pr60336-9.C: New test.
	* g++.dg/abi/pr68355.C: New test.
	* g++.dg/lto/pr60336_0.C: New test.

diff --git gcc/calls.c gcc/calls.c
index 3730f43c7a9..f9a6a5cce13 100644
--- gcc/calls.c
+++ gcc/calls.c
@@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       args[i].unsignedp = unsignedp;
       args[i].mode = mode;
 
+      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
+
       args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
 						argpos < n_named_args);
 
@@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	 Note that in C the default argument promotions
 	 will prevent such mismatches.  */
 
-      size = GET_MODE_SIZE (arg->mode);
+      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
+	size = 0;
+      else
+	size = GET_MODE_SIZE (arg->mode);
+
       /* Compute how much space the push instruction will push.
 	 On many machines, pushing a byte will advance the stack
 	 pointer by a halfword.  */
@@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 
       /* This isn't already where we want it on the stack, so put it there.
 	 This can either be done with push or copy insns.  */
-      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
-		      parm_align, partial, reg, used - size, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
+      if (used
+	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
+			      NULL_RTX, parm_align, partial, reg, used - size,
+			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
+			      reg_parm_stack_space,
+			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
 	sibcall_failure = 1;
 
       /* Unless this is a partially-in-register argument, the argument is now
@@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
 	     for BLKmode is careful to avoid it.  */
 	  excess = (arg->locate.size.constant
-		    - int_size_in_bytes (TREE_TYPE (pval))
+		    - arg_int_size_in_bytes (TREE_TYPE (pval))
 		    + partial);
-	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
+	  size_rtx = expand_expr (arg_size_in_bytes (TREE_TYPE (pval)),
 				  NULL_RTX, TYPE_MODE (sizetype),
 				  EXPAND_NORMAL);
 	}
@@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	    }
 	}
 
-      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
-		      parm_align, partial, reg, excess, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
+      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
+	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
+			parm_align, partial, reg, excess, argblock,
+			ARGS_SIZE_RTX (arg->locate.offset),
+			reg_parm_stack_space,
+			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
 
       /* Unless this is a partially-in-register argument, the argument is now
 	 in the stack.
@@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
   if (TREE_ADDRESSABLE (type))
     return true;
 
+  if (TYPE_EMPTY_P (type))
+    return false;
+
   /* If the padding and mode of the type is such that a copy into
      a register would put it into the wrong part of the register.  */
   if (mode == BLKmode
diff --git gcc/common.opt gcc/common.opt
index f8f2ed3db8a..28a0185f0cf 100644
--- gcc/common.opt
+++ gcc/common.opt
@@ -936,7 +936,7 @@ Driver Undocumented
 ;     Default in G++ 7.
 ;
 ; 12: Corrects the calling convention for classes with only deleted copy/move
-;     constructors.
+;     constructors and changes passing/returning of empty records.
 ;     Default in G++ 8.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
index 29678722226..f4aed73b107 100644
--- gcc/config/i386/i386.c
+++ gcc/config/i386/i386.c
@@ -7186,6 +7186,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->force_bnd_pass = 0;
   cum->decl = fndecl;
 
+  cum->warn_empty = !warn_abi || cum->stdarg;
+  if (!cum->warn_empty && fntype)
+    {
+      function_args_iterator iter;
+      tree argtype;
+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+	{
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  if (TYPE_EMPTY_P (argtype))
+	    seen_empty_type = true;
+	  else if (seen_empty_type)
+	    {
+	      cum->warn_empty = true;
+	      break;
+	    }
+	}
+    }
+
   if (!TARGET_64BIT)
     {
       /* If there are variable arguments, then we won't pass anything
@@ -8327,6 +8347,10 @@ ix86_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
   if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
     return;
 
+  /* Skip empty records because they won't be passed.  */
+  if (type && TYPE_EMPTY_P (type))
+    return;
+
   if (mode == BLKmode)
     bytes = int_size_in_bytes (type);
   else
@@ -9293,6 +9317,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
   if (POINTER_BOUNDS_TYPE_P (type))
     return false;
 
+  /* Empty records are never passed in memory.  */
+  if (type && TYPE_EMPTY_P (type))
+    return false;
+
   if (TARGET_64BIT)
     {
       if (ix86_function_type_abi (fntype) == MS_ABI)
@@ -9873,7 +9901,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
   indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
   if (indirect_p)
     type = build_pointer_type (type);
-  size = int_size_in_bytes (type);
+  size = arg_int_size_in_bytes (type);
   rsize = CEIL (size, UNITS_PER_WORD);
 
   nat_mode = type_natural_mode (type, NULL, false);
@@ -28753,6 +28781,44 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Implement TARGET_EMPTY_RECORD_P.  */
+
+static bool
+ix86_is_empty_record_p (const_tree type)
+{
+  if (!TARGET_64BIT)
+    return false;
+  return is_empty_record_p (type);
+}
+
+/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
+
+static void
+ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
+{
+  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
+
+  if (!cum->warn_empty)
+    return;
+
+  if (!TYPE_EMPTY_P (type))
+    return;
+
+  if (!TYPE_WARN_EMPTY_P (type))
+    return;
+
+  /* If the actual size of the type is zero, then there is no change
+     in how objects of this size are passed.  */
+  if (int_size_in_bytes (type) == 0)
+    return;
+
+  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
+	   "changes in -fabi-version=12 (GCC 8)", type);
+
+  /* Only warn once.  */
+  cum->warn_empty = false;
+}
+
 /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
    the data type, and ALIGN is the alignment that the object would
    ordinarily have.  */
@@ -50300,6 +50366,12 @@ ix86_run_selftests (void)
 #undef TARGET_CONSTANT_ALIGNMENT
 #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
 
+#undef TARGET_EMPTY_RECORD_P
+#define TARGET_EMPTY_RECORD_P ix86_is_empty_record_p
+
+#undef TARGET_WARN_PARAMETER_PASSING_ABI
+#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
+
 #if CHECKING_P
 #undef TARGET_RUN_TARGET_SELFTESTS
 #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
index 837906b5169..7f5d245b568 100644
--- gcc/config/i386/i386.h
+++ gcc/config/i386/i386.h
@@ -1633,6 +1633,8 @@ typedef struct ix86_args {
   int warn_avx;			/* True when we want to warn about AVX ABI.  */
   int warn_sse;			/* True when we want to warn about SSE ABI.  */
   int warn_mmx;			/* True when we want to warn about MMX ABI.  */
+  int warn_empty;		/* True when we want to warn about empty classes
+				   passing ABI change.  */
   int sse_regno;		/* next available sse register number */
   int mmx_words;		/* # mmx words passed so far */
   int mmx_nregs;		/* # mmx registers available for passing */
diff --git gcc/cp/class.c gcc/cp/class.c
index 98e62c6ad45..1d5ce7bc98b 100644
--- gcc/cp/class.c
+++ gcc/cp/class.c
@@ -6327,6 +6327,10 @@ layout_class_type (tree t, tree *virtuals_p)
       && tree_int_cst_lt (sizeof_biggest_empty_class,
 			  TYPE_SIZE_UNIT (t)))
     sizeof_biggest_empty_class = TYPE_SIZE_UNIT (t);
+
+  /* Handle empty records as per the x86-64 psABI.  */
+  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
+  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
 }
 
 /* Determine the "key method" for the class type indicated by TYPE,
diff --git gcc/doc/tm.texi gcc/doc/tm.texi
index 72606f53f1c..f16e73c31b1 100644
--- gcc/doc/tm.texi
+++ gcc/doc/tm.texi
@@ -4559,6 +4559,16 @@ This target hook returns the mode to be used when accessing raw return registers
 This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
+This target hook returns true if the type is an empty record.  The default
+is to return @code{false}.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
+This target hook warns about the change in empty class parameter passing
+ABI.
+@end deftypefn
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
index e7d4ada290f..39f6fcaaa11 100644
--- gcc/doc/tm.texi.in
+++ gcc/doc/tm.texi.in
@@ -3439,6 +3439,10 @@ nothing when you use @option{-freg-struct-return} mode.
 
 @hook TARGET_GET_RAW_ARG_MODE
 
+@hook TARGET_EMPTY_RECORD_P
+
+@hook TARGET_WARN_PARAMETER_PASSING_ABI
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/explow.c gcc/explow.c
index 662865d2808..72c2e01a44e 100644
--- gcc/explow.c
+++ gcc/explow.c
@@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
   if (REG_P (val)
       && GET_MODE (val) == BLKmode)
     {
-      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
+      unsigned HOST_WIDE_INT bytes = arg_int_size_in_bytes (valtype);
       opt_scalar_int_mode tmpmode;
 
       /* int_size_in_bytes can return -1.  We don't need a check here
diff --git gcc/expr.c gcc/expr.c
index 649a057e43d..60971066c7a 100644
--- gcc/expr.c
+++ gcc/expr.c
@@ -2749,7 +2749,7 @@ copy_blkmode_to_reg (machine_mode mode_in, tree src)
 
   x = expand_normal (src);
 
-  bytes = int_size_in_bytes (TREE_TYPE (src));
+  bytes = arg_int_size_in_bytes (TREE_TYPE (src));
   if (bytes == 0)
     return NULL_RTX;
 
diff --git gcc/function.c gcc/function.c
index fe3d9c1bbf3..671a49fa76f 100644
--- gcc/function.c
+++ gcc/function.c
@@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
       return;
     }
 
+  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
+					    data->passed_type);
+
   entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
 						    data->promoted_mode,
 						    data->passed_type,
@@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
 
   part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
 
-  sizetree
-    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
+  sizetree = (type
+	      ? arg_size_in_bytes (type)
+	      : size_int (GET_MODE_SIZE (passed_mode)));
   where_pad = targetm.calls.function_arg_padding (passed_mode, type);
   boundary = targetm.calls.function_arg_boundary (passed_mode, type);
   round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
index 554f9cc9f01..83ade46ab07 100644
--- gcc/lto-streamer-out.c
+++ gcc/lto-streamer-out.c
@@ -1008,7 +1008,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
   else if (TYPE_P (t))
     hstate.add_flag (TYPE_UNSIGNED (t));
   if (TYPE_P (t))
-    hstate.add_flag (TYPE_ARTIFICIAL (t));
+    hstate.add_flag (TYPE_WARN_EMPTY_P (t));
   else
     hstate.add_flag (TREE_NO_WARNING (t));
   hstate.add_flag (TREE_NOTHROW (t));
@@ -1166,6 +1166,8 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
       hstate.commit_flag ();
       hstate.add_int (TYPE_PRECISION (t));
       hstate.add_int (TYPE_ALIGN (t));
+      hstate.add_int (TYPE_EMPTY_P (t));
+      hstate.add_int (TYPE_ARTIFICIAL (t));
     }
 
   if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
diff --git gcc/lto/lto.c gcc/lto/lto.c
index 63ba73c0dbf..011f07fd593 100644
--- gcc/lto/lto.c
+++ gcc/lto/lto.c
@@ -1017,7 +1017,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
   else if (TYPE_P (t1))
     compare_values (TYPE_UNSIGNED);
   if (TYPE_P (t1))
-    compare_values (TYPE_ARTIFICIAL);
+    compare_values (TYPE_WARN_EMPTY_P);
   else
     compare_values (TREE_NO_WARNING);
   compare_values (TREE_NOTHROW);
@@ -1165,6 +1165,8 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	compare_values (TYPE_NONALIASED_COMPONENT);
       if (AGGREGATE_TYPE_P (t1))
 	compare_values (TYPE_TYPELESS_STORAGE);
+      compare_values (TYPE_ARTIFICIAL);
+      compare_values (TYPE_EMPTY_P);
       compare_values (TYPE_PACKED);
       compare_values (TYPE_RESTRICT);
       compare_values (TYPE_USER_ALIGN);
diff --git gcc/target.def gcc/target.def
index 577dad8fe86..81aedee80d9 100644
--- gcc/target.def
+++ gcc/target.def
@@ -5055,6 +5055,22 @@ DEFHOOK
  fixed_size_mode, (int regno),
  default_get_reg_raw_mode)
 
+/* Return true if a type is an empty record.  */
+DEFHOOK
+(empty_record_p,
+ "This target hook returns true if the type is an empty record.  The default\n\
+is to return @code{false}.",
+ bool, (const_tree type),
+ hook_bool_const_tree_false)
+
+/* Warn about the change in empty class parameter passing ABI.  */
+DEFHOOK
+(warn_parameter_passing_abi,
+ "This target hook warns about the change in empty class parameter passing\n\
+ABI.",
+ void, (cumulative_args_t ca, tree type),
+ hook_void_CUMULATIVE_ARGS_tree)
+
 HOOK_VECTOR_END (calls)
 
 DEFHOOK
diff --git gcc/targhooks.c gcc/targhooks.c
index dad1e109d23..0edc57b0a15 100644
--- gcc/targhooks.c
+++ gcc/targhooks.c
@@ -756,6 +756,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
 }
 
 void
+hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
+				tree ATTRIBUTE_UNUSED)
+{
+}
+
+void
 default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
 			      machine_mode mode ATTRIBUTE_UNUSED,
 			      const_tree type ATTRIBUTE_UNUSED,
@@ -2108,6 +2114,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
   /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
      requires greater alignment, we must perform dynamic alignment.  */
   if (boundary > align
+      && !TYPE_EMPTY_P (type)
       && !integer_zerop (TYPE_SIZE (type)))
     {
       t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
@@ -2134,7 +2141,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
     }
 
   /* Compute the rounded size of the type.  */
-  type_size = size_in_bytes (type);
+  type_size = arg_size_in_bytes (type);
   rounded_size = round_up (type_size, align);
 
   /* Reduce rounded_size so it's sharable with the postqueue.  */
diff --git gcc/targhooks.h gcc/targhooks.h
index 15bbf5cdf24..e431934cd60 100644
--- gcc/targhooks.h
+++ gcc/targhooks.h
@@ -135,6 +135,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
   (cumulative_args_t, machine_mode, const_tree, bool);
 extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
   (cumulative_args_t, machine_mode, tree, bool);
+extern void hook_void_CUMULATIVE_ARGS_tree
+  (cumulative_args_t, tree);
 extern const char *hook_invalid_arg_for_unprototyped_fn
   (const_tree, const_tree, const_tree);
 extern void default_function_arg_advance
diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
index e69de29bb2d..20d85ff873e 100644
--- gcc/testsuite/g++.dg/abi/empty12.C
+++ gcc/testsuite/g++.dg/abi/empty12.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty12a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty12.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty12.h
+++ gcc/testsuite/g++.dg/abi/empty12.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
index e69de29bb2d..34a25bad75d 100644
--- gcc/testsuite/g++.dg/abi/empty12a.c
+++ gcc/testsuite/g++.dg/abi/empty12a.c
@@ -0,0 +1,6 @@
+#include "empty12.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
index e69de29bb2d..0cb9a373e35 100644
--- gcc/testsuite/g++.dg/abi/empty13.C
+++ gcc/testsuite/g++.dg/abi/empty13.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-x c -fabi-version=11" }
+// { dg-additional-sources "empty13a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty13.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty13.h
+++ gcc/testsuite/g++.dg/abi/empty13.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
index e69de29bb2d..b4303a63826 100644
--- gcc/testsuite/g++.dg/abi/empty13a.c
+++ gcc/testsuite/g++.dg/abi/empty13a.c
@@ -0,0 +1,6 @@
+#include "empty13.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 == -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
index e69de29bb2d..2868d8ad3f3 100644
--- gcc/testsuite/g++.dg/abi/empty14.C
+++ gcc/testsuite/g++.dg/abi/empty14.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty14a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty14.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
index e69de29bb2d..5842279cf37 100644
--- gcc/testsuite/g++.dg/abi/empty14.h
+++ gcc/testsuite/g++.dg/abi/empty14.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[140]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
index e69de29bb2d..8b3d7800c36 100644
--- gcc/testsuite/g++.dg/abi/empty14a.c
+++ gcc/testsuite/g++.dg/abi/empty14a.c
@@ -0,0 +1,6 @@
+#include "empty14.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
index e69de29bb2d..12385f78c78 100644
--- gcc/testsuite/g++.dg/abi/empty15.C
+++ gcc/testsuite/g++.dg/abi/empty15.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty15a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty15.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
index e69de29bb2d..1c6f26f5ae8 100644
--- gcc/testsuite/g++.dg/abi/empty15.h
+++ gcc/testsuite/g++.dg/abi/empty15.h
@@ -0,0 +1,30 @@
+struct A1 {};
+struct A2 {};
+struct B1 { struct A1 a; struct A2 b; };
+struct B2 { struct A1 a; struct A2 b; };
+struct C1 { struct B1 a; struct B2 b; };
+struct C2 { struct B1 a; struct B2 b; };
+struct D1 { struct C1 a; struct C2 b; };
+struct D2 { struct C1 a; struct C2 b; };
+struct E1 { struct D1 a; struct D2 b; };
+struct E2 { struct D1 a; struct D2 b; };
+struct F1 { struct E1 a; struct E2 b; };
+struct F2 { struct E1 a; struct E2 b; };
+struct G1 { struct F1 a; struct F2 b; };
+struct G2 { struct F1 a; struct F2 b; };
+struct H1 { struct G1 a; struct G2 b; };
+struct H2 { struct G1 a; struct G2 b; };
+struct I1 { struct H1 a; struct H2 b; };
+struct I2 { struct H1 a; struct H2 b; };
+struct J1 { struct I1 a; struct I2 b; };
+struct J2 { struct I1 a; struct I2 b; };
+struct dummy { struct J1 a; struct J2 b; };
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
index e69de29bb2d..325b2c5ba09 100644
--- gcc/testsuite/g++.dg/abi/empty15a.c
+++ gcc/testsuite/g++.dg/abi/empty15a.c
@@ -0,0 +1,6 @@
+#include "empty15.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
index e69de29bb2d..1ca52f9011e 100644
--- gcc/testsuite/g++.dg/abi/empty16.C
+++ gcc/testsuite/g++.dg/abi/empty16.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty16a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty16.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
index e69de29bb2d..7552ae06576 100644
--- gcc/testsuite/g++.dg/abi/empty16.h
+++ gcc/testsuite/g++.dg/abi/empty16.h
@@ -0,0 +1,16 @@
+#ifdef __cplusplus
+struct A1 {};
+struct A2 {};
+struct dummy : A1, A2 {} ;
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
index e69de29bb2d..6cb7fbccecc 100644
--- gcc/testsuite/g++.dg/abi/empty16a.c
+++ gcc/testsuite/g++.dg/abi/empty16a.c
@@ -0,0 +1,6 @@
+#include "empty16.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
index e69de29bb2d..d386e5481af 100644
--- gcc/testsuite/g++.dg/abi/empty17.C
+++ gcc/testsuite/g++.dg/abi/empty17.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty17a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty17.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
index e69de29bb2d..9cf72baca2e 100644
--- gcc/testsuite/g++.dg/abi/empty17.h
+++ gcc/testsuite/g++.dg/abi/empty17.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 15;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 15;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
index e69de29bb2d..24408fde09c 100644
--- gcc/testsuite/g++.dg/abi/empty17a.c
+++ gcc/testsuite/g++.dg/abi/empty17a.c
@@ -0,0 +1,6 @@
+#include "empty17.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
index e69de29bb2d..be69c6a2115 100644
--- gcc/testsuite/g++.dg/abi/empty18.C
+++ gcc/testsuite/g++.dg/abi/empty18.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty18a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty18.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
index e69de29bb2d..86e7ecdd211 100644
--- gcc/testsuite/g++.dg/abi/empty18.h
+++ gcc/testsuite/g++.dg/abi/empty18.h
@@ -0,0 +1,9 @@
+struct dummy { int d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
index e69de29bb2d..902860bdc01 100644
--- gcc/testsuite/g++.dg/abi/empty18a.c
+++ gcc/testsuite/g++.dg/abi/empty18a.c
@@ -0,0 +1,6 @@
+#include "empty18.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
index e69de29bb2d..84f5b75558b 100644
--- gcc/testsuite/g++.dg/abi/empty19.C
+++ gcc/testsuite/g++.dg/abi/empty19.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty19a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty19.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
index e69de29bb2d..616b87bdd93 100644
--- gcc/testsuite/g++.dg/abi/empty19.h
+++ gcc/testsuite/g++.dg/abi/empty19.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
index e69de29bb2d..767b1eb7320 100644
--- gcc/testsuite/g++.dg/abi/empty19a.c
+++ gcc/testsuite/g++.dg/abi/empty19a.c
@@ -0,0 +1,6 @@
+#include "empty19.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
index e69de29bb2d..5022033f669 100644
--- gcc/testsuite/g++.dg/abi/empty20.C
+++ gcc/testsuite/g++.dg/abi/empty20.C
@@ -0,0 +1,19 @@
+// PR c++/60336
+// { dg-options "-Wabi=11 -O0" }
+
+struct A { };
+
+void f(A, A) { }	// No warning, trailing parms all empty
+void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+__attribute__ ((always_inline))
+inline void f(A a, int i) // No warning, always inlined
+{
+  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
+int main()
+{
+  A a;
+  f(a,a);
+  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(a,42);
+}
diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
index e69de29bb2d..3b2e3b836b1 100644
--- gcc/testsuite/g++.dg/abi/empty21.C
+++ gcc/testsuite/g++.dg/abi/empty21.C
@@ -0,0 +1,23 @@
+// PR c++/60336
+// { dg-options "-Wabi=11" }
+
+#include <stdarg.h>
+
+struct A { };
+
+void f(int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (i >= 1)
+    va_arg (ap, A);
+  if (i >= 2)
+    va_arg (ap, int);
+}
+
+int main()
+{
+  f(0);
+  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
index e69de29bb2d..f4f4a02bf31 100644
--- gcc/testsuite/g++.dg/abi/empty22.C
+++ gcc/testsuite/g++.dg/abi/empty22.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty22a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty22.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty22.h
+++ gcc/testsuite/g++.dg/abi/empty22.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
index e69de29bb2d..7606c524263 100644
--- gcc/testsuite/g++.dg/abi/empty22a.c
+++ gcc/testsuite/g++.dg/abi/empty22a.c
@@ -0,0 +1,6 @@
+#include "empty22.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
index e69de29bb2d..b97d2804529 100644
--- gcc/testsuite/g++.dg/abi/empty23.C
+++ gcc/testsuite/g++.dg/abi/empty23.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[0];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S)
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]);
+}
diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
index e69de29bb2d..81deb36ff9f 100644
--- gcc/testsuite/g++.dg/abi/empty24.C
+++ gcc/testsuite/g++.dg/abi/empty24.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S)
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]);
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
index e69de29bb2d..59447890cec 100644
--- gcc/testsuite/g++.dg/abi/pr60336-1.C
+++ gcc/testsuite/g++.dg/abi/pr60336-1.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
index e69de29bb2d..960cc2307d1 100644
--- gcc/testsuite/g++.dg/abi/pr60336-10.C
+++ gcc/testsuite/g++.dg/abi/pr60336-10.C
@@ -0,0 +1,50 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0 { };
+struct dummy1 { };
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
index e69de29bb2d..14cd6d0ff3d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-11.C
+++ gcc/testsuite/g++.dg/abi/pr60336-11.C
@@ -0,0 +1,56 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+  void bar (void);
+};
+struct dummy1
+{
+  void foo (void);
+};
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
index e69de29bb2d..09917547930 100644
--- gcc/testsuite/g++.dg/abi/pr60336-12.C
+++ gcc/testsuite/g++.dg/abi/pr60336-12.C
@@ -0,0 +1,57 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+};
+struct dummy1
+{
+  unsigned : 15;
+};
+struct dummy : dummy0, dummy1
+{
+};
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
index e69de29bb2d..1c6c3eb8f01 100644
--- gcc/testsuite/g++.dg/abi/pr60336-2.C
+++ gcc/testsuite/g++.dg/abi/pr60336-2.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -Wabi=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...) // { dg-warning "empty" }
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
index e69de29bb2d..4157e553b6b 100644
--- gcc/testsuite/g++.dg/abi/pr60336-3.C
+++ gcc/testsuite/g++.dg/abi/pr60336-3.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
index e69de29bb2d..266f67a537d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-4.C
+++ gcc/testsuite/g++.dg/abi/pr60336-4.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -fabi-version=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count == 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
index e69de29bb2d..fe838750f55 100644
--- gcc/testsuite/g++.dg/abi/pr60336-5.C
+++ gcc/testsuite/g++.dg/abi/pr60336-5.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; struct dummy j; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
index e69de29bb2d..6e08c8f06fa 100644
--- gcc/testsuite/g++.dg/abi/pr60336-6.C
+++ gcc/testsuite/g++.dg/abi/pr60336-6.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i1; struct dummy i2; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
index e69de29bb2d..3b8b8ba6f35 100644
--- gcc/testsuite/g++.dg/abi/pr60336-7.C
+++ gcc/testsuite/g++.dg/abi/pr60336-7.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i[120]; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
index e69de29bb2d..a1ffb64ef02 100644
--- gcc/testsuite/g++.dg/abi/pr60336-8.C
+++ gcc/testsuite/g++.dg/abi/pr60336-8.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct{} a[7][3]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
index e69de29bb2d..393f02b62f0 100644
--- gcc/testsuite/g++.dg/abi/pr60336-9.C
+++ gcc/testsuite/g++.dg/abi/pr60336-9.C
@@ -0,0 +1,28 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct A1 {}; struct A2 {};
+struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
+struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
+struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
+struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
+struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
+struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
+struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
+struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
+struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
+struct dummy { J1 a; J2 b; };
+
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
index e69de29bb2d..1354fc497b5 100644
--- gcc/testsuite/g++.dg/abi/pr68355.C
+++ gcc/testsuite/g++.dg/abi/pr68355.C
@@ -0,0 +1,24 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+template<typename _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr _Tp value = __v;
+  typedef _Tp value_type;
+  typedef integral_constant<_Tp, __v> type;
+  constexpr operator value_type() const { return value; }
+};
+
+typedef integral_constant<bool, true> true_type;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  true_type y;
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
index e69de29bb2d..a0a598c0029 100644
--- gcc/testsuite/g++.dg/lto/pr60336_0.C
+++ gcc/testsuite/g++.dg/lto/pr60336_0.C
@@ -0,0 +1,47 @@
+// { dg-lto-do run }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/tree-core.h gcc/tree-core.h
index f74f1453de6..aaad8d48bcf 100644
--- gcc/tree-core.h
+++ gcc/tree-core.h
@@ -1265,8 +1265,8 @@ struct GTY(()) tree_base {
            all expressions
            all decls
 
-       TYPE_ARTIFICIAL in
-           all types
+       TYPE_WARN_EMPTY_P in
+	   all types
 
    default_def_flag:
 
@@ -1531,7 +1531,9 @@ struct GTY(()) tree_type_common {
   unsigned align : 6;
   unsigned warn_if_not_align : 6;
   unsigned typeless_storage : 1;
-  unsigned spare : 18;
+  unsigned artificial_flag : 1;
+  unsigned empty_flag : 1;
+  unsigned spare : 16;
 
   alias_set_type alias_set;
   tree pointer_to;
diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
index baf0c5bf837..7087186b9a8 100644
--- gcc/tree-streamer-in.c
+++ gcc/tree-streamer-in.c
@@ -130,7 +130,7 @@ unpack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
     bp_unpack_value (bp, 1);
   TREE_ASM_WRITTEN (expr) = (unsigned) bp_unpack_value (bp, 1);
   if (TYPE_P (expr))
-    TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
+    TYPE_WARN_EMPTY_P (expr) = (unsigned) bp_unpack_value (bp, 1);
   else
     TREE_NO_WARNING (expr) = (unsigned) bp_unpack_value (bp, 1);
   TREE_NOTHROW (expr) = (unsigned) bp_unpack_value (bp, 1);
@@ -381,6 +381,8 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
   if (AGGREGATE_TYPE_P (expr))
     TYPE_TYPELESS_STORAGE (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_EMPTY_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 gcc/tree-streamer-out.c gcc/tree-streamer-out.c
index 7f52d455f5e..622a414d533 100644
--- gcc/tree-streamer-out.c
+++ gcc/tree-streamer-out.c
@@ -100,7 +100,7 @@ pack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
   bp_pack_value (bp, (TREE_CODE (expr) != SSA_NAME
 		      ? 0 : TREE_ASM_WRITTEN (expr)), 1);
   if (TYPE_P (expr))
-    bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
+    bp_pack_value (bp, TYPE_WARN_EMPTY_P (expr), 1);
   else
     bp_pack_value (bp, TREE_NO_WARNING (expr), 1);
   bp_pack_value (bp, TREE_NOTHROW (expr), 1);
@@ -330,6 +330,8 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
   if (AGGREGATE_TYPE_P (expr))
     bp_pack_value (bp, TYPE_TYPELESS_STORAGE (expr), 1);
+  bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
+  bp_pack_value (bp, TYPE_EMPTY_P (expr), 1);
   bp_pack_var_len_unsigned (bp, TYPE_PRECISION (expr));
   bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
 }
diff --git gcc/tree.c gcc/tree.c
index 28e157f5fd2..7bd89ac389a 100644
--- gcc/tree.c
+++ gcc/tree.c
@@ -13811,6 +13812,87 @@ get_nonnull_args (const_tree fntype)
   return argmap;
 }
 
+/* Returns true if TYPE is a type where it and all of its subobjects
+   (recursively) are of structure, union, or array type.  */
+
+static bool
+is_empty_type (tree type)
+{
+  if (RECORD_OR_UNION_TYPE_P (type))
+    {
+      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+	{
+	  if (TREE_CODE (field) == FIELD_DECL)
+	    {
+	      tree ftype = TREE_TYPE (field);
+	      /* Don't consider struct S { struct { } a; int b[0]; };
+		 an empty type.  */
+	      if (TREE_CODE (ftype) == ARRAY_TYPE
+		  && integer_minus_onep (array_type_nelts (ftype)))
+		{
+		  tree t = DECL_CHAIN (field);
+		  bool found = false;
+		  /* See if the zero-length array is followed by another
+		     FIELD_DECL.  */
+		  while (t)
+		    {
+		      if (TREE_CODE (t) == FIELD_DECL)
+			{
+			  found = true;
+			  break;
+			}
+		      t = DECL_CHAIN (t);
+		    }
+		  if (!found)
+		    return false;
+		}
+	      if ((DECL_NAME (field) || RECORD_OR_UNION_TYPE_P (ftype))
+		  && !is_empty_type (ftype))
+		return false;
+	    }
+	}
+      return true;
+    }
+  else if (TREE_CODE (type) == ARRAY_TYPE)
+    return (integer_minus_onep (array_type_nelts (type))
+	    || is_empty_type (TREE_TYPE (type)));
+  return false;
+}
+
+/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
+   that shouldn't be passed via stack.  */
+
+bool
+is_empty_record_p (const_tree type)
+{
+  if (!abi_version_at_least (12))
+    return false;
+
+  if (type == error_mark_node)
+    return false;
+
+  if (TREE_ADDRESSABLE (type))
+    return false;
+
+  return is_empty_type (TYPE_MAIN_VARIANT (type));
+}
+
+/* Like int_size_in_bytes, but handle empty records specially.  */
+
+HOST_WIDE_INT
+arg_int_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
+}
+
+/* Like size_in_bytes, but handle empty records specially.  */
+
+tree
+arg_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
+}
+
 /* List of pointer types used to declare builtins before we have seen their
    real declaration.
 
diff --git gcc/tree.h gcc/tree.h
index 277aa919780..577d2b01468 100644
--- gcc/tree.h
+++ gcc/tree.h
@@ -696,8 +696,16 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
    emitted.  */
 #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
 
+/* Nonzero if we should warn about the change in empty class parameter
+   passing ABI.  */
+#define TYPE_WARN_EMPTY_P(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
+
+/* Nonzero if this type is "empty" according to the particular psABI.  */
+#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
+
 /* Used to indicate that this TYPE represents a compiler-generated entity.  */
-#define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
+#define TYPE_ARTIFICIAL(NODE) \
+  (TYPE_CHECK (NODE)->type_common.artificial_flag)
 
 /* In an IDENTIFIER_NODE, this means that assemble_name was called with
    this string as an argument.  */
@@ -5428,6 +5436,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
 
 extern bool nonnull_arg_p (const_tree);
 extern bool is_redundant_typedef (const_tree);
+extern bool is_empty_record_p (const_tree);
+extern HOST_WIDE_INT arg_int_size_in_bytes (const_tree);
+extern tree arg_size_in_bytes (const_tree);
 
 extern location_t
 set_source_range (tree expr, location_t start, location_t finish);

	Marek
Jason Merrill Nov. 2, 2017, 6:50 p.m. UTC | #10
On Thu, Nov 2, 2017 at 1:08 PM, Marek Polacek <polacek@redhat.com> wrote:
> On Thu, Nov 02, 2017 at 09:53:33AM -0400, Jason Merrill wrote:
>> On Thu, Nov 2, 2017 at 8:21 AM, Richard Biener <rguenther@suse.de> wrote:
>> > On Wed, 1 Nov 2017, Marek Polacek wrote:
>> >
>> >> On Fri, Oct 27, 2017 at 12:46:12PM +0200, Richard Biener wrote:
>> >> > On Fri, 27 Oct 2017, Jakub Jelinek wrote:
>> >> >
>> >> > > On Fri, Oct 27, 2017 at 12:31:46PM +0200, Richard Biener wrote:
>> >> > > > I fear it doesn't work at all with LTO (you'll always get the old ABI
>> >> > > > if I read the patch correctly).  This is because the function
>> >> > > > computing the size looks at flag_abi_version which isn't saved
>> >> > > > per function / TU.
>> >> > > >
>> >> > > > Similarly you'll never get the ABI warning with LTO (less of a big
>> >> > > > deal of course) because the langhook doesn't reflect things correctly
>> >> > > > either.
>> >> > > >
>> >> > > > So...  can we instead compute whether a type is "empty" according
>> >> > > > to the ABI early and store the result in the type (thinking of
>> >> > > > doing this in layout_type?).  Similarly set a flag whether to
>> >> > > > warn.  Why do you warn from backends / code emission and not
>> >> > > > from the FEs?  Is that to avoid warnings for calls that got inlined?
>> >> > > > Maybe the FE could set a flag on the call itself (ok, somewhat
>> >> > > > awkward to funnel through gimple).
>> >> > >
>> >> > > Warning in the FE is too early both because of the inlining, never
>> >> > > emitted functions and because whether an empty struct is passed differently
>> >> > > from the past matters on the backend (whether its psABI says it should be
>> >> > > not passed at all or not).
>> >> > >
>> >> > > Perhaps if empty types are rare enough it could be an artificial attribute
>> >> > > on the type if we can't get a spare bit for that.  But computing in the FE
>> >> > > or before free_lang_data and saving on the type whether it is empty or not
>> >> > > seems reasonable to me.
>> >> >
>> >> > There are 18 unused bits in tree_type_common if we don't want to re-use
>> >> > any.  For the warning I first thought of setting TREE_NO_WARNING on it
>> >> > but that bit is used already.  OTOH given the "fit" of TREE_NO_WARNING
>> >> > I'd move TYPE_ARTIFICIAL somewhere else.
>> >>
>> >> All right, should be done in the below.  I've introduced two new flags,
>> >> TYPE_EMPTY_P (says whether the type is empty according to the psABI), and
>> >> TYPE_WARN_EMPTY_P (whether we should warn).  I've added two new fields to
>> >> type_type_common and moved TYPE_ARTIFICIAL there; TYPE_WARN_EMPTY_P is now
>> >> mapped to nowarning_flag.  So this should work with LTO, as demonstrated
>> >> by g++.dg/lto/pr60336_0.C.
>> >>
>> >> Regarding LTO and -Wabi warning, I've added Optimization to c.opt so that
>> >> we get warnings with LTO.  But as pointed out IRC, this doesn't fully work
>> >> with cross-inlining.  I tried to do some flags merging in inline_call, but
>> >> that didn't help, one of the problems is that warn_abi_version lives in
>> >> c-family only.  Not sure if I'll be able to improve things here though.
>> >>
>> >> Bootstrapped/regtested on x86_64-linux, ppc64-linux, and aarch64-linux.
>> >> Bootstrap-lto passed on x86_64-linux and ppc64-linux.
>> >
>> > To me the tree.c stuff is_empty_type looks awfully ABI dependent
>> > and should thus reside in i386.c near the target hook implementation?
>>
>> I think there should be a default version in common code, to hopefully
>> be shared by all targets that want this behavior.
>
> That was my thinking too.  Also, I don't see anything target-specific in
> is_empty_type.

We probably want to call them something like default_is_empty_type and
default_is_empty_record, though.

>> > What goes wrong if we do not introduce new int_maybe_empty_type_size
>> > and maybe_empty_type_size but instead change int_size_in_bytes and
>> > size_in_bytes to return 0 if TYPE_EMPTY_P ()?  If the ABI can omit
>> > passing things assuming the size is zero should work as well, no?
>>
>> We need to distinguish between size in general and size for calling
>> convention purposes, but the function names should mention the calling
>> convention rather than "maybe_empty".  Maybe something like
>> "arg_size_in_bytes"?
>
> Sure, that works for me.  Changed.
>
>> > Otherwise I'd really prefer seeing explicit TYPE_EMPTY_P checks
>> > which would reduce the number of "indirect" greps one has to do when
>> > looking for effects of TYPE_EMPTY_P.
>>
>> Hmm, yes, I was hoping we could encapsulate this in target code, but
>> needing these flags for LTO messes that up; if we can't have full
>> encapsulation, maybe we want less?
>
> So I don't know what to do here, I could go either way.  The explicit
> checks with ?: striked me as ugly but I can go back on that.
>
>> > Still needs FE and target maintainer approval -- the target maintainer
>> > wants to look at the seemingly ABI independent functions in tree.c.
>>
>> Instead of moving array_type_nelts_top to tree.c, you can use
>> integer_minus_onep (array_type_nelts (ftype)).
>
> Okay, done.
>
>> I'm still not sure why you want to consider a type with a flexible
>> array member non-empty.  Is this to avoid changing the C ABI?  I'm
>> surprised it's even allowed to pass/return by value a struct with a
>> flexible array member, since that doesn't copy the contents of the
>> array.
>
> For one thing I thought we should be consistent in treating these two
> structs, regarding being empty:
>
> struct S { struct { } a; int a[0]; };
> struct T { struct { } a; int a[]; };
>
> Without the (ugly, I know) special handling in is_empty_type, only T would be
> considered empty.

Why would T be considered empty?  I don't see anything in
is_empty_type that would cause that.

> And that would mean that the g++.dg/abi/empty23.C test
> crashes (the abort triggers).  The problem seems to be that when we're passing
> a pointer to an empty struct, it got turned into a null pointer.

That seems like a bug that needs fixing regardless of whether we end
up considering S and T empty.

> And yeah, when passing such a struct by value, the flexible array member is
> ignored.  The ABI of passing struct with a flexible array member has changed in
> GCC 4.4.

Can we add a warning about such pass/return (as a separate patch)?

> +  if (TREE_ADDRESSABLE (type))
> +    return false;

I think we want to abort in this case, as the front end should have
turned this into an invisible reference already.

Jason
Marek Polacek Nov. 3, 2017, 1:55 p.m. UTC | #11
On Thu, Nov 02, 2017 at 02:50:17PM -0400, Jason Merrill wrote:
> We probably want to call them something like default_is_empty_type and
> default_is_empty_record, though.

Done.

> > For one thing I thought we should be consistent in treating these two
> > structs, regarding being empty:
> >
> > struct S { struct { } a; int a[0]; };
> > struct T { struct { } a; int a[]; };
> >
> > Without the (ugly, I know) special handling in is_empty_type, only T would be
> > considered empty.
> 
> Why would T be considered empty?  I don't see anything in
> is_empty_type that would cause that.
 
Sorry, I meant *non*-empty :(.  I've changed the code so that both S and T
are considered empty, because...

> > And that would mean that the g++.dg/abi/empty23.C test
> > crashes (the abort triggers).  The problem seems to be that when we're passing
> > a pointer to an empty struct, it got turned into a null pointer.
> 
> That seems like a bug that needs fixing regardless of whether we end
> up considering S and T empty.

...I fixed this.  The problem was that ix86_function_arg_advance tried to
skip parameters with empty types:

+  /* Skip empty records because they won't be passed.  */
+  if (type && targetm.calls.empty_record_p (type))
+    return;

but I think that's wrong.  Thus the testcase passes now.

> > And yeah, when passing such a struct by value, the flexible array member is
> > ignored.  The ABI of passing struct with a flexible array member has changed in
> > GCC 4.4.
> 
> Can we add a warning about such pass/return (as a separate patch)?

Oh, the warning is already there.

> > +  if (TREE_ADDRESSABLE (type))
> > +    return false;
> 
> I think we want to abort in this case, as the front end should have
> turned this into an invisible reference already.

Done.  Thanks,

Bootstrap/regtest running on x86_64-linux and ppc64-linux.

2017-11-03  Marek Polacek  <polacek@redhat.com>
	    H.J. Lu  <hongjiu.lu@intel.com>
	    Jason Merrill  <jason@redhat.com>

	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* class.c (layout_class_type): Set TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.

	* lto.c (compare_tree_sccs_1): Compare TYPE_WARN_EMPTY_P and
	TYPE_EMPTY_P.

	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_return_in_memory): Return false for empty types.
	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
	int_size_in_bytes.
	(ix86_is_empty_record): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* explow.c (hard_function_value): Call arg_int_size_in_bytes
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (assign_parm_find_entry_rtl): Call
	warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call arg size_in_bytes instead
	size_in_bytes.
	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and
	TYPE_WARN_EMPTY_P.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hook.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	arg_size_in_bytes instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree-core.h (tree_type_common): Update comment.  Add artificial_flag
	and empty_flag.
	* tree-streamer-in.c (unpack_ts_base_value_fields): Stream
	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
	TYPE_ARTIFICIAL.
	* tree-streamer-out.c (pack_ts_base_value_fields): Stream
	TYPE_WARN_EMPTY_P instead of TYPE_ARTIFICIAL.
	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P and
	TYPE_ARTIFICIAL.
	* tree.c (is_empty_type): New function.
	(default_is_empty_record): New function.
	(arg_int_size_in_bytes): New function.
	(arg_size_in_bytes): New function.
	* tree.h: Define TYPE_EMPTY_P and TYPE_WARN_EMPTY_P.  Map
	TYPE_ARTIFICIAL to type_common.artificial_flag.
	(default_is_empty_record, arg_int_size_in_bytes,
	arg_size_in_bytes): Declare.

	* g++.dg/abi/empty12.C: New test.
	* g++.dg/abi/empty12.h: New test.
	* g++.dg/abi/empty12a.c: New test.
	* g++.dg/abi/empty13.C: New test.
	* g++.dg/abi/empty13.h: New test.
	* g++.dg/abi/empty13a.c: New test.
	* g++.dg/abi/empty14.C: New test.
	* g++.dg/abi/empty14.h: New test.
	* g++.dg/abi/empty14a.c: New test.
	* g++.dg/abi/empty15.C: New test.
	* g++.dg/abi/empty15.h: New test.
	* g++.dg/abi/empty15a.c: New test.
	* g++.dg/abi/empty16.C: New test.
	* g++.dg/abi/empty16.h: New test.
	* g++.dg/abi/empty16a.c: New test.
	* g++.dg/abi/empty17.C: New test.
	* g++.dg/abi/empty17.h: New test.
	* g++.dg/abi/empty17a.c: New test.
	* g++.dg/abi/empty18.C: New test.
	* g++.dg/abi/empty18.h: New test.
	* g++.dg/abi/empty18a.c: New test.
	* g++.dg/abi/empty19.C: New test.
	* g++.dg/abi/empty19.h: New test.
	* g++.dg/abi/empty19a.c: New test.
	* g++.dg/abi/empty20.C: New test.
	* g++.dg/abi/empty21.C: New test.
	* g++.dg/abi/empty22.C: New test.
	* g++.dg/abi/empty22.h: New test.
	* g++.dg/abi/empty22a.c: New test.
	* g++.dg/abi/empty23.C: New test.
	* g++.dg/abi/empty24.C: New test.
	* g++.dg/abi/pr60336-1.C: New test.
	* g++.dg/abi/pr60336-10.C: New test.
	* g++.dg/abi/pr60336-11.C: New test.
	* g++.dg/abi/pr60336-12.C: New test.
	* g++.dg/abi/pr60336-2.C: New test.
	* g++.dg/abi/pr60336-3.C: New test.
	* g++.dg/abi/pr60336-4.C: New test.
	* g++.dg/abi/pr60336-5.C: New test.
	* g++.dg/abi/pr60336-6.C: New test.
	* g++.dg/abi/pr60336-7.C: New test.
	* g++.dg/abi/pr60336-8.C: New test.
	* g++.dg/abi/pr60336-9.C: New test.
	* g++.dg/abi/pr68355.C: New test.
	* g++.dg/lto/pr60336_0.C: New test.

diff --git gcc/calls.c gcc/calls.c
index 3730f43c7a9..f9a6a5cce13 100644
--- gcc/calls.c
+++ gcc/calls.c
@@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       args[i].unsignedp = unsignedp;
       args[i].mode = mode;
 
+      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
+
       args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
 						argpos < n_named_args);
 
@@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	 Note that in C the default argument promotions
 	 will prevent such mismatches.  */
 
-      size = GET_MODE_SIZE (arg->mode);
+      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
+	size = 0;
+      else
+	size = GET_MODE_SIZE (arg->mode);
+
       /* Compute how much space the push instruction will push.
 	 On many machines, pushing a byte will advance the stack
 	 pointer by a halfword.  */
@@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 
       /* This isn't already where we want it on the stack, so put it there.
 	 This can either be done with push or copy insns.  */
-      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
-		      parm_align, partial, reg, used - size, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
+      if (used
+	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
+			      NULL_RTX, parm_align, partial, reg, used - size,
+			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
+			      reg_parm_stack_space,
+			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
 	sibcall_failure = 1;
 
       /* Unless this is a partially-in-register argument, the argument is now
@@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
 	     for BLKmode is careful to avoid it.  */
 	  excess = (arg->locate.size.constant
-		    - int_size_in_bytes (TREE_TYPE (pval))
+		    - arg_int_size_in_bytes (TREE_TYPE (pval))
 		    + partial);
-	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
+	  size_rtx = expand_expr (arg_size_in_bytes (TREE_TYPE (pval)),
 				  NULL_RTX, TYPE_MODE (sizetype),
 				  EXPAND_NORMAL);
 	}
@@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	    }
 	}
 
-      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
-		      parm_align, partial, reg, excess, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
+      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
+	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
+			parm_align, partial, reg, excess, argblock,
+			ARGS_SIZE_RTX (arg->locate.offset),
+			reg_parm_stack_space,
+			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
 
       /* Unless this is a partially-in-register argument, the argument is now
 	 in the stack.
@@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
   if (TREE_ADDRESSABLE (type))
     return true;
 
+  if (TYPE_EMPTY_P (type))
+    return false;
+
   /* If the padding and mode of the type is such that a copy into
      a register would put it into the wrong part of the register.  */
   if (mode == BLKmode
diff --git gcc/common.opt gcc/common.opt
index f8f2ed3db8a..28a0185f0cf 100644
--- gcc/common.opt
+++ gcc/common.opt
@@ -936,7 +936,7 @@ Driver Undocumented
 ;     Default in G++ 7.
 ;
 ; 12: Corrects the calling convention for classes with only deleted copy/move
-;     constructors.
+;     constructors and changes passing/returning of empty records.
 ;     Default in G++ 8.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
index 29678722226..46b76aeefdd 100644
--- gcc/config/i386/i386.c
+++ gcc/config/i386/i386.c
@@ -7186,6 +7186,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->force_bnd_pass = 0;
   cum->decl = fndecl;
 
+  cum->warn_empty = !warn_abi || cum->stdarg;
+  if (!cum->warn_empty && fntype)
+    {
+      function_args_iterator iter;
+      tree argtype;
+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+	{
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  if (TYPE_EMPTY_P (argtype))
+	    seen_empty_type = true;
+	  else if (seen_empty_type)
+	    {
+	      cum->warn_empty = true;
+	      break;
+	    }
+	}
+    }
+
   if (!TARGET_64BIT)
     {
       /* If there are variable arguments, then we won't pass anything
@@ -9293,6 +9313,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
   if (POINTER_BOUNDS_TYPE_P (type))
     return false;
 
+  /* Empty records are never passed in memory.  */
+  if (type && TYPE_EMPTY_P (type))
+    return false;
+
   if (TARGET_64BIT)
     {
       if (ix86_function_type_abi (fntype) == MS_ABI)
@@ -9873,7 +9897,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
   indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
   if (indirect_p)
     type = build_pointer_type (type);
-  size = int_size_in_bytes (type);
+  size = arg_int_size_in_bytes (type);
   rsize = CEIL (size, UNITS_PER_WORD);
 
   nat_mode = type_natural_mode (type, NULL, false);
@@ -28753,6 +28777,44 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Implement TARGET_EMPTY_RECORD_P.  */
+
+static bool
+ix86_is_empty_record (const_tree type)
+{
+  if (!TARGET_64BIT)
+    return false;
+  return default_is_empty_record (type);
+}
+
+/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
+
+static void
+ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
+{
+  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
+
+  if (!cum->warn_empty)
+    return;
+
+  if (!TYPE_EMPTY_P (type))
+    return;
+
+  if (!TYPE_WARN_EMPTY_P (type))
+    return;
+
+  /* If the actual size of the type is zero, then there is no change
+     in how objects of this size are passed.  */
+  if (int_size_in_bytes (type) == 0)
+    return;
+
+  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
+	   "changes in -fabi-version=12 (GCC 8)", type);
+
+  /* Only warn once.  */
+  cum->warn_empty = false;
+}
+
 /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
    the data type, and ALIGN is the alignment that the object would
    ordinarily have.  */
@@ -50300,6 +50362,12 @@ ix86_run_selftests (void)
 #undef TARGET_CONSTANT_ALIGNMENT
 #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
 
+#undef TARGET_EMPTY_RECORD_P
+#define TARGET_EMPTY_RECORD_P ix86_is_empty_record
+
+#undef TARGET_WARN_PARAMETER_PASSING_ABI
+#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
+
 #if CHECKING_P
 #undef TARGET_RUN_TARGET_SELFTESTS
 #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
index 837906b5169..7f5d245b568 100644
--- gcc/config/i386/i386.h
+++ gcc/config/i386/i386.h
@@ -1633,6 +1633,8 @@ typedef struct ix86_args {
   int warn_avx;			/* True when we want to warn about AVX ABI.  */
   int warn_sse;			/* True when we want to warn about SSE ABI.  */
   int warn_mmx;			/* True when we want to warn about MMX ABI.  */
+  int warn_empty;		/* True when we want to warn about empty classes
+				   passing ABI change.  */
   int sse_regno;		/* next available sse register number */
   int mmx_words;		/* # mmx words passed so far */
   int mmx_nregs;		/* # mmx registers available for passing */
diff --git gcc/cp/class.c gcc/cp/class.c
index 98e62c6ad45..1d5ce7bc98b 100644
--- gcc/cp/class.c
+++ gcc/cp/class.c
@@ -6327,6 +6327,10 @@ layout_class_type (tree t, tree *virtuals_p)
       && tree_int_cst_lt (sizeof_biggest_empty_class,
 			  TYPE_SIZE_UNIT (t)))
     sizeof_biggest_empty_class = TYPE_SIZE_UNIT (t);
+
+  /* Handle empty records as per the x86-64 psABI.  */
+  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
+  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
 }
 
 /* Determine the "key method" for the class type indicated by TYPE,
diff --git gcc/doc/tm.texi gcc/doc/tm.texi
index 72606f53f1c..f16e73c31b1 100644
--- gcc/doc/tm.texi
+++ gcc/doc/tm.texi
@@ -4559,6 +4559,16 @@ This target hook returns the mode to be used when accessing raw return registers
 This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
+This target hook returns true if the type is an empty record.  The default
+is to return @code{false}.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
+This target hook warns about the change in empty class parameter passing
+ABI.
+@end deftypefn
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
index e7d4ada290f..39f6fcaaa11 100644
--- gcc/doc/tm.texi.in
+++ gcc/doc/tm.texi.in
@@ -3439,6 +3439,10 @@ nothing when you use @option{-freg-struct-return} mode.
 
 @hook TARGET_GET_RAW_ARG_MODE
 
+@hook TARGET_EMPTY_RECORD_P
+
+@hook TARGET_WARN_PARAMETER_PASSING_ABI
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/explow.c gcc/explow.c
index 662865d2808..72c2e01a44e 100644
--- gcc/explow.c
+++ gcc/explow.c
@@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
   if (REG_P (val)
       && GET_MODE (val) == BLKmode)
     {
-      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
+      unsigned HOST_WIDE_INT bytes = arg_int_size_in_bytes (valtype);
       opt_scalar_int_mode tmpmode;
 
       /* int_size_in_bytes can return -1.  We don't need a check here
diff --git gcc/expr.c gcc/expr.c
index 649a057e43d..60971066c7a 100644
--- gcc/expr.c
+++ gcc/expr.c
@@ -2749,7 +2749,7 @@ copy_blkmode_to_reg (machine_mode mode_in, tree src)
 
   x = expand_normal (src);
 
-  bytes = int_size_in_bytes (TREE_TYPE (src));
+  bytes = arg_int_size_in_bytes (TREE_TYPE (src));
   if (bytes == 0)
     return NULL_RTX;
 
diff --git gcc/function.c gcc/function.c
index fe3d9c1bbf3..671a49fa76f 100644
--- gcc/function.c
+++ gcc/function.c
@@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
       return;
     }
 
+  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
+					    data->passed_type);
+
   entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
 						    data->promoted_mode,
 						    data->passed_type,
@@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
 
   part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
 
-  sizetree
-    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
+  sizetree = (type
+	      ? arg_size_in_bytes (type)
+	      : size_int (GET_MODE_SIZE (passed_mode)));
   where_pad = targetm.calls.function_arg_padding (passed_mode, type);
   boundary = targetm.calls.function_arg_boundary (passed_mode, type);
   round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
index 554f9cc9f01..83ade46ab07 100644
--- gcc/lto-streamer-out.c
+++ gcc/lto-streamer-out.c
@@ -1008,7 +1008,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
   else if (TYPE_P (t))
     hstate.add_flag (TYPE_UNSIGNED (t));
   if (TYPE_P (t))
-    hstate.add_flag (TYPE_ARTIFICIAL (t));
+    hstate.add_flag (TYPE_WARN_EMPTY_P (t));
   else
     hstate.add_flag (TREE_NO_WARNING (t));
   hstate.add_flag (TREE_NOTHROW (t));
@@ -1166,6 +1166,8 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
       hstate.commit_flag ();
       hstate.add_int (TYPE_PRECISION (t));
       hstate.add_int (TYPE_ALIGN (t));
+      hstate.add_int (TYPE_EMPTY_P (t));
+      hstate.add_int (TYPE_ARTIFICIAL (t));
     }
 
   if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
diff --git gcc/lto/lto.c gcc/lto/lto.c
index 63ba73c0dbf..011f07fd593 100644
--- gcc/lto/lto.c
+++ gcc/lto/lto.c
@@ -1017,7 +1017,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
   else if (TYPE_P (t1))
     compare_values (TYPE_UNSIGNED);
   if (TYPE_P (t1))
-    compare_values (TYPE_ARTIFICIAL);
+    compare_values (TYPE_WARN_EMPTY_P);
   else
     compare_values (TREE_NO_WARNING);
   compare_values (TREE_NOTHROW);
@@ -1165,6 +1165,8 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	compare_values (TYPE_NONALIASED_COMPONENT);
       if (AGGREGATE_TYPE_P (t1))
 	compare_values (TYPE_TYPELESS_STORAGE);
+      compare_values (TYPE_ARTIFICIAL);
+      compare_values (TYPE_EMPTY_P);
       compare_values (TYPE_PACKED);
       compare_values (TYPE_RESTRICT);
       compare_values (TYPE_USER_ALIGN);
diff --git gcc/target.def gcc/target.def
index 577dad8fe86..81aedee80d9 100644
--- gcc/target.def
+++ gcc/target.def
@@ -5055,6 +5055,22 @@ DEFHOOK
  fixed_size_mode, (int regno),
  default_get_reg_raw_mode)
 
+/* Return true if a type is an empty record.  */
+DEFHOOK
+(empty_record_p,
+ "This target hook returns true if the type is an empty record.  The default\n\
+is to return @code{false}.",
+ bool, (const_tree type),
+ hook_bool_const_tree_false)
+
+/* Warn about the change in empty class parameter passing ABI.  */
+DEFHOOK
+(warn_parameter_passing_abi,
+ "This target hook warns about the change in empty class parameter passing\n\
+ABI.",
+ void, (cumulative_args_t ca, tree type),
+ hook_void_CUMULATIVE_ARGS_tree)
+
 HOOK_VECTOR_END (calls)
 
 DEFHOOK
diff --git gcc/targhooks.c gcc/targhooks.c
index dad1e109d23..0edc57b0a15 100644
--- gcc/targhooks.c
+++ gcc/targhooks.c
@@ -756,6 +756,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
 }
 
 void
+hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
+				tree ATTRIBUTE_UNUSED)
+{
+}
+
+void
 default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
 			      machine_mode mode ATTRIBUTE_UNUSED,
 			      const_tree type ATTRIBUTE_UNUSED,
@@ -2108,6 +2114,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
   /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
      requires greater alignment, we must perform dynamic alignment.  */
   if (boundary > align
+      && !TYPE_EMPTY_P (type)
       && !integer_zerop (TYPE_SIZE (type)))
     {
       t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
@@ -2134,7 +2141,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
     }
 
   /* Compute the rounded size of the type.  */
-  type_size = size_in_bytes (type);
+  type_size = arg_size_in_bytes (type);
   rounded_size = round_up (type_size, align);
 
   /* Reduce rounded_size so it's sharable with the postqueue.  */
diff --git gcc/targhooks.h gcc/targhooks.h
index 15bbf5cdf24..e431934cd60 100644
--- gcc/targhooks.h
+++ gcc/targhooks.h
@@ -135,6 +135,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
   (cumulative_args_t, machine_mode, const_tree, bool);
 extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
   (cumulative_args_t, machine_mode, tree, bool);
+extern void hook_void_CUMULATIVE_ARGS_tree
+  (cumulative_args_t, tree);
 extern const char *hook_invalid_arg_for_unprototyped_fn
   (const_tree, const_tree, const_tree);
 extern void default_function_arg_advance
diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
index e69de29bb2d..20d85ff873e 100644
--- gcc/testsuite/g++.dg/abi/empty12.C
+++ gcc/testsuite/g++.dg/abi/empty12.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty12a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty12.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty12.h
+++ gcc/testsuite/g++.dg/abi/empty12.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
index e69de29bb2d..34a25bad75d 100644
--- gcc/testsuite/g++.dg/abi/empty12a.c
+++ gcc/testsuite/g++.dg/abi/empty12a.c
@@ -0,0 +1,6 @@
+#include "empty12.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
index e69de29bb2d..0cb9a373e35 100644
--- gcc/testsuite/g++.dg/abi/empty13.C
+++ gcc/testsuite/g++.dg/abi/empty13.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-x c -fabi-version=11" }
+// { dg-additional-sources "empty13a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty13.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty13.h
+++ gcc/testsuite/g++.dg/abi/empty13.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
index e69de29bb2d..b4303a63826 100644
--- gcc/testsuite/g++.dg/abi/empty13a.c
+++ gcc/testsuite/g++.dg/abi/empty13a.c
@@ -0,0 +1,6 @@
+#include "empty13.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 == -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
index e69de29bb2d..2868d8ad3f3 100644
--- gcc/testsuite/g++.dg/abi/empty14.C
+++ gcc/testsuite/g++.dg/abi/empty14.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty14a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty14.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
index e69de29bb2d..5842279cf37 100644
--- gcc/testsuite/g++.dg/abi/empty14.h
+++ gcc/testsuite/g++.dg/abi/empty14.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[140]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
index e69de29bb2d..8b3d7800c36 100644
--- gcc/testsuite/g++.dg/abi/empty14a.c
+++ gcc/testsuite/g++.dg/abi/empty14a.c
@@ -0,0 +1,6 @@
+#include "empty14.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
index e69de29bb2d..12385f78c78 100644
--- gcc/testsuite/g++.dg/abi/empty15.C
+++ gcc/testsuite/g++.dg/abi/empty15.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty15a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty15.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
index e69de29bb2d..1c6f26f5ae8 100644
--- gcc/testsuite/g++.dg/abi/empty15.h
+++ gcc/testsuite/g++.dg/abi/empty15.h
@@ -0,0 +1,30 @@
+struct A1 {};
+struct A2 {};
+struct B1 { struct A1 a; struct A2 b; };
+struct B2 { struct A1 a; struct A2 b; };
+struct C1 { struct B1 a; struct B2 b; };
+struct C2 { struct B1 a; struct B2 b; };
+struct D1 { struct C1 a; struct C2 b; };
+struct D2 { struct C1 a; struct C2 b; };
+struct E1 { struct D1 a; struct D2 b; };
+struct E2 { struct D1 a; struct D2 b; };
+struct F1 { struct E1 a; struct E2 b; };
+struct F2 { struct E1 a; struct E2 b; };
+struct G1 { struct F1 a; struct F2 b; };
+struct G2 { struct F1 a; struct F2 b; };
+struct H1 { struct G1 a; struct G2 b; };
+struct H2 { struct G1 a; struct G2 b; };
+struct I1 { struct H1 a; struct H2 b; };
+struct I2 { struct H1 a; struct H2 b; };
+struct J1 { struct I1 a; struct I2 b; };
+struct J2 { struct I1 a; struct I2 b; };
+struct dummy { struct J1 a; struct J2 b; };
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
index e69de29bb2d..325b2c5ba09 100644
--- gcc/testsuite/g++.dg/abi/empty15a.c
+++ gcc/testsuite/g++.dg/abi/empty15a.c
@@ -0,0 +1,6 @@
+#include "empty15.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
index e69de29bb2d..1ca52f9011e 100644
--- gcc/testsuite/g++.dg/abi/empty16.C
+++ gcc/testsuite/g++.dg/abi/empty16.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty16a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty16.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
index e69de29bb2d..7552ae06576 100644
--- gcc/testsuite/g++.dg/abi/empty16.h
+++ gcc/testsuite/g++.dg/abi/empty16.h
@@ -0,0 +1,16 @@
+#ifdef __cplusplus
+struct A1 {};
+struct A2 {};
+struct dummy : A1, A2 {} ;
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
index e69de29bb2d..6cb7fbccecc 100644
--- gcc/testsuite/g++.dg/abi/empty16a.c
+++ gcc/testsuite/g++.dg/abi/empty16a.c
@@ -0,0 +1,6 @@
+#include "empty16.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
index e69de29bb2d..d386e5481af 100644
--- gcc/testsuite/g++.dg/abi/empty17.C
+++ gcc/testsuite/g++.dg/abi/empty17.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty17a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty17.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
index e69de29bb2d..9cf72baca2e 100644
--- gcc/testsuite/g++.dg/abi/empty17.h
+++ gcc/testsuite/g++.dg/abi/empty17.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 15;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 15;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
index e69de29bb2d..24408fde09c 100644
--- gcc/testsuite/g++.dg/abi/empty17a.c
+++ gcc/testsuite/g++.dg/abi/empty17a.c
@@ -0,0 +1,6 @@
+#include "empty17.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
index e69de29bb2d..be69c6a2115 100644
--- gcc/testsuite/g++.dg/abi/empty18.C
+++ gcc/testsuite/g++.dg/abi/empty18.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty18a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty18.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
index e69de29bb2d..86e7ecdd211 100644
--- gcc/testsuite/g++.dg/abi/empty18.h
+++ gcc/testsuite/g++.dg/abi/empty18.h
@@ -0,0 +1,9 @@
+struct dummy { int d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
index e69de29bb2d..902860bdc01 100644
--- gcc/testsuite/g++.dg/abi/empty18a.c
+++ gcc/testsuite/g++.dg/abi/empty18a.c
@@ -0,0 +1,6 @@
+#include "empty18.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
index e69de29bb2d..84f5b75558b 100644
--- gcc/testsuite/g++.dg/abi/empty19.C
+++ gcc/testsuite/g++.dg/abi/empty19.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty19a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty19.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
index e69de29bb2d..616b87bdd93 100644
--- gcc/testsuite/g++.dg/abi/empty19.h
+++ gcc/testsuite/g++.dg/abi/empty19.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
index e69de29bb2d..767b1eb7320 100644
--- gcc/testsuite/g++.dg/abi/empty19a.c
+++ gcc/testsuite/g++.dg/abi/empty19a.c
@@ -0,0 +1,6 @@
+#include "empty19.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
index e69de29bb2d..5022033f669 100644
--- gcc/testsuite/g++.dg/abi/empty20.C
+++ gcc/testsuite/g++.dg/abi/empty20.C
@@ -0,0 +1,19 @@
+// PR c++/60336
+// { dg-options "-Wabi=11 -O0" }
+
+struct A { };
+
+void f(A, A) { }	// No warning, trailing parms all empty
+void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+__attribute__ ((always_inline))
+inline void f(A a, int i) // No warning, always inlined
+{
+  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
+int main()
+{
+  A a;
+  f(a,a);
+  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(a,42);
+}
diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
index e69de29bb2d..3b2e3b836b1 100644
--- gcc/testsuite/g++.dg/abi/empty21.C
+++ gcc/testsuite/g++.dg/abi/empty21.C
@@ -0,0 +1,23 @@
+// PR c++/60336
+// { dg-options "-Wabi=11" }
+
+#include <stdarg.h>
+
+struct A { };
+
+void f(int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (i >= 1)
+    va_arg (ap, A);
+  if (i >= 2)
+    va_arg (ap, int);
+}
+
+int main()
+{
+  f(0);
+  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
index e69de29bb2d..f4f4a02bf31 100644
--- gcc/testsuite/g++.dg/abi/empty22.C
+++ gcc/testsuite/g++.dg/abi/empty22.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty22a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty22.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty22.h
+++ gcc/testsuite/g++.dg/abi/empty22.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
index e69de29bb2d..7606c524263 100644
--- gcc/testsuite/g++.dg/abi/empty22a.c
+++ gcc/testsuite/g++.dg/abi/empty22a.c
@@ -0,0 +1,6 @@
+#include "empty22.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
index e69de29bb2d..dbeda81fb24 100644
--- gcc/testsuite/g++.dg/abi/empty23.C
+++ gcc/testsuite/g++.dg/abi/empty23.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[0];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
index e69de29bb2d..822ced1ef50 100644
--- gcc/testsuite/g++.dg/abi/empty24.C
+++ gcc/testsuite/g++.dg/abi/empty24.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
index e69de29bb2d..59447890cec 100644
--- gcc/testsuite/g++.dg/abi/pr60336-1.C
+++ gcc/testsuite/g++.dg/abi/pr60336-1.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
index e69de29bb2d..960cc2307d1 100644
--- gcc/testsuite/g++.dg/abi/pr60336-10.C
+++ gcc/testsuite/g++.dg/abi/pr60336-10.C
@@ -0,0 +1,50 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0 { };
+struct dummy1 { };
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
index e69de29bb2d..14cd6d0ff3d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-11.C
+++ gcc/testsuite/g++.dg/abi/pr60336-11.C
@@ -0,0 +1,56 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+  void bar (void);
+};
+struct dummy1
+{
+  void foo (void);
+};
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
index e69de29bb2d..09917547930 100644
--- gcc/testsuite/g++.dg/abi/pr60336-12.C
+++ gcc/testsuite/g++.dg/abi/pr60336-12.C
@@ -0,0 +1,57 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+};
+struct dummy1
+{
+  unsigned : 15;
+};
+struct dummy : dummy0, dummy1
+{
+};
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
index e69de29bb2d..1c6c3eb8f01 100644
--- gcc/testsuite/g++.dg/abi/pr60336-2.C
+++ gcc/testsuite/g++.dg/abi/pr60336-2.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -Wabi=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...) // { dg-warning "empty" }
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
index e69de29bb2d..4157e553b6b 100644
--- gcc/testsuite/g++.dg/abi/pr60336-3.C
+++ gcc/testsuite/g++.dg/abi/pr60336-3.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
index e69de29bb2d..266f67a537d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-4.C
+++ gcc/testsuite/g++.dg/abi/pr60336-4.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -fabi-version=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count == 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
index e69de29bb2d..fe838750f55 100644
--- gcc/testsuite/g++.dg/abi/pr60336-5.C
+++ gcc/testsuite/g++.dg/abi/pr60336-5.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; struct dummy j; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
index e69de29bb2d..6e08c8f06fa 100644
--- gcc/testsuite/g++.dg/abi/pr60336-6.C
+++ gcc/testsuite/g++.dg/abi/pr60336-6.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i1; struct dummy i2; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
index e69de29bb2d..3b8b8ba6f35 100644
--- gcc/testsuite/g++.dg/abi/pr60336-7.C
+++ gcc/testsuite/g++.dg/abi/pr60336-7.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i[120]; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
index e69de29bb2d..a1ffb64ef02 100644
--- gcc/testsuite/g++.dg/abi/pr60336-8.C
+++ gcc/testsuite/g++.dg/abi/pr60336-8.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct{} a[7][3]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
index e69de29bb2d..393f02b62f0 100644
--- gcc/testsuite/g++.dg/abi/pr60336-9.C
+++ gcc/testsuite/g++.dg/abi/pr60336-9.C
@@ -0,0 +1,28 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct A1 {}; struct A2 {};
+struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
+struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
+struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
+struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
+struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
+struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
+struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
+struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
+struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
+struct dummy { J1 a; J2 b; };
+
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
index e69de29bb2d..1354fc497b5 100644
--- gcc/testsuite/g++.dg/abi/pr68355.C
+++ gcc/testsuite/g++.dg/abi/pr68355.C
@@ -0,0 +1,24 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+template<typename _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr _Tp value = __v;
+  typedef _Tp value_type;
+  typedef integral_constant<_Tp, __v> type;
+  constexpr operator value_type() const { return value; }
+};
+
+typedef integral_constant<bool, true> true_type;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  true_type y;
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
index e69de29bb2d..a0a598c0029 100644
--- gcc/testsuite/g++.dg/lto/pr60336_0.C
+++ gcc/testsuite/g++.dg/lto/pr60336_0.C
@@ -0,0 +1,47 @@
+// { dg-lto-do run }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/tree-core.h gcc/tree-core.h
index f74f1453de6..aaad8d48bcf 100644
--- gcc/tree-core.h
+++ gcc/tree-core.h
@@ -1265,8 +1265,8 @@ struct GTY(()) tree_base {
            all expressions
            all decls
 
-       TYPE_ARTIFICIAL in
-           all types
+       TYPE_WARN_EMPTY_P in
+	   all types
 
    default_def_flag:
 
@@ -1531,7 +1531,9 @@ struct GTY(()) tree_type_common {
   unsigned align : 6;
   unsigned warn_if_not_align : 6;
   unsigned typeless_storage : 1;
-  unsigned spare : 18;
+  unsigned artificial_flag : 1;
+  unsigned empty_flag : 1;
+  unsigned spare : 16;
 
   alias_set_type alias_set;
   tree pointer_to;
diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
index baf0c5bf837..7087186b9a8 100644
--- gcc/tree-streamer-in.c
+++ gcc/tree-streamer-in.c
@@ -130,7 +130,7 @@ unpack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
     bp_unpack_value (bp, 1);
   TREE_ASM_WRITTEN (expr) = (unsigned) bp_unpack_value (bp, 1);
   if (TYPE_P (expr))
-    TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
+    TYPE_WARN_EMPTY_P (expr) = (unsigned) bp_unpack_value (bp, 1);
   else
     TREE_NO_WARNING (expr) = (unsigned) bp_unpack_value (bp, 1);
   TREE_NOTHROW (expr) = (unsigned) bp_unpack_value (bp, 1);
@@ -381,6 +381,8 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
   if (AGGREGATE_TYPE_P (expr))
     TYPE_TYPELESS_STORAGE (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_ARTIFICIAL (expr) = (unsigned) bp_unpack_value (bp, 1);
+  TYPE_EMPTY_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 gcc/tree-streamer-out.c gcc/tree-streamer-out.c
index 7f52d455f5e..622a414d533 100644
--- gcc/tree-streamer-out.c
+++ gcc/tree-streamer-out.c
@@ -100,7 +100,7 @@ pack_ts_base_value_fields (struct bitpack_d *bp, tree expr)
   bp_pack_value (bp, (TREE_CODE (expr) != SSA_NAME
 		      ? 0 : TREE_ASM_WRITTEN (expr)), 1);
   if (TYPE_P (expr))
-    bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
+    bp_pack_value (bp, TYPE_WARN_EMPTY_P (expr), 1);
   else
     bp_pack_value (bp, TREE_NO_WARNING (expr), 1);
   bp_pack_value (bp, TREE_NOTHROW (expr), 1);
@@ -330,6 +330,8 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
   if (AGGREGATE_TYPE_P (expr))
     bp_pack_value (bp, TYPE_TYPELESS_STORAGE (expr), 1);
+  bp_pack_value (bp, TYPE_ARTIFICIAL (expr), 1);
+  bp_pack_value (bp, TYPE_EMPTY_P (expr), 1);
   bp_pack_var_len_unsigned (bp, TYPE_PRECISION (expr));
   bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
 }
diff --git gcc/tree.c gcc/tree.c
index 28e157f5fd2..4603402f654 100644
--- gcc/tree.c
+++ gcc/tree.c
@@ -13811,6 +13811,64 @@ get_nonnull_args (const_tree fntype)
   return argmap;
 }
 
+/* Returns true if TYPE is a type where it and all of its subobjects
+   (recursively) are of structure, union, or array type.  */
+
+static bool
+default_is_empty_type (tree type)
+{
+  if (RECORD_OR_UNION_TYPE_P (type))
+    {
+      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+	if (TREE_CODE (field) == FIELD_DECL
+	    && (DECL_NAME (field)
+		|| RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
+	    && !default_is_empty_type (TREE_TYPE (field)))
+	  return false;
+      return true;
+    }
+  else if (TREE_CODE (type) == ARRAY_TYPE)
+    return (integer_minus_onep (array_type_nelts (type))
+	    || TYPE_DOMAIN (type) == NULL_TREE
+	    || default_is_empty_type (TREE_TYPE (type)));
+  return false;
+}
+
+/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
+   that shouldn't be passed via stack.  */
+
+bool
+default_is_empty_record (const_tree type)
+{
+  if (!abi_version_at_least (12))
+    return false;
+
+  if (type == error_mark_node)
+    return false;
+
+  /* The front end should have turned this into an invisible reference
+     already.  */
+  gcc_assert (!TREE_ADDRESSABLE (type));
+
+  return default_is_empty_type (TYPE_MAIN_VARIANT (type));
+}
+
+/* Like int_size_in_bytes, but handle empty records specially.  */
+
+HOST_WIDE_INT
+arg_int_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
+}
+
+/* Like size_in_bytes, but handle empty records specially.  */
+
+tree
+arg_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
+}
+
 /* List of pointer types used to declare builtins before we have seen their
    real declaration.
 
diff --git gcc/tree.h gcc/tree.h
index 277aa919780..1f20e018567 100644
--- gcc/tree.h
+++ gcc/tree.h
@@ -696,8 +696,16 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
    emitted.  */
 #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
 
+/* Nonzero if we should warn about the change in empty class parameter
+   passing ABI.  */
+#define TYPE_WARN_EMPTY_P(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
+
+/* Nonzero if this type is "empty" according to the particular psABI.  */
+#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
+
 /* Used to indicate that this TYPE represents a compiler-generated entity.  */
-#define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
+#define TYPE_ARTIFICIAL(NODE) \
+  (TYPE_CHECK (NODE)->type_common.artificial_flag)
 
 /* In an IDENTIFIER_NODE, this means that assemble_name was called with
    this string as an argument.  */
@@ -5428,6 +5436,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
 
 extern bool nonnull_arg_p (const_tree);
 extern bool is_redundant_typedef (const_tree);
+extern bool default_is_empty_record (const_tree);
+extern HOST_WIDE_INT arg_int_size_in_bytes (const_tree);
+extern tree arg_size_in_bytes (const_tree);
 
 extern location_t
 set_source_range (tree expr, location_t start, location_t finish);

	Marek
Jason Merrill Nov. 3, 2017, 4:19 p.m. UTC | #12
On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
> +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);

I think we want to set this in finalize_type_size; since the point of
all this is becoming compliant with the psABI (and compatible with the
C front end), I wouldn't think it should be specific to the C++ front
end.

> +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);

Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?

> + if (TREE_CODE (field) == FIELD_DECL
> +     && (DECL_NAME (field)
> +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> +     && !default_is_empty_type (TREE_TYPE (field)))
> +    return false;
> +  return true;

Hmm, this assumes that any unnamed field can be ignored; I'm concerned
that some front end might clear DECL_NAME for a reason that doesn't
imply that the field is just padding.

Jason
Marek Polacek Nov. 6, 2017, 3:37 p.m. UTC | #13
On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
> 
> I think we want to set this in finalize_type_size; since the point of
> all this is becoming compliant with the psABI (and compatible with the
> C front end), I wouldn't think it should be specific to the C++ front
> end.
 
Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in layout_type,
but that wasn't called for the classes in my testcases.  I've moved the
setting of TYPE_EMPTY_P to finalize_type_size now.

> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
> 
> Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?

Yeah, that works, too.  To avoid needing a lang hook, I'm setting the
flag in cxx_init_decl_processing, hope that's ok.

> > + if (TREE_CODE (field) == FIELD_DECL
> > +     && (DECL_NAME (field)
> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> > +     && !default_is_empty_type (TREE_TYPE (field)))
> > +    return false;
> > +  return true;
> 
> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
> that some front end might clear DECL_NAME for a reason that doesn't
> imply that the field is just padding.

In that case I guess we need a new lang hook, right?  Because in
default_is_empty_type we can't check FE-specific flags such as
DECL_C_BIT_FIELD.  For C++, should that lang hook be just
is_really_empty_class?  Or is there anything else I can do?

	Marek
Jason Merrill Nov. 6, 2017, 3:41 p.m. UTC | #14
On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com> wrote:
> On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
>> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
>> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
>>
>> I think we want to set this in finalize_type_size; since the point of
>> all this is becoming compliant with the psABI (and compatible with the
>> C front end), I wouldn't think it should be specific to the C++ front
>> end.
>
> Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in layout_type,
> but that wasn't called for the classes in my testcases.  I've moved the
> setting of TYPE_EMPTY_P to finalize_type_size now.
>
>> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
>>
>> Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?
>
> Yeah, that works, too.  To avoid needing a lang hook, I'm setting the
> flag in cxx_init_decl_processing, hope that's ok.

Sure.

>> > + if (TREE_CODE (field) == FIELD_DECL
>> > +     && (DECL_NAME (field)
>> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
>> > +     && !default_is_empty_type (TREE_TYPE (field)))
>> > +    return false;
>> > +  return true;
>>
>> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
>> that some front end might clear DECL_NAME for a reason that doesn't
>> imply that the field is just padding.
>
> In that case I guess we need a new lang hook, right?  Because in
> default_is_empty_type we can't check FE-specific flags such as
> DECL_C_BIT_FIELD.  For C++, should that lang hook be just
> is_really_empty_class?  Or is there anything else I can do?

Hmm, maybe leave it as it is and just document this assumption about
FIELD_DECL with null DECL_NAME, both here and in tree.def.

Jason
Richard Biener Nov. 7, 2017, 8:14 a.m. UTC | #15
On Mon, 6 Nov 2017, Jason Merrill wrote:

> On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com> wrote:
> > On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
> >> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
> >> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
> >>
> >> I think we want to set this in finalize_type_size; since the point of
> >> all this is becoming compliant with the psABI (and compatible with the
> >> C front end), I wouldn't think it should be specific to the C++ front
> >> end.
> >
> > Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in layout_type,
> > but that wasn't called for the classes in my testcases.  I've moved the
> > setting of TYPE_EMPTY_P to finalize_type_size now.
> >
> >> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
> >>
> >> Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?
> >
> > Yeah, that works, too.  To avoid needing a lang hook, I'm setting the
> > flag in cxx_init_decl_processing, hope that's ok.
> 
> Sure.
> 
> >> > + if (TREE_CODE (field) == FIELD_DECL
> >> > +     && (DECL_NAME (field)
> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
> >> > +    return false;
> >> > +  return true;
> >>
> >> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
> >> that some front end might clear DECL_NAME for a reason that doesn't
> >> imply that the field is just padding.
> >
> > In that case I guess we need a new lang hook, right?  Because in
> > default_is_empty_type we can't check FE-specific flags such as
> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
> > is_really_empty_class?  Or is there anything else I can do?
> 
> Hmm, maybe leave it as it is and just document this assumption about
> FIELD_DECL with null DECL_NAME, both here and in tree.def.

But are you sure you are not changing the ABI for a language other
than C++ then?  I don't think DECL_NAME has any special meaning - why
not check DECL_ARTIFICIAL or another flag that has appropriate
semantic - _what_ semantic are you looking for after all?

Thanks,
Richard.
Marek Polacek Nov. 7, 2017, 6:01 p.m. UTC | #16
On Tue, Nov 07, 2017 at 09:14:58AM +0100, Richard Biener wrote:
> On Mon, 6 Nov 2017, Jason Merrill wrote:
> 
> > On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com> wrote:
> > > On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
> > >> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
> > >> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
> > >>
> > >> I think we want to set this in finalize_type_size; since the point of
> > >> all this is becoming compliant with the psABI (and compatible with the
> > >> C front end), I wouldn't think it should be specific to the C++ front
> > >> end.
> > >
> > > Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in layout_type,
> > > but that wasn't called for the classes in my testcases.  I've moved the
> > > setting of TYPE_EMPTY_P to finalize_type_size now.
> > >
> > >> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
> > >>
> > >> Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?
> > >
> > > Yeah, that works, too.  To avoid needing a lang hook, I'm setting the
> > > flag in cxx_init_decl_processing, hope that's ok.
> > 
> > Sure.
> > 
> > >> > + if (TREE_CODE (field) == FIELD_DECL
> > >> > +     && (DECL_NAME (field)
> > >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> > >> > +     && !default_is_empty_type (TREE_TYPE (field)))
> > >> > +    return false;
> > >> > +  return true;
> > >>
> > >> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
> > >> that some front end might clear DECL_NAME for a reason that doesn't
> > >> imply that the field is just padding.
> > >
> > > In that case I guess we need a new lang hook, right?  Because in
> > > default_is_empty_type we can't check FE-specific flags such as
> > > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
> > > is_really_empty_class?  Or is there anything else I can do?
> > 
> > Hmm, maybe leave it as it is and just document this assumption about
> > FIELD_DECL with null DECL_NAME, both here and in tree.def.
> 
> But are you sure you are not changing the ABI for a language other
> than C++ then?

We might be but I guess we should follow the psABI for all languages,
not just C++.

> I don't think DECL_NAME has any special meaning - why
> not check DECL_ARTIFICIAL or another flag that has appropriate
> semantic - _what_ semantic are you looking for after all?

I believe we just want to see if a field is padding.  Just checking
DECL_ARTIFICIAL will not fly, because unnamed bit-field are not artificial, and
we should consider them padding.  So I'd think it makes sense to stick with
DECL_NAME.

	Marek
Jason Merrill Nov. 7, 2017, 6:21 p.m. UTC | #17
On Tue, Nov 7, 2017 at 3:14 AM, Richard Biener <rguenther@suse.de> wrote:
> On Mon, 6 Nov 2017, Jason Merrill wrote:
>
>> On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com> wrote:
>> > On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
>> >> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
>> >> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
>> >>
>> >> I think we want to set this in finalize_type_size; since the point of
>> >> all this is becoming compliant with the psABI (and compatible with the
>> >> C front end), I wouldn't think it should be specific to the C++ front
>> >> end.
>> >
>> > Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in layout_type,
>> > but that wasn't called for the classes in my testcases.  I've moved the
>> > setting of TYPE_EMPTY_P to finalize_type_size now.
>> >
>> >> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
>> >>
>> >> Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?
>> >
>> > Yeah, that works, too.  To avoid needing a lang hook, I'm setting the
>> > flag in cxx_init_decl_processing, hope that's ok.
>>
>> Sure.
>>
>> >> > + if (TREE_CODE (field) == FIELD_DECL
>> >> > +     && (DECL_NAME (field)
>> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
>> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
>> >> > +    return false;
>> >> > +  return true;
>> >>
>> >> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
>> >> that some front end might clear DECL_NAME for a reason that doesn't
>> >> imply that the field is just padding.
>> >
>> > In that case I guess we need a new lang hook, right?  Because in
>> > default_is_empty_type we can't check FE-specific flags such as
>> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
>> > is_really_empty_class?  Or is there anything else I can do?
>>
>> Hmm, maybe leave it as it is and just document this assumption about
>> FIELD_DECL with null DECL_NAME, both here and in tree.def.
>
> But are you sure you are not changing the ABI for a language other
> than C++ then?

No, that is the concern, we'd need to check that.

> I don't think DECL_NAME has any special meaning - why
> not check DECL_ARTIFICIAL or another flag that has appropriate
> semantic - _what_ semantic are you looking for after all?

That a struct consisting only of padding is considered empty.

Perhaps we could use decl_flag_3 on FIELD_DECL for DECL_PADDING_P to
indicate that a field isn't really data.  Or perhaps we should remove
such things from TYPE_FIELDS after layout is done.

Jason
Richard Biener Nov. 7, 2017, 7:40 p.m. UTC | #18
On November 7, 2017 7:21:17 PM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
>On Tue, Nov 7, 2017 at 3:14 AM, Richard Biener <rguenther@suse.de>
>wrote:
>> On Mon, 6 Nov 2017, Jason Merrill wrote:
>>
>>> On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com>
>wrote:
>>> > On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
>>> >> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek
><polacek@redhat.com> wrote:
>>> >> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
>>> >>
>>> >> I think we want to set this in finalize_type_size; since the
>point of
>>> >> all this is becoming compliant with the psABI (and compatible
>with the
>>> >> C front end), I wouldn't think it should be specific to the C++
>front
>>> >> end.
>>> >
>>> > Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in
>layout_type,
>>> > but that wasn't called for the classes in my testcases.  I've
>moved the
>>> > setting of TYPE_EMPTY_P to finalize_type_size now.
>>> >
>>> >> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses
>(12);
>>> >>
>>> >> Can this flag go on the TRANSLATION_UNIT_DECL rather than the
>type?
>>> >
>>> > Yeah, that works, too.  To avoid needing a lang hook, I'm setting
>the
>>> > flag in cxx_init_decl_processing, hope that's ok.
>>>
>>> Sure.
>>>
>>> >> > + if (TREE_CODE (field) == FIELD_DECL
>>> >> > +     && (DECL_NAME (field)
>>> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
>>> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
>>> >> > +    return false;
>>> >> > +  return true;
>>> >>
>>> >> Hmm, this assumes that any unnamed field can be ignored; I'm
>concerned
>>> >> that some front end might clear DECL_NAME for a reason that
>doesn't
>>> >> imply that the field is just padding.
>>> >
>>> > In that case I guess we need a new lang hook, right?  Because in
>>> > default_is_empty_type we can't check FE-specific flags such as
>>> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
>>> > is_really_empty_class?  Or is there anything else I can do?
>>>
>>> Hmm, maybe leave it as it is and just document this assumption about
>>> FIELD_DECL with null DECL_NAME, both here and in tree.def.
>>
>> But are you sure you are not changing the ABI for a language other
>> than C++ then?
>
>No, that is the concern, we'd need to check that.
>
>> I don't think DECL_NAME has any special meaning - why
>> not check DECL_ARTIFICIAL or another flag that has appropriate
>> semantic - _what_ semantic are you looking for after all?
>
>That a struct consisting only of padding is considered empty.
>
>Perhaps we could use decl_flag_3 on FIELD_DECL for DECL_PADDING_P to
>indicate that a field isn't really data.  Or perhaps we should remove
>such things from TYPE_FIELDS after layout is done.

Hmm, I thought we had custom layout routines if you need padding that isn't indicated by the real fields alignment? Why does C++ need explicit FIELD_DECLs here? 

Richard. 

>Jason
Jason Merrill Nov. 7, 2017, 9:55 p.m. UTC | #19
On Tue, Nov 7, 2017 at 2:40 PM, Richard Biener <rguenther@suse.de> wrote:
> On November 7, 2017 7:21:17 PM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
>>On Tue, Nov 7, 2017 at 3:14 AM, Richard Biener <rguenther@suse.de>
>>wrote:
>>> On Mon, 6 Nov 2017, Jason Merrill wrote:
>>>
>>>> On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com>
>>wrote:
>>>> > On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
>>>> >> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek
>><polacek@redhat.com> wrote:
>>>> >> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
>>>> >>
>>>> >> I think we want to set this in finalize_type_size; since the
>>point of
>>>> >> all this is becoming compliant with the psABI (and compatible
>>with the
>>>> >> C front end), I wouldn't think it should be specific to the C++
>>front
>>>> >> end.
>>>> >
>>>> > Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in
>>layout_type,
>>>> > but that wasn't called for the classes in my testcases.  I've
>>moved the
>>>> > setting of TYPE_EMPTY_P to finalize_type_size now.
>>>> >
>>>> >> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses
>>(12);
>>>> >>
>>>> >> Can this flag go on the TRANSLATION_UNIT_DECL rather than the
>>type?
>>>> >
>>>> > Yeah, that works, too.  To avoid needing a lang hook, I'm setting
>>the
>>>> > flag in cxx_init_decl_processing, hope that's ok.
>>>>
>>>> Sure.
>>>>
>>>> >> > + if (TREE_CODE (field) == FIELD_DECL
>>>> >> > +     && (DECL_NAME (field)
>>>> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
>>>> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
>>>> >> > +    return false;
>>>> >> > +  return true;
>>>> >>
>>>> >> Hmm, this assumes that any unnamed field can be ignored; I'm
>>concerned
>>>> >> that some front end might clear DECL_NAME for a reason that
>>doesn't
>>>> >> imply that the field is just padding.
>>>> >
>>>> > In that case I guess we need a new lang hook, right?  Because in
>>>> > default_is_empty_type we can't check FE-specific flags such as
>>>> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
>>>> > is_really_empty_class?  Or is there anything else I can do?
>>>>
>>>> Hmm, maybe leave it as it is and just document this assumption about
>>>> FIELD_DECL with null DECL_NAME, both here and in tree.def.
>>>
>>> But are you sure you are not changing the ABI for a language other
>>> than C++ then?
>>
>>No, that is the concern, we'd need to check that.
>>
>>> I don't think DECL_NAME has any special meaning - why
>>> not check DECL_ARTIFICIAL or another flag that has appropriate
>>> semantic - _what_ semantic are you looking for after all?
>>
>>That a struct consisting only of padding is considered empty.
>>
>>Perhaps we could use decl_flag_3 on FIELD_DECL for DECL_PADDING_P to
>>indicate that a field isn't really data.  Or perhaps we should remove
>>such things from TYPE_FIELDS after layout is done.
>
> Hmm, I thought we had custom layout routines if you need padding that isn't indicated by the real fields alignment? Why does C++ need explicit FIELD_DECLs here?

The C front end builds FIELD_DECLs for unnamed bit-fields; I think the
C++ front end inherited that decision.

Jason
Richard Biener Nov. 8, 2017, 8:06 a.m. UTC | #20
On Tue, 7 Nov 2017, Jason Merrill wrote:

> On Tue, Nov 7, 2017 at 3:14 AM, Richard Biener <rguenther@suse.de> wrote:
> > On Mon, 6 Nov 2017, Jason Merrill wrote:
> >
> >> On Mon, Nov 6, 2017 at 10:37 AM, Marek Polacek <polacek@redhat.com> wrote:
> >> > On Fri, Nov 03, 2017 at 12:19:05PM -0400, Jason Merrill wrote:
> >> >> On Fri, Nov 3, 2017 at 9:55 AM, Marek Polacek <polacek@redhat.com> wrote:
> >> >> > +  TYPE_EMPTY_P (t) = targetm.calls.empty_record_p (t);
> >> >>
> >> >> I think we want to set this in finalize_type_size; since the point of
> >> >> all this is becoming compliant with the psABI (and compatible with the
> >> >> C front end), I wouldn't think it should be specific to the C++ front
> >> >> end.
> >> >
> >> > Sure, that works.  I'd wanted to do the TYPE_EMPTY_P setting in layout_type,
> >> > but that wasn't called for the classes in my testcases.  I've moved the
> >> > setting of TYPE_EMPTY_P to finalize_type_size now.
> >> >
> >> >> > +  TYPE_WARN_EMPTY_P (t) = warn_abi && abi_version_crosses (12);
> >> >>
> >> >> Can this flag go on the TRANSLATION_UNIT_DECL rather than the type?
> >> >
> >> > Yeah, that works, too.  To avoid needing a lang hook, I'm setting the
> >> > flag in cxx_init_decl_processing, hope that's ok.
> >>
> >> Sure.
> >>
> >> >> > + if (TREE_CODE (field) == FIELD_DECL
> >> >> > +     && (DECL_NAME (field)
> >> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> >> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
> >> >> > +    return false;
> >> >> > +  return true;
> >> >>
> >> >> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
> >> >> that some front end might clear DECL_NAME for a reason that doesn't
> >> >> imply that the field is just padding.
> >> >
> >> > In that case I guess we need a new lang hook, right?  Because in
> >> > default_is_empty_type we can't check FE-specific flags such as
> >> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
> >> > is_really_empty_class?  Or is there anything else I can do?
> >>
> >> Hmm, maybe leave it as it is and just document this assumption about
> >> FIELD_DECL with null DECL_NAME, both here and in tree.def.
> >
> > But are you sure you are not changing the ABI for a language other
> > than C++ then?
> 
> No, that is the concern, we'd need to check that.
> 
> > I don't think DECL_NAME has any special meaning - why
> > not check DECL_ARTIFICIAL or another flag that has appropriate
> > semantic - _what_ semantic are you looking for after all?
> 
> That a struct consisting only of padding is considered empty.
> 
> Perhaps we could use decl_flag_3 on FIELD_DECL for DECL_PADDING_P to
> indicate that a field isn't really data.  Or perhaps we should remove
> such things from TYPE_FIELDS after layout is done.

Both works for me.  As said I'd rather not use NULL DECL_NAME - backends
might build record types without names for va_list.  There's no
technical reason to have a DECL_NAME for sth the user cannot access -
which means every DECL_ARTIFICIAL entity.  This patch would 
retroactively introduce one and thus we'd have to audit each and
every piece of code building record types...

Richard.
Marek Polacek Nov. 9, 2017, 3:23 p.m. UTC | #21
On Wed, Nov 08, 2017 at 09:06:51AM +0100, Richard Biener wrote:
> > >> >> > + if (TREE_CODE (field) == FIELD_DECL
> > >> >> > +     && (DECL_NAME (field)
> > >> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> > >> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
> > >> >> > +    return false;
> > >> >> > +  return true;
> > >> >>
> > >> >> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
> > >> >> that some front end might clear DECL_NAME for a reason that doesn't
> > >> >> imply that the field is just padding.
> > >> >
> > >> > In that case I guess we need a new lang hook, right?  Because in
> > >> > default_is_empty_type we can't check FE-specific flags such as
> > >> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
> > >> > is_really_empty_class?  Or is there anything else I can do?
> > >>
> > >> Hmm, maybe leave it as it is and just document this assumption about
> > >> FIELD_DECL with null DECL_NAME, both here and in tree.def.
> > >
> > > But are you sure you are not changing the ABI for a language other
> > > than C++ then?
> > 
> > No, that is the concern, we'd need to check that.
> > 
> > > I don't think DECL_NAME has any special meaning - why
> > > not check DECL_ARTIFICIAL or another flag that has appropriate
> > > semantic - _what_ semantic are you looking for after all?
> > 
> > That a struct consisting only of padding is considered empty.
> > 
> > Perhaps we could use decl_flag_3 on FIELD_DECL for DECL_PADDING_P to
> > indicate that a field isn't really data.  Or perhaps we should remove
> > such things from TYPE_FIELDS after layout is done.
> 
> Both works for me.  As said I'd rather not use NULL DECL_NAME - backends
> might build record types without names for va_list.  There's no
> technical reason to have a DECL_NAME for sth the user cannot access -
> which means every DECL_ARTIFICIAL entity.  This patch would 
> retroactively introduce one and thus we'd have to audit each and
> every piece of code building record types...

Done in the below, hopefully.  I only had to set DECL_PADDING_P on unnamed
bit-fields.  I've added testcases for zero-sized unnamed bit-fields as well
as for classes with virtual functions.

Moving TYPE_EMPTY_P to finalize_type_size revealed a bug in Cilk+, it was
bogusly (I believe) setting TREE_ADDRESSABLE, so the assert fired.  Since
Cilk+ is being deprecated and the Cilk+ testsuite still passes, I'm not
too worried about this change.

So if you're fine with the DECL_PADDING_P change, all that remains is to
check whether this patch is not changing the ABI for other languages than
C++.  I suppose one way to check that could be to do sth like

  if (strncmp (lang_hooks.name, "GNU C++", 7))
    {FILE *f=fopen("/tmp/A", "a");fprintf(f,"%s\n",main_input_filename);fclose(f);}

and compare the assembly of the files it prints?  But there were thousands of
them I think :(.

Bootstrapped/regtested on x86_64-linux and ppc64-linux.

2017-11-09  Marek Polacek  <polacek@redhat.com>
	    H.J. Lu  <hongjiu.lu@intel.com>
	    Jason Merrill  <jason@redhat.com>

	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* c-decl.c (grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.

	* class.c (layout_class_type): Set DECL_PADDING_P on padding.
	* decl.c (cxx_init_decl_processing): Set TRANSLATION_UNIT_WARN_EMPTY_P.
	(grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.

	* lto.c (compare_tree_sccs_1): Compare TYPE_EMPTY_P and DECL_PADDING_P.

	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* cilk-common.c (cilk_init_builtins): Don't set TREE_ADDRESSABLE.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_return_in_memory): Return false for empty types.
	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
	int_size_in_bytes.
	(ix86_is_empty_record): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* explow.c (hard_function_value): Call arg_int_size_in_bytes
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (assign_parm_find_entry_rtl): Call
	warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call arg size_in_bytes instead
	size_in_bytes.
	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and DECL_PADDING_P.
	* stor-layout.c (finalize_type_size): Set TYPE_EMPTY_P.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hooks.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	arg_size_in_bytes instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree-core.h (tree_type_common): Add empty_flag.
	(tree_decl_common): Update comments.
	* tree-streamer-in.c (unpack_ts_decl_common_value_fields): Stream
	DECL_PADDING_P.
	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
	* tree-streamer-out.c (pack_ts_decl_common_value_fields): Stream
	DECL_PADDING_P.
	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
	* tree.c (default_is_empty_type): New function.
	(default_is_empty_record): New function.
	(arg_int_size_in_bytes): New function.
	(arg_size_in_bytes): New function.
	* tree.h: Define TYPE_EMPTY_P, DECL_PADDING_P and
	TRANSLATION_UNIT_WARN_EMPTY_P.
	(default_is_empty_record, arg_int_size_in_bytes,
	arg_size_in_bytes): Declare.

	* g++.dg/abi/empty12.C: New test.
	* g++.dg/abi/empty12.h: New test.
	* g++.dg/abi/empty12a.c: New test.
	* g++.dg/abi/empty13.C: New test.
	* g++.dg/abi/empty13.h: New test.
	* g++.dg/abi/empty13a.c: New test.
	* g++.dg/abi/empty14.C: New test.
	* g++.dg/abi/empty14.h: New test.
	* g++.dg/abi/empty14a.c: New test.
	* g++.dg/abi/empty15.C: New test.
	* g++.dg/abi/empty15.h: New test.
	* g++.dg/abi/empty15a.c: New test.
	* g++.dg/abi/empty16.C: New test.
	* g++.dg/abi/empty16.h: New test.
	* g++.dg/abi/empty16a.c: New test.
	* g++.dg/abi/empty17.C: New test.
	* g++.dg/abi/empty17.h: New test.
	* g++.dg/abi/empty17a.c: New test.
	* g++.dg/abi/empty18.C: New test.
	* g++.dg/abi/empty18.h: New test.
	* g++.dg/abi/empty18a.c: New test.
	* g++.dg/abi/empty19.C: New test.
	* g++.dg/abi/empty19.h: New test.
	* g++.dg/abi/empty19a.c: New test.
	* g++.dg/abi/empty20.C: New test.
	* g++.dg/abi/empty21.C: New test.
	* g++.dg/abi/empty22.C: New test.
	* g++.dg/abi/empty22.h: New test.
	* g++.dg/abi/empty22a.c: New test.
	* g++.dg/abi/empty23.C: New test.
	* g++.dg/abi/empty24.C: New test.
	* g++.dg/abi/empty25.C: New test.
	* g++.dg/abi/empty25.h: New test.
	* g++.dg/abi/empty25a.c: New test.
	* g++.dg/abi/empty26.C: New test.
	* g++.dg/abi/empty26.h: New test.
	* g++.dg/abi/empty26a.c: New test.
	* g++.dg/abi/pr60336-1.C: New test.
	* g++.dg/abi/pr60336-10.C: New test.
	* g++.dg/abi/pr60336-11.C: New test.
	* g++.dg/abi/pr60336-12.C: New test.
	* g++.dg/abi/pr60336-2.C: New test.
	* g++.dg/abi/pr60336-3.C: New test.
	* g++.dg/abi/pr60336-4.C: New test.
	* g++.dg/abi/pr60336-5.C: New test.
	* g++.dg/abi/pr60336-6.C: New test.
	* g++.dg/abi/pr60336-7.C: New test.
	* g++.dg/abi/pr60336-8.C: New test.
	* g++.dg/abi/pr60336-9.C: New test.
	* g++.dg/abi/pr68355.C: New test.
	* g++.dg/lto/pr60336_0.C: New test.

diff --git gcc/c/c-decl.c gcc/c/c-decl.c
index d95a2b6ea4f..dd01631e0bd 100644
--- gcc/c/c-decl.c
+++ gcc/c/c-decl.c
@@ -6801,7 +6801,10 @@ grokdeclarator (const struct c_declarator *declarator,
 			   FIELD_DECL, declarator->u.id, type);
 	DECL_NONADDRESSABLE_P (decl) = bitfield;
 	if (bitfield && !declarator->u.id)
-	  TREE_NO_WARNING (decl) = 1;
+	  {
+	    TREE_NO_WARNING (decl) = 1;
+	    DECL_PADDING_P (decl) = 1;
+	  }
 
 	if (size_varies)
 	  C_DECL_VARIABLE_SIZE (decl) = 1;
diff --git gcc/calls.c gcc/calls.c
index 3730f43c7a9..f9a6a5cce13 100644
--- gcc/calls.c
+++ gcc/calls.c
@@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       args[i].unsignedp = unsignedp;
       args[i].mode = mode;
 
+      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
+
       args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
 						argpos < n_named_args);
 
@@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	 Note that in C the default argument promotions
 	 will prevent such mismatches.  */
 
-      size = GET_MODE_SIZE (arg->mode);
+      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
+	size = 0;
+      else
+	size = GET_MODE_SIZE (arg->mode);
+
       /* Compute how much space the push instruction will push.
 	 On many machines, pushing a byte will advance the stack
 	 pointer by a halfword.  */
@@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 
       /* This isn't already where we want it on the stack, so put it there.
 	 This can either be done with push or copy insns.  */
-      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
-		      parm_align, partial, reg, used - size, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
+      if (used
+	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
+			      NULL_RTX, parm_align, partial, reg, used - size,
+			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
+			      reg_parm_stack_space,
+			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
 	sibcall_failure = 1;
 
       /* Unless this is a partially-in-register argument, the argument is now
@@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
 	     for BLKmode is careful to avoid it.  */
 	  excess = (arg->locate.size.constant
-		    - int_size_in_bytes (TREE_TYPE (pval))
+		    - arg_int_size_in_bytes (TREE_TYPE (pval))
 		    + partial);
-	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
+	  size_rtx = expand_expr (arg_size_in_bytes (TREE_TYPE (pval)),
 				  NULL_RTX, TYPE_MODE (sizetype),
 				  EXPAND_NORMAL);
 	}
@@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	    }
 	}
 
-      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
-		      parm_align, partial, reg, excess, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
+      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
+	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
+			parm_align, partial, reg, excess, argblock,
+			ARGS_SIZE_RTX (arg->locate.offset),
+			reg_parm_stack_space,
+			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
 
       /* Unless this is a partially-in-register argument, the argument is now
 	 in the stack.
@@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
   if (TREE_ADDRESSABLE (type))
     return true;
 
+  if (TYPE_EMPTY_P (type))
+    return false;
+
   /* If the padding and mode of the type is such that a copy into
      a register would put it into the wrong part of the register.  */
   if (mode == BLKmode
diff --git gcc/cilk-common.c gcc/cilk-common.c
index edde4711869..f3c477f7a96 100644
--- gcc/cilk-common.c
+++ gcc/cilk-common.c
@@ -206,7 +206,6 @@ cilk_init_builtins (void)
   cilk_trees[CILK_TI_FRAME_CONTEXT] = context;
   /* We don't care about reserved, so no need to store it in cilk_trees.  */
   cilk_trees[CILK_TI_FRAME_PEDIGREE] = pedigree;
-  TREE_ADDRESSABLE (frame) = 1;
 
   finish_builtin_struct (frame, "__cilkrts_st_frame_GCC", pedigree, NULL_TREE);
   cilk_frame_type_decl = frame;
diff --git gcc/common.opt gcc/common.opt
index f8f2ed3db8a..28a0185f0cf 100644
--- gcc/common.opt
+++ gcc/common.opt
@@ -936,7 +936,7 @@ Driver Undocumented
 ;     Default in G++ 7.
 ;
 ; 12: Corrects the calling convention for classes with only deleted copy/move
-;     constructors.
+;     constructors and changes passing/returning of empty records.
 ;     Default in G++ 8.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
index 769f1898b22..573dc2cf53a 100644
--- gcc/config/i386/i386.c
+++ gcc/config/i386/i386.c
@@ -7188,6 +7188,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->force_bnd_pass = 0;
   cum->decl = fndecl;
 
+  cum->warn_empty = !warn_abi || cum->stdarg;
+  if (!cum->warn_empty && fntype)
+    {
+      function_args_iterator iter;
+      tree argtype;
+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+	{
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  if (TYPE_EMPTY_P (argtype))
+	    seen_empty_type = true;
+	  else if (seen_empty_type)
+	    {
+	      cum->warn_empty = true;
+	      break;
+	    }
+	}
+    }
+
   if (!TARGET_64BIT)
     {
       /* If there are variable arguments, then we won't pass anything
@@ -9295,6 +9315,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
   if (POINTER_BOUNDS_TYPE_P (type))
     return false;
 
+  /* Empty records are never passed in memory.  */
+  if (type && TYPE_EMPTY_P (type))
+    return false;
+
   if (TARGET_64BIT)
     {
       if (ix86_function_type_abi (fntype) == MS_ABI)
@@ -9875,7 +9899,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
   indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
   if (indirect_p)
     type = build_pointer_type (type);
-  size = int_size_in_bytes (type);
+  size = arg_int_size_in_bytes (type);
   rsize = CEIL (size, UNITS_PER_WORD);
 
   nat_mode = type_natural_mode (type, NULL, false);
@@ -28810,6 +28834,47 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Implement TARGET_EMPTY_RECORD_P.  */
+
+static bool
+ix86_is_empty_record (const_tree type)
+{
+  if (!TARGET_64BIT)
+    return false;
+  return default_is_empty_record (type);
+}
+
+/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
+
+static void
+ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
+{
+  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
+
+  if (!cum->warn_empty)
+    return;
+
+  if (!TYPE_EMPTY_P (type))
+    return;
+
+  tree ctx = cum->decl ? DECL_CONTEXT (cum->decl) : NULL_TREE;
+  if (ctx != NULL_TREE
+      && TREE_CODE (ctx) == TRANSLATION_UNIT_DECL
+      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
+    return;
+
+  /* If the actual size of the type is zero, then there is no change
+     in how objects of this size are passed.  */
+  if (int_size_in_bytes (type) == 0)
+    return;
+
+  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
+	   "changes in -fabi-version=12 (GCC 8)", type);
+
+  /* Only warn once.  */
+  cum->warn_empty = false;
+}
+
 /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
    the data type, and ALIGN is the alignment that the object would
    ordinarily have.  */
@@ -50357,6 +50422,12 @@ ix86_run_selftests (void)
 #undef TARGET_CONSTANT_ALIGNMENT
 #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
 
+#undef TARGET_EMPTY_RECORD_P
+#define TARGET_EMPTY_RECORD_P ix86_is_empty_record
+
+#undef TARGET_WARN_PARAMETER_PASSING_ABI
+#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
+
 #if CHECKING_P
 #undef TARGET_RUN_TARGET_SELFTESTS
 #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
index 837906b5169..7f5d245b568 100644
--- gcc/config/i386/i386.h
+++ gcc/config/i386/i386.h
@@ -1633,6 +1633,8 @@ typedef struct ix86_args {
   int warn_avx;			/* True when we want to warn about AVX ABI.  */
   int warn_sse;			/* True when we want to warn about SSE ABI.  */
   int warn_mmx;			/* True when we want to warn about MMX ABI.  */
+  int warn_empty;		/* True when we want to warn about empty classes
+				   passing ABI change.  */
   int sse_regno;		/* next available sse register number */
   int mmx_words;		/* # mmx words passed so far */
   int mmx_nregs;		/* # mmx registers available for passing */
diff --git gcc/cp/class.c gcc/cp/class.c
index 98e62c6ad45..c6b0349f8e7 100644
--- gcc/cp/class.c
+++ gcc/cp/class.c
@@ -6198,6 +6198,7 @@ layout_class_type (tree t, tree *virtuals_p)
 	  DECL_CONTEXT (padding_field) = t;
 	  DECL_ARTIFICIAL (padding_field) = 1;
 	  DECL_IGNORED_P (padding_field) = 1;
+	  DECL_PADDING_P (padding_field) = 1;
 	  layout_nonempty_base_or_field (rli, padding_field,
 					 NULL_TREE,
 					 empty_base_offsets);
diff --git gcc/cp/decl.c gcc/cp/decl.c
index 0ce8f2d3435..6f0f1baa179 100644
--- gcc/cp/decl.c
+++ gcc/cp/decl.c
@@ -4012,6 +4012,10 @@ cxx_init_decl_processing (void)
   TREE_PUBLIC (global_namespace) = 1;
   DECL_CONTEXT (global_namespace)
     = build_translation_unit_decl (get_identifier (main_input_filename));
+  /* Remember whether we want the empty class passing ABI change warning
+     in this TU.  */
+  TRANSLATION_UNIT_WARN_EMPTY_P (DECL_CONTEXT (global_namespace))
+    = warn_abi && abi_version_crosses (12);
   debug_hooks->register_main_translation_unit
     (DECL_CONTEXT (global_namespace));
   begin_scope (sk_namespace, global_namespace);
@@ -12091,7 +12095,10 @@ grokdeclarator (const cp_declarator *declarator,
 				   FIELD_DECL, unqualified_id, type);
 		DECL_NONADDRESSABLE_P (decl) = bitfield;
 		if (bitfield && !unqualified_id)
-		  TREE_NO_WARNING (decl) = 1;
+		  {
+		    TREE_NO_WARNING (decl) = 1;
+		    DECL_PADDING_P (decl) = 1;
+		  }
 
 		if (storage_class == sc_mutable)
 		  {
diff --git gcc/doc/tm.texi gcc/doc/tm.texi
index 72606f53f1c..f16e73c31b1 100644
--- gcc/doc/tm.texi
+++ gcc/doc/tm.texi
@@ -4559,6 +4559,16 @@ This target hook returns the mode to be used when accessing raw return registers
 This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
+This target hook returns true if the type is an empty record.  The default
+is to return @code{false}.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
+This target hook warns about the change in empty class parameter passing
+ABI.
+@end deftypefn
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
index e7d4ada290f..39f6fcaaa11 100644
--- gcc/doc/tm.texi.in
+++ gcc/doc/tm.texi.in
@@ -3439,6 +3439,10 @@ nothing when you use @option{-freg-struct-return} mode.
 
 @hook TARGET_GET_RAW_ARG_MODE
 
+@hook TARGET_EMPTY_RECORD_P
+
+@hook TARGET_WARN_PARAMETER_PASSING_ABI
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/explow.c gcc/explow.c
index 662865d2808..72c2e01a44e 100644
--- gcc/explow.c
+++ gcc/explow.c
@@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
   if (REG_P (val)
       && GET_MODE (val) == BLKmode)
     {
-      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
+      unsigned HOST_WIDE_INT bytes = arg_int_size_in_bytes (valtype);
       opt_scalar_int_mode tmpmode;
 
       /* int_size_in_bytes can return -1.  We don't need a check here
diff --git gcc/expr.c gcc/expr.c
index 649a057e43d..60971066c7a 100644
--- gcc/expr.c
+++ gcc/expr.c
@@ -2749,7 +2749,7 @@ copy_blkmode_to_reg (machine_mode mode_in, tree src)
 
   x = expand_normal (src);
 
-  bytes = int_size_in_bytes (TREE_TYPE (src));
+  bytes = arg_int_size_in_bytes (TREE_TYPE (src));
   if (bytes == 0)
     return NULL_RTX;
 
diff --git gcc/function.c gcc/function.c
index fe3d9c1bbf3..671a49fa76f 100644
--- gcc/function.c
+++ gcc/function.c
@@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
       return;
     }
 
+  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
+					    data->passed_type);
+
   entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
 						    data->promoted_mode,
 						    data->passed_type,
@@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
 
   part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
 
-  sizetree
-    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
+  sizetree = (type
+	      ? arg_size_in_bytes (type)
+	      : size_int (GET_MODE_SIZE (passed_mode)));
   where_pad = targetm.calls.function_arg_padding (passed_mode, type);
   boundary = targetm.calls.function_arg_boundary (passed_mode, type);
   round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
index 554f9cc9f01..e127e508f97 100644
--- gcc/lto-streamer-out.c
+++ gcc/lto-streamer-out.c
@@ -1073,6 +1073,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
 	{
 	  hstate.add_flag (DECL_PACKED (t));
 	  hstate.add_flag (DECL_NONADDRESSABLE_P (t));
+	  hstate.add_flag (DECL_PADDING_P (t));
 	  hstate.add_int (DECL_OFFSET_ALIGN (t));
 	}
       else if (code == VAR_DECL)
@@ -1166,6 +1167,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
       hstate.commit_flag ();
       hstate.add_int (TYPE_PRECISION (t));
       hstate.add_int (TYPE_ALIGN (t));
+      hstate.add_int (TYPE_EMPTY_P (t));
     }
 
   if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
diff --git gcc/lto/lto.c gcc/lto/lto.c
index 63ba73c0dbf..748ef02143c 100644
--- gcc/lto/lto.c
+++ gcc/lto/lto.c
@@ -1087,6 +1087,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	{
 	  compare_values (DECL_PACKED);
 	  compare_values (DECL_NONADDRESSABLE_P);
+	  compare_values (DECL_PADDING_P);
 	  compare_values (DECL_OFFSET_ALIGN);
 	}
       else if (code == VAR_DECL)
@@ -1165,6 +1166,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	compare_values (TYPE_NONALIASED_COMPONENT);
       if (AGGREGATE_TYPE_P (t1))
 	compare_values (TYPE_TYPELESS_STORAGE);
+      compare_values (TYPE_EMPTY_P);
       compare_values (TYPE_PACKED);
       compare_values (TYPE_RESTRICT);
       compare_values (TYPE_USER_ALIGN);
diff --git gcc/stor-layout.c gcc/stor-layout.c
index 7730ac33e4f..1dd215ec27d 100644
--- gcc/stor-layout.c
+++ gcc/stor-layout.c
@@ -1859,6 +1859,9 @@ finalize_type_size (tree type)
 	  SET_TYPE_MODE (variant, mode);
 	}
     }
+
+  /* Handle empty records as per the x86-64 psABI.  */
+  TYPE_EMPTY_P (type) = targetm.calls.empty_record_p (type);
 }
 
 /* Return a new underlying object for a bitfield started with FIELD.  */
diff --git gcc/target.def gcc/target.def
index 577dad8fe86..81aedee80d9 100644
--- gcc/target.def
+++ gcc/target.def
@@ -5055,6 +5055,22 @@ DEFHOOK
  fixed_size_mode, (int regno),
  default_get_reg_raw_mode)
 
+/* Return true if a type is an empty record.  */
+DEFHOOK
+(empty_record_p,
+ "This target hook returns true if the type is an empty record.  The default\n\
+is to return @code{false}.",
+ bool, (const_tree type),
+ hook_bool_const_tree_false)
+
+/* Warn about the change in empty class parameter passing ABI.  */
+DEFHOOK
+(warn_parameter_passing_abi,
+ "This target hook warns about the change in empty class parameter passing\n\
+ABI.",
+ void, (cumulative_args_t ca, tree type),
+ hook_void_CUMULATIVE_ARGS_tree)
+
 HOOK_VECTOR_END (calls)
 
 DEFHOOK
diff --git gcc/targhooks.c gcc/targhooks.c
index dad1e109d23..0edc57b0a15 100644
--- gcc/targhooks.c
+++ gcc/targhooks.c
@@ -756,6 +756,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
 }
 
 void
+hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
+				tree ATTRIBUTE_UNUSED)
+{
+}
+
+void
 default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
 			      machine_mode mode ATTRIBUTE_UNUSED,
 			      const_tree type ATTRIBUTE_UNUSED,
@@ -2108,6 +2114,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
   /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
      requires greater alignment, we must perform dynamic alignment.  */
   if (boundary > align
+      && !TYPE_EMPTY_P (type)
       && !integer_zerop (TYPE_SIZE (type)))
     {
       t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
@@ -2134,7 +2141,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
     }
 
   /* Compute the rounded size of the type.  */
-  type_size = size_in_bytes (type);
+  type_size = arg_size_in_bytes (type);
   rounded_size = round_up (type_size, align);
 
   /* Reduce rounded_size so it's sharable with the postqueue.  */
diff --git gcc/targhooks.h gcc/targhooks.h
index 15bbf5cdf24..e431934cd60 100644
--- gcc/targhooks.h
+++ gcc/targhooks.h
@@ -135,6 +135,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
   (cumulative_args_t, machine_mode, const_tree, bool);
 extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
   (cumulative_args_t, machine_mode, tree, bool);
+extern void hook_void_CUMULATIVE_ARGS_tree
+  (cumulative_args_t, tree);
 extern const char *hook_invalid_arg_for_unprototyped_fn
   (const_tree, const_tree, const_tree);
 extern void default_function_arg_advance
diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
index e69de29bb2d..20d85ff873e 100644
--- gcc/testsuite/g++.dg/abi/empty12.C
+++ gcc/testsuite/g++.dg/abi/empty12.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty12a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty12.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty12.h
+++ gcc/testsuite/g++.dg/abi/empty12.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
index e69de29bb2d..34a25bad75d 100644
--- gcc/testsuite/g++.dg/abi/empty12a.c
+++ gcc/testsuite/g++.dg/abi/empty12a.c
@@ -0,0 +1,6 @@
+#include "empty12.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
index e69de29bb2d..0cb9a373e35 100644
--- gcc/testsuite/g++.dg/abi/empty13.C
+++ gcc/testsuite/g++.dg/abi/empty13.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-x c -fabi-version=11" }
+// { dg-additional-sources "empty13a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty13.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty13.h
+++ gcc/testsuite/g++.dg/abi/empty13.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
index e69de29bb2d..b4303a63826 100644
--- gcc/testsuite/g++.dg/abi/empty13a.c
+++ gcc/testsuite/g++.dg/abi/empty13a.c
@@ -0,0 +1,6 @@
+#include "empty13.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 == -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
index e69de29bb2d..2868d8ad3f3 100644
--- gcc/testsuite/g++.dg/abi/empty14.C
+++ gcc/testsuite/g++.dg/abi/empty14.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty14a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty14.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
index e69de29bb2d..5842279cf37 100644
--- gcc/testsuite/g++.dg/abi/empty14.h
+++ gcc/testsuite/g++.dg/abi/empty14.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[140]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
index e69de29bb2d..8b3d7800c36 100644
--- gcc/testsuite/g++.dg/abi/empty14a.c
+++ gcc/testsuite/g++.dg/abi/empty14a.c
@@ -0,0 +1,6 @@
+#include "empty14.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
index e69de29bb2d..12385f78c78 100644
--- gcc/testsuite/g++.dg/abi/empty15.C
+++ gcc/testsuite/g++.dg/abi/empty15.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty15a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty15.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
index e69de29bb2d..1c6f26f5ae8 100644
--- gcc/testsuite/g++.dg/abi/empty15.h
+++ gcc/testsuite/g++.dg/abi/empty15.h
@@ -0,0 +1,30 @@
+struct A1 {};
+struct A2 {};
+struct B1 { struct A1 a; struct A2 b; };
+struct B2 { struct A1 a; struct A2 b; };
+struct C1 { struct B1 a; struct B2 b; };
+struct C2 { struct B1 a; struct B2 b; };
+struct D1 { struct C1 a; struct C2 b; };
+struct D2 { struct C1 a; struct C2 b; };
+struct E1 { struct D1 a; struct D2 b; };
+struct E2 { struct D1 a; struct D2 b; };
+struct F1 { struct E1 a; struct E2 b; };
+struct F2 { struct E1 a; struct E2 b; };
+struct G1 { struct F1 a; struct F2 b; };
+struct G2 { struct F1 a; struct F2 b; };
+struct H1 { struct G1 a; struct G2 b; };
+struct H2 { struct G1 a; struct G2 b; };
+struct I1 { struct H1 a; struct H2 b; };
+struct I2 { struct H1 a; struct H2 b; };
+struct J1 { struct I1 a; struct I2 b; };
+struct J2 { struct I1 a; struct I2 b; };
+struct dummy { struct J1 a; struct J2 b; };
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
index e69de29bb2d..325b2c5ba09 100644
--- gcc/testsuite/g++.dg/abi/empty15a.c
+++ gcc/testsuite/g++.dg/abi/empty15a.c
@@ -0,0 +1,6 @@
+#include "empty15.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
index e69de29bb2d..1ca52f9011e 100644
--- gcc/testsuite/g++.dg/abi/empty16.C
+++ gcc/testsuite/g++.dg/abi/empty16.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty16a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty16.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
index e69de29bb2d..7552ae06576 100644
--- gcc/testsuite/g++.dg/abi/empty16.h
+++ gcc/testsuite/g++.dg/abi/empty16.h
@@ -0,0 +1,16 @@
+#ifdef __cplusplus
+struct A1 {};
+struct A2 {};
+struct dummy : A1, A2 {} ;
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
index e69de29bb2d..6cb7fbccecc 100644
--- gcc/testsuite/g++.dg/abi/empty16a.c
+++ gcc/testsuite/g++.dg/abi/empty16a.c
@@ -0,0 +1,6 @@
+#include "empty16.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
index e69de29bb2d..d386e5481af 100644
--- gcc/testsuite/g++.dg/abi/empty17.C
+++ gcc/testsuite/g++.dg/abi/empty17.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty17a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty17.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
index e69de29bb2d..9cf72baca2e 100644
--- gcc/testsuite/g++.dg/abi/empty17.h
+++ gcc/testsuite/g++.dg/abi/empty17.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 15;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 15;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
index e69de29bb2d..24408fde09c 100644
--- gcc/testsuite/g++.dg/abi/empty17a.c
+++ gcc/testsuite/g++.dg/abi/empty17a.c
@@ -0,0 +1,6 @@
+#include "empty17.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
index e69de29bb2d..be69c6a2115 100644
--- gcc/testsuite/g++.dg/abi/empty18.C
+++ gcc/testsuite/g++.dg/abi/empty18.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty18a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty18.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
index e69de29bb2d..86e7ecdd211 100644
--- gcc/testsuite/g++.dg/abi/empty18.h
+++ gcc/testsuite/g++.dg/abi/empty18.h
@@ -0,0 +1,9 @@
+struct dummy { int d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
index e69de29bb2d..902860bdc01 100644
--- gcc/testsuite/g++.dg/abi/empty18a.c
+++ gcc/testsuite/g++.dg/abi/empty18a.c
@@ -0,0 +1,6 @@
+#include "empty18.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
index e69de29bb2d..84f5b75558b 100644
--- gcc/testsuite/g++.dg/abi/empty19.C
+++ gcc/testsuite/g++.dg/abi/empty19.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty19a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty19.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
index e69de29bb2d..616b87bdd93 100644
--- gcc/testsuite/g++.dg/abi/empty19.h
+++ gcc/testsuite/g++.dg/abi/empty19.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
index e69de29bb2d..767b1eb7320 100644
--- gcc/testsuite/g++.dg/abi/empty19a.c
+++ gcc/testsuite/g++.dg/abi/empty19a.c
@@ -0,0 +1,6 @@
+#include "empty19.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
index e69de29bb2d..5022033f669 100644
--- gcc/testsuite/g++.dg/abi/empty20.C
+++ gcc/testsuite/g++.dg/abi/empty20.C
@@ -0,0 +1,19 @@
+// PR c++/60336
+// { dg-options "-Wabi=11 -O0" }
+
+struct A { };
+
+void f(A, A) { }	// No warning, trailing parms all empty
+void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+__attribute__ ((always_inline))
+inline void f(A a, int i) // No warning, always inlined
+{
+  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
+int main()
+{
+  A a;
+  f(a,a);
+  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(a,42);
+}
diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
index e69de29bb2d..3b2e3b836b1 100644
--- gcc/testsuite/g++.dg/abi/empty21.C
+++ gcc/testsuite/g++.dg/abi/empty21.C
@@ -0,0 +1,23 @@
+// PR c++/60336
+// { dg-options "-Wabi=11" }
+
+#include <stdarg.h>
+
+struct A { };
+
+void f(int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (i >= 1)
+    va_arg (ap, A);
+  if (i >= 2)
+    va_arg (ap, int);
+}
+
+int main()
+{
+  f(0);
+  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
index e69de29bb2d..f4f4a02bf31 100644
--- gcc/testsuite/g++.dg/abi/empty22.C
+++ gcc/testsuite/g++.dg/abi/empty22.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty22a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty22.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty22.h
+++ gcc/testsuite/g++.dg/abi/empty22.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
index e69de29bb2d..7606c524263 100644
--- gcc/testsuite/g++.dg/abi/empty22a.c
+++ gcc/testsuite/g++.dg/abi/empty22a.c
@@ -0,0 +1,6 @@
+#include "empty22.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
index e69de29bb2d..dbeda81fb24 100644
--- gcc/testsuite/g++.dg/abi/empty23.C
+++ gcc/testsuite/g++.dg/abi/empty23.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[0];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
index e69de29bb2d..822ced1ef50 100644
--- gcc/testsuite/g++.dg/abi/empty24.C
+++ gcc/testsuite/g++.dg/abi/empty24.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty25.C gcc/testsuite/g++.dg/abi/empty25.C
index e69de29bb2d..da6ef51ff0d 100644
--- gcc/testsuite/g++.dg/abi/empty25.C
+++ gcc/testsuite/g++.dg/abi/empty25.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty25a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty25.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-bogus "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty25.h gcc/testsuite/g++.dg/abi/empty25.h
index e69de29bb2d..2f22fd5e505 100644
--- gcc/testsuite/g++.dg/abi/empty25.h
+++ gcc/testsuite/g++.dg/abi/empty25.h
@@ -0,0 +1,18 @@
+#ifdef __cplusplus
+struct dummy
+{
+  virtual void bar (void) { }
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty25a.c gcc/testsuite/g++.dg/abi/empty25a.c
index e69de29bb2d..8c16e453c75 100644
--- gcc/testsuite/g++.dg/abi/empty25a.c
+++ gcc/testsuite/g++.dg/abi/empty25a.c
@@ -0,0 +1,6 @@
+#include "empty25.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty26.C gcc/testsuite/g++.dg/abi/empty26.C
index e69de29bb2d..ab2f54d8dab 100644
--- gcc/testsuite/g++.dg/abi/empty26.C
+++ gcc/testsuite/g++.dg/abi/empty26.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty26a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty26.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty26.h gcc/testsuite/g++.dg/abi/empty26.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty26.h
+++ gcc/testsuite/g++.dg/abi/empty26.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty26a.c gcc/testsuite/g++.dg/abi/empty26a.c
index e69de29bb2d..bc0ae47ba2c 100644
--- gcc/testsuite/g++.dg/abi/empty26a.c
+++ gcc/testsuite/g++.dg/abi/empty26a.c
@@ -0,0 +1,6 @@
+#include "empty26.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
index e69de29bb2d..59447890cec 100644
--- gcc/testsuite/g++.dg/abi/pr60336-1.C
+++ gcc/testsuite/g++.dg/abi/pr60336-1.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
index e69de29bb2d..960cc2307d1 100644
--- gcc/testsuite/g++.dg/abi/pr60336-10.C
+++ gcc/testsuite/g++.dg/abi/pr60336-10.C
@@ -0,0 +1,50 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0 { };
+struct dummy1 { };
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
index e69de29bb2d..14cd6d0ff3d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-11.C
+++ gcc/testsuite/g++.dg/abi/pr60336-11.C
@@ -0,0 +1,56 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+  void bar (void);
+};
+struct dummy1
+{
+  void foo (void);
+};
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
index e69de29bb2d..09917547930 100644
--- gcc/testsuite/g++.dg/abi/pr60336-12.C
+++ gcc/testsuite/g++.dg/abi/pr60336-12.C
@@ -0,0 +1,57 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+};
+struct dummy1
+{
+  unsigned : 15;
+};
+struct dummy : dummy0, dummy1
+{
+};
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
index e69de29bb2d..1c6c3eb8f01 100644
--- gcc/testsuite/g++.dg/abi/pr60336-2.C
+++ gcc/testsuite/g++.dg/abi/pr60336-2.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -Wabi=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...) // { dg-warning "empty" }
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
index e69de29bb2d..4157e553b6b 100644
--- gcc/testsuite/g++.dg/abi/pr60336-3.C
+++ gcc/testsuite/g++.dg/abi/pr60336-3.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
index e69de29bb2d..266f67a537d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-4.C
+++ gcc/testsuite/g++.dg/abi/pr60336-4.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -fabi-version=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count == 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
index e69de29bb2d..fe838750f55 100644
--- gcc/testsuite/g++.dg/abi/pr60336-5.C
+++ gcc/testsuite/g++.dg/abi/pr60336-5.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; struct dummy j; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
index e69de29bb2d..6e08c8f06fa 100644
--- gcc/testsuite/g++.dg/abi/pr60336-6.C
+++ gcc/testsuite/g++.dg/abi/pr60336-6.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i1; struct dummy i2; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
index e69de29bb2d..3b8b8ba6f35 100644
--- gcc/testsuite/g++.dg/abi/pr60336-7.C
+++ gcc/testsuite/g++.dg/abi/pr60336-7.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i[120]; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
index e69de29bb2d..a1ffb64ef02 100644
--- gcc/testsuite/g++.dg/abi/pr60336-8.C
+++ gcc/testsuite/g++.dg/abi/pr60336-8.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct{} a[7][3]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
index e69de29bb2d..393f02b62f0 100644
--- gcc/testsuite/g++.dg/abi/pr60336-9.C
+++ gcc/testsuite/g++.dg/abi/pr60336-9.C
@@ -0,0 +1,28 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct A1 {}; struct A2 {};
+struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
+struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
+struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
+struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
+struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
+struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
+struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
+struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
+struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
+struct dummy { J1 a; J2 b; };
+
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
index e69de29bb2d..1354fc497b5 100644
--- gcc/testsuite/g++.dg/abi/pr68355.C
+++ gcc/testsuite/g++.dg/abi/pr68355.C
@@ -0,0 +1,24 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+template<typename _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr _Tp value = __v;
+  typedef _Tp value_type;
+  typedef integral_constant<_Tp, __v> type;
+  constexpr operator value_type() const { return value; }
+};
+
+typedef integral_constant<bool, true> true_type;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  true_type y;
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
index e69de29bb2d..a0a598c0029 100644
--- gcc/testsuite/g++.dg/lto/pr60336_0.C
+++ gcc/testsuite/g++.dg/lto/pr60336_0.C
@@ -0,0 +1,47 @@
+// { dg-lto-do run }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/tree-core.h gcc/tree-core.h
index f74f1453de6..13ff5e3db71 100644
--- gcc/tree-core.h
+++ gcc/tree-core.h
@@ -1531,7 +1531,8 @@ struct GTY(()) tree_type_common {
   unsigned align : 6;
   unsigned warn_if_not_align : 6;
   unsigned typeless_storage : 1;
-  unsigned spare : 18;
+  unsigned empty_flag : 1;
+  unsigned spare : 17;
 
   alias_set_type alias_set;
   tree pointer_to;
@@ -1609,7 +1610,8 @@ struct GTY(()) tree_decl_common {
   unsigned lang_flag_7 : 1;
   unsigned lang_flag_8 : 1;
 
-  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER.  */
+  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER
+     IN TRANSLATION_UNIT_DECL, this is TRANSLATION_UNIT_WARN_EMPTY_P.  */
   unsigned decl_flag_0 : 1;
   /* In FIELD_DECL, this is DECL_BIT_FIELD
      In VAR_DECL and FUNCTION_DECL, this is DECL_EXTERNAL.
@@ -1619,7 +1621,7 @@ struct GTY(()) tree_decl_common {
      In VAR_DECL, PARM_DECL and RESULT_DECL, this is
      DECL_HAS_VALUE_EXPR_P.  */
   unsigned decl_flag_2 : 1;
-  /* 1 bit unused.  */
+  /* In FIELD_DECL, this is DECL_PADDING_P.  */
   unsigned decl_flag_3 : 1;
   /* Logically, these two would go in a theoretical base shared by var and
      parm decl. */
diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
index baf0c5bf837..36402c6a39c 100644
--- gcc/tree-streamer-in.c
+++ gcc/tree-streamer-in.c
@@ -251,6 +251,7 @@ unpack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
     {
       DECL_PACKED (expr) = (unsigned) bp_unpack_value (bp, 1);
       DECL_NONADDRESSABLE_P (expr) = (unsigned) bp_unpack_value (bp, 1);
+      DECL_PADDING_P (expr) = (unsigned) bp_unpack_value (bp, 1);
       expr->decl_common.off_align = bp_unpack_value (bp, 8);
     }
 
@@ -381,6 +382,7 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
   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_PRECISION (expr) = bp_unpack_var_len_unsigned (bp);
   SET_TYPE_ALIGN (expr, bp_unpack_var_len_unsigned (bp));
 #ifdef ACCEL_COMPILER
diff --git gcc/tree-streamer-out.c gcc/tree-streamer-out.c
index 7f52d455f5e..08c58a4709d 100644
--- gcc/tree-streamer-out.c
+++ gcc/tree-streamer-out.c
@@ -211,6 +211,7 @@ pack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
     {
       bp_pack_value (bp, DECL_PACKED (expr), 1);
       bp_pack_value (bp, DECL_NONADDRESSABLE_P (expr), 1);
+      bp_pack_value (bp, DECL_PADDING_P (expr), 1);
       bp_pack_value (bp, expr->decl_common.off_align, 8);
     }
 
@@ -330,6 +331,7 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
   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_var_len_unsigned (bp, TYPE_PRECISION (expr));
   bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
 }
diff --git gcc/tree.c gcc/tree.c
index 28e157f5fd2..e400c45d484 100644
--- gcc/tree.c
+++ gcc/tree.c
@@ -13811,6 +13811,63 @@ get_nonnull_args (const_tree fntype)
   return argmap;
 }
 
+/* Returns true if TYPE is a type where it and all of its subobjects
+   (recursively) are of structure, union, or array type.  */
+
+static bool
+default_is_empty_type (tree type)
+{
+  if (RECORD_OR_UNION_TYPE_P (type))
+    {
+      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+	if (TREE_CODE (field) == FIELD_DECL
+	    && !DECL_PADDING_P (field)
+	    && !default_is_empty_type (TREE_TYPE (field)))
+	  return false;
+      return true;
+    }
+  else if (TREE_CODE (type) == ARRAY_TYPE)
+    return (integer_minus_onep (array_type_nelts (type))
+	    || TYPE_DOMAIN (type) == NULL_TREE
+	    || default_is_empty_type (TREE_TYPE (type)));
+  return false;
+}
+
+/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
+   that shouldn't be passed via stack.  */
+
+bool
+default_is_empty_record (const_tree type)
+{
+  if (!abi_version_at_least (12))
+    return false;
+
+  if (type == error_mark_node)
+    return false;
+
+  /* The front end should have turned this into an invisible reference
+     already.  */
+  gcc_assert (!TREE_ADDRESSABLE (type));
+
+  return default_is_empty_type (TYPE_MAIN_VARIANT (type));
+}
+
+/* Like int_size_in_bytes, but handle empty records specially.  */
+
+HOST_WIDE_INT
+arg_int_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
+}
+
+/* Like size_in_bytes, but handle empty records specially.  */
+
+tree
+arg_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
+}
+
 /* List of pointer types used to declare builtins before we have seen their
    real declaration.
 
diff --git gcc/tree.h gcc/tree.h
index 277aa919780..fcd015edbd2 100644
--- gcc/tree.h
+++ gcc/tree.h
@@ -696,6 +696,14 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
    emitted.  */
 #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
 
+/* Nonzero if we should warn about the change in empty class parameter
+   passing ABI in this TU.  */
+#define TRANSLATION_UNIT_WARN_EMPTY_P(NODE) \
+  (TRANSLATION_UNIT_DECL_CHECK (NODE)->decl_common.decl_flag_0)
+
+/* Nonzero if this type is "empty" according to the particular psABI.  */
+#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
+
 /* Used to indicate that this TYPE represents a compiler-generated entity.  */
 #define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
 
@@ -2619,6 +2627,12 @@ extern void decl_value_expr_insert (tree, tree);
 #define DECL_NONADDRESSABLE_P(NODE) \
   (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_2)
 
+/* Used in a FIELD_DECL to indicate that this field is padding for
+   the purpose of determining whether the record this field is a member
+   of is considered empty.  */
+#define DECL_PADDING_P(NODE) \
+  (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_3)
+
 /* A numeric unique identifier for a LABEL_DECL.  The UID allocation is
    dense, unique within any one function, and may be used to index arrays.
    If the value is -1, then no UID has been assigned.  */
@@ -5428,6 +5442,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
 
 extern bool nonnull_arg_p (const_tree);
 extern bool is_redundant_typedef (const_tree);
+extern bool default_is_empty_record (const_tree);
+extern HOST_WIDE_INT arg_int_size_in_bytes (const_tree);
+extern tree arg_size_in_bytes (const_tree);
 
 extern location_t
 set_source_range (tree expr, location_t start, location_t finish);

	Marek
Richard Biener Nov. 9, 2017, 3:26 p.m. UTC | #22
On Thu, 9 Nov 2017, Marek Polacek wrote:

> On Wed, Nov 08, 2017 at 09:06:51AM +0100, Richard Biener wrote:
> > > >> >> > + if (TREE_CODE (field) == FIELD_DECL
> > > >> >> > +     && (DECL_NAME (field)
> > > >> >> > +         || RECORD_OR_UNION_TYPE_P (TREE_TYPE (field)))
> > > >> >> > +     && !default_is_empty_type (TREE_TYPE (field)))
> > > >> >> > +    return false;
> > > >> >> > +  return true;
> > > >> >>
> > > >> >> Hmm, this assumes that any unnamed field can be ignored; I'm concerned
> > > >> >> that some front end might clear DECL_NAME for a reason that doesn't
> > > >> >> imply that the field is just padding.
> > > >> >
> > > >> > In that case I guess we need a new lang hook, right?  Because in
> > > >> > default_is_empty_type we can't check FE-specific flags such as
> > > >> > DECL_C_BIT_FIELD.  For C++, should that lang hook be just
> > > >> > is_really_empty_class?  Or is there anything else I can do?
> > > >>
> > > >> Hmm, maybe leave it as it is and just document this assumption about
> > > >> FIELD_DECL with null DECL_NAME, both here and in tree.def.
> > > >
> > > > But are you sure you are not changing the ABI for a language other
> > > > than C++ then?
> > > 
> > > No, that is the concern, we'd need to check that.
> > > 
> > > > I don't think DECL_NAME has any special meaning - why
> > > > not check DECL_ARTIFICIAL or another flag that has appropriate
> > > > semantic - _what_ semantic are you looking for after all?
> > > 
> > > That a struct consisting only of padding is considered empty.
> > > 
> > > Perhaps we could use decl_flag_3 on FIELD_DECL for DECL_PADDING_P to
> > > indicate that a field isn't really data.  Or perhaps we should remove
> > > such things from TYPE_FIELDS after layout is done.
> > 
> > Both works for me.  As said I'd rather not use NULL DECL_NAME - backends
> > might build record types without names for va_list.  There's no
> > technical reason to have a DECL_NAME for sth the user cannot access -
> > which means every DECL_ARTIFICIAL entity.  This patch would 
> > retroactively introduce one and thus we'd have to audit each and
> > every piece of code building record types...
> 
> Done in the below, hopefully.  I only had to set DECL_PADDING_P on unnamed
> bit-fields.  I've added testcases for zero-sized unnamed bit-fields as well
> as for classes with virtual functions.
> 
> Moving TYPE_EMPTY_P to finalize_type_size revealed a bug in Cilk+, it was
> bogusly (I believe) setting TREE_ADDRESSABLE, so the assert fired.  Since
> Cilk+ is being deprecated and the Cilk+ testsuite still passes, I'm not
> too worried about this change.
> 
> So if you're fine with the DECL_PADDING_P change, all that remains is to
> check whether this patch is not changing the ABI for other languages than
> C++.  I suppose one way to check that could be to do sth like
> 
>   if (strncmp (lang_hooks.name, "GNU C++", 7))
>     {FILE *f=fopen("/tmp/A", "a");fprintf(f,"%s\n",main_input_filename);fclose(f);}
> 
> and compare the assembly of the files it prints?  But there were thousands of
> them I think :(.

Shouldn't most of GCCs own object files (and target library object files)
compare 1:1 before and after the patch?  After all it should _only_ affect
parameter passing of empty aggregates (and the files the patch changes
of course)?

Richard.

> Bootstrapped/regtested on x86_64-linux and ppc64-linux.
> 
> 2017-11-09  Marek Polacek  <polacek@redhat.com>
> 	    H.J. Lu  <hongjiu.lu@intel.com>
> 	    Jason Merrill  <jason@redhat.com>
> 
> 	PR c++/60336
> 	PR middle-end/67239
> 	PR target/68355
> 	* c-decl.c (grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.
> 
> 	* class.c (layout_class_type): Set DECL_PADDING_P on padding.
> 	* decl.c (cxx_init_decl_processing): Set TRANSLATION_UNIT_WARN_EMPTY_P.
> 	(grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.
> 
> 	* lto.c (compare_tree_sccs_1): Compare TYPE_EMPTY_P and DECL_PADDING_P.
> 
> 	* calls.c (initialize_argument_information): Call
> 	warn_parameter_passing_abi target hook.
> 	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
> 	argument onto stack.
> 	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
> 	* cilk-common.c (cilk_init_builtins): Don't set TREE_ADDRESSABLE.
> 	* common.opt: Update -fabi-version description.
> 	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
> 	(ix86_return_in_memory): Return false for empty types.
> 	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
> 	int_size_in_bytes.
> 	(ix86_is_empty_record): New function.
> 	(ix86_warn_parameter_passing_abi): New function.
> 	(TARGET_EMPTY_RECORD_P): Redefine.
> 	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
> 	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
> 	* doc/tm.texi: Regenerated.
> 	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
> 	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
> 	* explow.c (hard_function_value): Call arg_int_size_in_bytes
> 	instead of int_size_in_bytes.
> 	* expr.c (copy_blkmode_to_reg): Likewise.
> 	* function.c (assign_parm_find_entry_rtl): Call
> 	warn_parameter_passing_abi target hook.
> 	(locate_and_pad_parm): Call arg size_in_bytes instead
> 	size_in_bytes.
> 	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and DECL_PADDING_P.
> 	* stor-layout.c (finalize_type_size): Set TYPE_EMPTY_P.
> 	* target.def (empty_record_p, warn_parameter_passing_abi): New target
> 	hooks.
> 	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
> 	(std_gimplify_va_arg_expr): Skip empty records.  Call
> 	arg_size_in_bytes instead size_in_bytes.
> 	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
> 	* tree-core.h (tree_type_common): Add empty_flag.
> 	(tree_decl_common): Update comments.
> 	* tree-streamer-in.c (unpack_ts_decl_common_value_fields): Stream
> 	DECL_PADDING_P.
> 	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
> 	* tree-streamer-out.c (pack_ts_decl_common_value_fields): Stream
> 	DECL_PADDING_P.
> 	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
> 	* tree.c (default_is_empty_type): New function.
> 	(default_is_empty_record): New function.
> 	(arg_int_size_in_bytes): New function.
> 	(arg_size_in_bytes): New function.
> 	* tree.h: Define TYPE_EMPTY_P, DECL_PADDING_P and
> 	TRANSLATION_UNIT_WARN_EMPTY_P.
> 	(default_is_empty_record, arg_int_size_in_bytes,
> 	arg_size_in_bytes): Declare.
> 
> 	* g++.dg/abi/empty12.C: New test.
> 	* g++.dg/abi/empty12.h: New test.
> 	* g++.dg/abi/empty12a.c: New test.
> 	* g++.dg/abi/empty13.C: New test.
> 	* g++.dg/abi/empty13.h: New test.
> 	* g++.dg/abi/empty13a.c: New test.
> 	* g++.dg/abi/empty14.C: New test.
> 	* g++.dg/abi/empty14.h: New test.
> 	* g++.dg/abi/empty14a.c: New test.
> 	* g++.dg/abi/empty15.C: New test.
> 	* g++.dg/abi/empty15.h: New test.
> 	* g++.dg/abi/empty15a.c: New test.
> 	* g++.dg/abi/empty16.C: New test.
> 	* g++.dg/abi/empty16.h: New test.
> 	* g++.dg/abi/empty16a.c: New test.
> 	* g++.dg/abi/empty17.C: New test.
> 	* g++.dg/abi/empty17.h: New test.
> 	* g++.dg/abi/empty17a.c: New test.
> 	* g++.dg/abi/empty18.C: New test.
> 	* g++.dg/abi/empty18.h: New test.
> 	* g++.dg/abi/empty18a.c: New test.
> 	* g++.dg/abi/empty19.C: New test.
> 	* g++.dg/abi/empty19.h: New test.
> 	* g++.dg/abi/empty19a.c: New test.
> 	* g++.dg/abi/empty20.C: New test.
> 	* g++.dg/abi/empty21.C: New test.
> 	* g++.dg/abi/empty22.C: New test.
> 	* g++.dg/abi/empty22.h: New test.
> 	* g++.dg/abi/empty22a.c: New test.
> 	* g++.dg/abi/empty23.C: New test.
> 	* g++.dg/abi/empty24.C: New test.
> 	* g++.dg/abi/empty25.C: New test.
> 	* g++.dg/abi/empty25.h: New test.
> 	* g++.dg/abi/empty25a.c: New test.
> 	* g++.dg/abi/empty26.C: New test.
> 	* g++.dg/abi/empty26.h: New test.
> 	* g++.dg/abi/empty26a.c: New test.
> 	* g++.dg/abi/pr60336-1.C: New test.
> 	* g++.dg/abi/pr60336-10.C: New test.
> 	* g++.dg/abi/pr60336-11.C: New test.
> 	* g++.dg/abi/pr60336-12.C: New test.
> 	* g++.dg/abi/pr60336-2.C: New test.
> 	* g++.dg/abi/pr60336-3.C: New test.
> 	* g++.dg/abi/pr60336-4.C: New test.
> 	* g++.dg/abi/pr60336-5.C: New test.
> 	* g++.dg/abi/pr60336-6.C: New test.
> 	* g++.dg/abi/pr60336-7.C: New test.
> 	* g++.dg/abi/pr60336-8.C: New test.
> 	* g++.dg/abi/pr60336-9.C: New test.
> 	* g++.dg/abi/pr68355.C: New test.
> 	* g++.dg/lto/pr60336_0.C: New test.
> 
> diff --git gcc/c/c-decl.c gcc/c/c-decl.c
> index d95a2b6ea4f..dd01631e0bd 100644
> --- gcc/c/c-decl.c
> +++ gcc/c/c-decl.c
> @@ -6801,7 +6801,10 @@ grokdeclarator (const struct c_declarator *declarator,
>  			   FIELD_DECL, declarator->u.id, type);
>  	DECL_NONADDRESSABLE_P (decl) = bitfield;
>  	if (bitfield && !declarator->u.id)
> -	  TREE_NO_WARNING (decl) = 1;
> +	  {
> +	    TREE_NO_WARNING (decl) = 1;
> +	    DECL_PADDING_P (decl) = 1;
> +	  }
>  
>  	if (size_varies)
>  	  C_DECL_VARIABLE_SIZE (decl) = 1;
> diff --git gcc/calls.c gcc/calls.c
> index 3730f43c7a9..f9a6a5cce13 100644
> --- gcc/calls.c
> +++ gcc/calls.c
> @@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
>        args[i].unsignedp = unsignedp;
>        args[i].mode = mode;
>  
> +      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
> +
>        args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
>  						argpos < n_named_args);
>  
> @@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	 Note that in C the default argument promotions
>  	 will prevent such mismatches.  */
>  
> -      size = GET_MODE_SIZE (arg->mode);
> +      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
> +	size = 0;
> +      else
> +	size = GET_MODE_SIZE (arg->mode);
> +
>        /* Compute how much space the push instruction will push.
>  	 On many machines, pushing a byte will advance the stack
>  	 pointer by a halfword.  */
> @@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  
>        /* This isn't already where we want it on the stack, so put it there.
>  	 This can either be done with push or copy insns.  */
> -      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
> -		      parm_align, partial, reg, used - size, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
> +      if (used
> +	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
> +			      NULL_RTX, parm_align, partial, reg, used - size,
> +			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
> +			      reg_parm_stack_space,
> +			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
>  	sibcall_failure = 1;
>  
>        /* Unless this is a partially-in-register argument, the argument is now
> @@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
>  	     for BLKmode is careful to avoid it.  */
>  	  excess = (arg->locate.size.constant
> -		    - int_size_in_bytes (TREE_TYPE (pval))
> +		    - arg_int_size_in_bytes (TREE_TYPE (pval))
>  		    + partial);
> -	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
> +	  size_rtx = expand_expr (arg_size_in_bytes (TREE_TYPE (pval)),
>  				  NULL_RTX, TYPE_MODE (sizetype),
>  				  EXPAND_NORMAL);
>  	}
> @@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	    }
>  	}
>  
> -      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> -		      parm_align, partial, reg, excess, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
> +      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
> +	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> +			parm_align, partial, reg, excess, argblock,
> +			ARGS_SIZE_RTX (arg->locate.offset),
> +			reg_parm_stack_space,
> +			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
>  
>        /* Unless this is a partially-in-register argument, the argument is now
>  	 in the stack.
> @@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
>    if (TREE_ADDRESSABLE (type))
>      return true;
>  
> +  if (TYPE_EMPTY_P (type))
> +    return false;
> +
>    /* If the padding and mode of the type is such that a copy into
>       a register would put it into the wrong part of the register.  */
>    if (mode == BLKmode
> diff --git gcc/cilk-common.c gcc/cilk-common.c
> index edde4711869..f3c477f7a96 100644
> --- gcc/cilk-common.c
> +++ gcc/cilk-common.c
> @@ -206,7 +206,6 @@ cilk_init_builtins (void)
>    cilk_trees[CILK_TI_FRAME_CONTEXT] = context;
>    /* We don't care about reserved, so no need to store it in cilk_trees.  */
>    cilk_trees[CILK_TI_FRAME_PEDIGREE] = pedigree;
> -  TREE_ADDRESSABLE (frame) = 1;
>  
>    finish_builtin_struct (frame, "__cilkrts_st_frame_GCC", pedigree, NULL_TREE);
>    cilk_frame_type_decl = frame;
> diff --git gcc/common.opt gcc/common.opt
> index f8f2ed3db8a..28a0185f0cf 100644
> --- gcc/common.opt
> +++ gcc/common.opt
> @@ -936,7 +936,7 @@ Driver Undocumented
>  ;     Default in G++ 7.
>  ;
>  ; 12: Corrects the calling convention for classes with only deleted copy/move
> -;     constructors.
> +;     constructors and changes passing/returning of empty records.
>  ;     Default in G++ 8.
>  ;
>  ; Additional positive integers will be assigned as new versions of
> diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
> index 769f1898b22..573dc2cf53a 100644
> --- gcc/config/i386/i386.c
> +++ gcc/config/i386/i386.c
> @@ -7188,6 +7188,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
>    cum->force_bnd_pass = 0;
>    cum->decl = fndecl;
>  
> +  cum->warn_empty = !warn_abi || cum->stdarg;
> +  if (!cum->warn_empty && fntype)
> +    {
> +      function_args_iterator iter;
> +      tree argtype;
> +      bool seen_empty_type = false;
> +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
> +	{
> +	  if (VOID_TYPE_P (argtype))
> +	    break;
> +	  if (TYPE_EMPTY_P (argtype))
> +	    seen_empty_type = true;
> +	  else if (seen_empty_type)
> +	    {
> +	      cum->warn_empty = true;
> +	      break;
> +	    }
> +	}
> +    }
> +
>    if (!TARGET_64BIT)
>      {
>        /* If there are variable arguments, then we won't pass anything
> @@ -9295,6 +9315,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
>    if (POINTER_BOUNDS_TYPE_P (type))
>      return false;
>  
> +  /* Empty records are never passed in memory.  */
> +  if (type && TYPE_EMPTY_P (type))
> +    return false;
> +
>    if (TARGET_64BIT)
>      {
>        if (ix86_function_type_abi (fntype) == MS_ABI)
> @@ -9875,7 +9899,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
>    indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
>    if (indirect_p)
>      type = build_pointer_type (type);
> -  size = int_size_in_bytes (type);
> +  size = arg_int_size_in_bytes (type);
>    rsize = CEIL (size, UNITS_PER_WORD);
>  
>    nat_mode = type_natural_mode (type, NULL, false);
> @@ -28810,6 +28834,47 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
>    return align;
>  }
>  
> +/* Implement TARGET_EMPTY_RECORD_P.  */
> +
> +static bool
> +ix86_is_empty_record (const_tree type)
> +{
> +  if (!TARGET_64BIT)
> +    return false;
> +  return default_is_empty_record (type);
> +}
> +
> +/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
> +
> +static void
> +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
> +{
> +  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
> +
> +  if (!cum->warn_empty)
> +    return;
> +
> +  if (!TYPE_EMPTY_P (type))
> +    return;
> +
> +  tree ctx = cum->decl ? DECL_CONTEXT (cum->decl) : NULL_TREE;
> +  if (ctx != NULL_TREE
> +      && TREE_CODE (ctx) == TRANSLATION_UNIT_DECL
> +      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
> +    return;
> +
> +  /* If the actual size of the type is zero, then there is no change
> +     in how objects of this size are passed.  */
> +  if (int_size_in_bytes (type) == 0)
> +    return;
> +
> +  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
> +	   "changes in -fabi-version=12 (GCC 8)", type);
> +
> +  /* Only warn once.  */
> +  cum->warn_empty = false;
> +}
> +
>  /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
>     the data type, and ALIGN is the alignment that the object would
>     ordinarily have.  */
> @@ -50357,6 +50422,12 @@ ix86_run_selftests (void)
>  #undef TARGET_CONSTANT_ALIGNMENT
>  #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
>  
> +#undef TARGET_EMPTY_RECORD_P
> +#define TARGET_EMPTY_RECORD_P ix86_is_empty_record
> +
> +#undef TARGET_WARN_PARAMETER_PASSING_ABI
> +#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
> +
>  #if CHECKING_P
>  #undef TARGET_RUN_TARGET_SELFTESTS
>  #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
> diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
> index 837906b5169..7f5d245b568 100644
> --- gcc/config/i386/i386.h
> +++ gcc/config/i386/i386.h
> @@ -1633,6 +1633,8 @@ typedef struct ix86_args {
>    int warn_avx;			/* True when we want to warn about AVX ABI.  */
>    int warn_sse;			/* True when we want to warn about SSE ABI.  */
>    int warn_mmx;			/* True when we want to warn about MMX ABI.  */
> +  int warn_empty;		/* True when we want to warn about empty classes
> +				   passing ABI change.  */
>    int sse_regno;		/* next available sse register number */
>    int mmx_words;		/* # mmx words passed so far */
>    int mmx_nregs;		/* # mmx registers available for passing */
> diff --git gcc/cp/class.c gcc/cp/class.c
> index 98e62c6ad45..c6b0349f8e7 100644
> --- gcc/cp/class.c
> +++ gcc/cp/class.c
> @@ -6198,6 +6198,7 @@ layout_class_type (tree t, tree *virtuals_p)
>  	  DECL_CONTEXT (padding_field) = t;
>  	  DECL_ARTIFICIAL (padding_field) = 1;
>  	  DECL_IGNORED_P (padding_field) = 1;
> +	  DECL_PADDING_P (padding_field) = 1;
>  	  layout_nonempty_base_or_field (rli, padding_field,
>  					 NULL_TREE,
>  					 empty_base_offsets);
> diff --git gcc/cp/decl.c gcc/cp/decl.c
> index 0ce8f2d3435..6f0f1baa179 100644
> --- gcc/cp/decl.c
> +++ gcc/cp/decl.c
> @@ -4012,6 +4012,10 @@ cxx_init_decl_processing (void)
>    TREE_PUBLIC (global_namespace) = 1;
>    DECL_CONTEXT (global_namespace)
>      = build_translation_unit_decl (get_identifier (main_input_filename));
> +  /* Remember whether we want the empty class passing ABI change warning
> +     in this TU.  */
> +  TRANSLATION_UNIT_WARN_EMPTY_P (DECL_CONTEXT (global_namespace))
> +    = warn_abi && abi_version_crosses (12);
>    debug_hooks->register_main_translation_unit
>      (DECL_CONTEXT (global_namespace));
>    begin_scope (sk_namespace, global_namespace);
> @@ -12091,7 +12095,10 @@ grokdeclarator (const cp_declarator *declarator,
>  				   FIELD_DECL, unqualified_id, type);
>  		DECL_NONADDRESSABLE_P (decl) = bitfield;
>  		if (bitfield && !unqualified_id)
> -		  TREE_NO_WARNING (decl) = 1;
> +		  {
> +		    TREE_NO_WARNING (decl) = 1;
> +		    DECL_PADDING_P (decl) = 1;
> +		  }
>  
>  		if (storage_class == sc_mutable)
>  		  {
> diff --git gcc/doc/tm.texi gcc/doc/tm.texi
> index 72606f53f1c..f16e73c31b1 100644
> --- gcc/doc/tm.texi
> +++ gcc/doc/tm.texi
> @@ -4559,6 +4559,16 @@ This target hook returns the mode to be used when accessing raw return registers
>  This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
>  @end deftypefn
>  
> +@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
> +This target hook returns true if the type is an empty record.  The default
> +is to return @code{false}.
> +@end deftypefn
> +
> +@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
> +This target hook warns about the change in empty class parameter passing
> +ABI.
> +@end deftypefn
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
> index e7d4ada290f..39f6fcaaa11 100644
> --- gcc/doc/tm.texi.in
> +++ gcc/doc/tm.texi.in
> @@ -3439,6 +3439,10 @@ nothing when you use @option{-freg-struct-return} mode.
>  
>  @hook TARGET_GET_RAW_ARG_MODE
>  
> +@hook TARGET_EMPTY_RECORD_P
> +
> +@hook TARGET_WARN_PARAMETER_PASSING_ABI
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/explow.c gcc/explow.c
> index 662865d2808..72c2e01a44e 100644
> --- gcc/explow.c
> +++ gcc/explow.c
> @@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
>    if (REG_P (val)
>        && GET_MODE (val) == BLKmode)
>      {
> -      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
> +      unsigned HOST_WIDE_INT bytes = arg_int_size_in_bytes (valtype);
>        opt_scalar_int_mode tmpmode;
>  
>        /* int_size_in_bytes can return -1.  We don't need a check here
> diff --git gcc/expr.c gcc/expr.c
> index 649a057e43d..60971066c7a 100644
> --- gcc/expr.c
> +++ gcc/expr.c
> @@ -2749,7 +2749,7 @@ copy_blkmode_to_reg (machine_mode mode_in, tree src)
>  
>    x = expand_normal (src);
>  
> -  bytes = int_size_in_bytes (TREE_TYPE (src));
> +  bytes = arg_int_size_in_bytes (TREE_TYPE (src));
>    if (bytes == 0)
>      return NULL_RTX;
>  
> diff --git gcc/function.c gcc/function.c
> index fe3d9c1bbf3..671a49fa76f 100644
> --- gcc/function.c
> +++ gcc/function.c
> @@ -2528,6 +2528,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
>        return;
>      }
>  
> +  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
> +					    data->passed_type);
> +
>    entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
>  						    data->promoted_mode,
>  						    data->passed_type,
> @@ -4140,8 +4143,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
>  
>    part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
>  
> -  sizetree
> -    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
> +  sizetree = (type
> +	      ? arg_size_in_bytes (type)
> +	      : size_int (GET_MODE_SIZE (passed_mode)));
>    where_pad = targetm.calls.function_arg_padding (passed_mode, type);
>    boundary = targetm.calls.function_arg_boundary (passed_mode, type);
>    round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
> diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
> index 554f9cc9f01..e127e508f97 100644
> --- gcc/lto-streamer-out.c
> +++ gcc/lto-streamer-out.c
> @@ -1073,6 +1073,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
>  	{
>  	  hstate.add_flag (DECL_PACKED (t));
>  	  hstate.add_flag (DECL_NONADDRESSABLE_P (t));
> +	  hstate.add_flag (DECL_PADDING_P (t));
>  	  hstate.add_int (DECL_OFFSET_ALIGN (t));
>  	}
>        else if (code == VAR_DECL)
> @@ -1166,6 +1167,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
>        hstate.commit_flag ();
>        hstate.add_int (TYPE_PRECISION (t));
>        hstate.add_int (TYPE_ALIGN (t));
> +      hstate.add_int (TYPE_EMPTY_P (t));
>      }
>  
>    if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
> diff --git gcc/lto/lto.c gcc/lto/lto.c
> index 63ba73c0dbf..748ef02143c 100644
> --- gcc/lto/lto.c
> +++ gcc/lto/lto.c
> @@ -1087,6 +1087,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>  	{
>  	  compare_values (DECL_PACKED);
>  	  compare_values (DECL_NONADDRESSABLE_P);
> +	  compare_values (DECL_PADDING_P);
>  	  compare_values (DECL_OFFSET_ALIGN);
>  	}
>        else if (code == VAR_DECL)
> @@ -1165,6 +1166,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>  	compare_values (TYPE_NONALIASED_COMPONENT);
>        if (AGGREGATE_TYPE_P (t1))
>  	compare_values (TYPE_TYPELESS_STORAGE);
> +      compare_values (TYPE_EMPTY_P);
>        compare_values (TYPE_PACKED);
>        compare_values (TYPE_RESTRICT);
>        compare_values (TYPE_USER_ALIGN);
> diff --git gcc/stor-layout.c gcc/stor-layout.c
> index 7730ac33e4f..1dd215ec27d 100644
> --- gcc/stor-layout.c
> +++ gcc/stor-layout.c
> @@ -1859,6 +1859,9 @@ finalize_type_size (tree type)
>  	  SET_TYPE_MODE (variant, mode);
>  	}
>      }
> +
> +  /* Handle empty records as per the x86-64 psABI.  */
> +  TYPE_EMPTY_P (type) = targetm.calls.empty_record_p (type);
>  }
>  
>  /* Return a new underlying object for a bitfield started with FIELD.  */
> diff --git gcc/target.def gcc/target.def
> index 577dad8fe86..81aedee80d9 100644
> --- gcc/target.def
> +++ gcc/target.def
> @@ -5055,6 +5055,22 @@ DEFHOOK
>   fixed_size_mode, (int regno),
>   default_get_reg_raw_mode)
>  
> +/* Return true if a type is an empty record.  */
> +DEFHOOK
> +(empty_record_p,
> + "This target hook returns true if the type is an empty record.  The default\n\
> +is to return @code{false}.",
> + bool, (const_tree type),
> + hook_bool_const_tree_false)
> +
> +/* Warn about the change in empty class parameter passing ABI.  */
> +DEFHOOK
> +(warn_parameter_passing_abi,
> + "This target hook warns about the change in empty class parameter passing\n\
> +ABI.",
> + void, (cumulative_args_t ca, tree type),
> + hook_void_CUMULATIVE_ARGS_tree)
> +
>  HOOK_VECTOR_END (calls)
>  
>  DEFHOOK
> diff --git gcc/targhooks.c gcc/targhooks.c
> index dad1e109d23..0edc57b0a15 100644
> --- gcc/targhooks.c
> +++ gcc/targhooks.c
> @@ -756,6 +756,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
>  }
>  
>  void
> +hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
> +				tree ATTRIBUTE_UNUSED)
> +{
> +}
> +
> +void
>  default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
>  			      machine_mode mode ATTRIBUTE_UNUSED,
>  			      const_tree type ATTRIBUTE_UNUSED,
> @@ -2108,6 +2114,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>    /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
>       requires greater alignment, we must perform dynamic alignment.  */
>    if (boundary > align
> +      && !TYPE_EMPTY_P (type)
>        && !integer_zerop (TYPE_SIZE (type)))
>      {
>        t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
> @@ -2134,7 +2141,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>      }
>  
>    /* Compute the rounded size of the type.  */
> -  type_size = size_in_bytes (type);
> +  type_size = arg_size_in_bytes (type);
>    rounded_size = round_up (type_size, align);
>  
>    /* Reduce rounded_size so it's sharable with the postqueue.  */
> diff --git gcc/targhooks.h gcc/targhooks.h
> index 15bbf5cdf24..e431934cd60 100644
> --- gcc/targhooks.h
> +++ gcc/targhooks.h
> @@ -135,6 +135,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
>    (cumulative_args_t, machine_mode, const_tree, bool);
>  extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
>    (cumulative_args_t, machine_mode, tree, bool);
> +extern void hook_void_CUMULATIVE_ARGS_tree
> +  (cumulative_args_t, tree);
>  extern const char *hook_invalid_arg_for_unprototyped_fn
>    (const_tree, const_tree, const_tree);
>  extern void default_function_arg_advance
> diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
> index e69de29bb2d..20d85ff873e 100644
> --- gcc/testsuite/g++.dg/abi/empty12.C
> +++ gcc/testsuite/g++.dg/abi/empty12.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty12a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty12.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty12.h
> +++ gcc/testsuite/g++.dg/abi/empty12.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
> index e69de29bb2d..34a25bad75d 100644
> --- gcc/testsuite/g++.dg/abi/empty12a.c
> +++ gcc/testsuite/g++.dg/abi/empty12a.c
> @@ -0,0 +1,6 @@
> +#include "empty12.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
> index e69de29bb2d..0cb9a373e35 100644
> --- gcc/testsuite/g++.dg/abi/empty13.C
> +++ gcc/testsuite/g++.dg/abi/empty13.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-x c -fabi-version=11" }
> +// { dg-additional-sources "empty13a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty13.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty13.h
> +++ gcc/testsuite/g++.dg/abi/empty13.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
> index e69de29bb2d..b4303a63826 100644
> --- gcc/testsuite/g++.dg/abi/empty13a.c
> +++ gcc/testsuite/g++.dg/abi/empty13a.c
> @@ -0,0 +1,6 @@
> +#include "empty13.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 == -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
> index e69de29bb2d..2868d8ad3f3 100644
> --- gcc/testsuite/g++.dg/abi/empty14.C
> +++ gcc/testsuite/g++.dg/abi/empty14.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty14a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty14.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
> index e69de29bb2d..5842279cf37 100644
> --- gcc/testsuite/g++.dg/abi/empty14.h
> +++ gcc/testsuite/g++.dg/abi/empty14.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[140]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
> index e69de29bb2d..8b3d7800c36 100644
> --- gcc/testsuite/g++.dg/abi/empty14a.c
> +++ gcc/testsuite/g++.dg/abi/empty14a.c
> @@ -0,0 +1,6 @@
> +#include "empty14.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
> index e69de29bb2d..12385f78c78 100644
> --- gcc/testsuite/g++.dg/abi/empty15.C
> +++ gcc/testsuite/g++.dg/abi/empty15.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty15a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty15.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
> index e69de29bb2d..1c6f26f5ae8 100644
> --- gcc/testsuite/g++.dg/abi/empty15.h
> +++ gcc/testsuite/g++.dg/abi/empty15.h
> @@ -0,0 +1,30 @@
> +struct A1 {};
> +struct A2 {};
> +struct B1 { struct A1 a; struct A2 b; };
> +struct B2 { struct A1 a; struct A2 b; };
> +struct C1 { struct B1 a; struct B2 b; };
> +struct C2 { struct B1 a; struct B2 b; };
> +struct D1 { struct C1 a; struct C2 b; };
> +struct D2 { struct C1 a; struct C2 b; };
> +struct E1 { struct D1 a; struct D2 b; };
> +struct E2 { struct D1 a; struct D2 b; };
> +struct F1 { struct E1 a; struct E2 b; };
> +struct F2 { struct E1 a; struct E2 b; };
> +struct G1 { struct F1 a; struct F2 b; };
> +struct G2 { struct F1 a; struct F2 b; };
> +struct H1 { struct G1 a; struct G2 b; };
> +struct H2 { struct G1 a; struct G2 b; };
> +struct I1 { struct H1 a; struct H2 b; };
> +struct I2 { struct H1 a; struct H2 b; };
> +struct J1 { struct I1 a; struct I2 b; };
> +struct J2 { struct I1 a; struct I2 b; };
> +struct dummy { struct J1 a; struct J2 b; };
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
> index e69de29bb2d..325b2c5ba09 100644
> --- gcc/testsuite/g++.dg/abi/empty15a.c
> +++ gcc/testsuite/g++.dg/abi/empty15a.c
> @@ -0,0 +1,6 @@
> +#include "empty15.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
> index e69de29bb2d..1ca52f9011e 100644
> --- gcc/testsuite/g++.dg/abi/empty16.C
> +++ gcc/testsuite/g++.dg/abi/empty16.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty16a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty16.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
> index e69de29bb2d..7552ae06576 100644
> --- gcc/testsuite/g++.dg/abi/empty16.h
> +++ gcc/testsuite/g++.dg/abi/empty16.h
> @@ -0,0 +1,16 @@
> +#ifdef __cplusplus
> +struct A1 {};
> +struct A2 {};
> +struct dummy : A1, A2 {} ;
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
> index e69de29bb2d..6cb7fbccecc 100644
> --- gcc/testsuite/g++.dg/abi/empty16a.c
> +++ gcc/testsuite/g++.dg/abi/empty16a.c
> @@ -0,0 +1,6 @@
> +#include "empty16.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
> index e69de29bb2d..d386e5481af 100644
> --- gcc/testsuite/g++.dg/abi/empty17.C
> +++ gcc/testsuite/g++.dg/abi/empty17.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty17a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty17.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
> index e69de29bb2d..9cf72baca2e 100644
> --- gcc/testsuite/g++.dg/abi/empty17.h
> +++ gcc/testsuite/g++.dg/abi/empty17.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 15;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 15;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 15;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
> index e69de29bb2d..24408fde09c 100644
> --- gcc/testsuite/g++.dg/abi/empty17a.c
> +++ gcc/testsuite/g++.dg/abi/empty17a.c
> @@ -0,0 +1,6 @@
> +#include "empty17.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
> index e69de29bb2d..be69c6a2115 100644
> --- gcc/testsuite/g++.dg/abi/empty18.C
> +++ gcc/testsuite/g++.dg/abi/empty18.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty18a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty18.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
> index e69de29bb2d..86e7ecdd211 100644
> --- gcc/testsuite/g++.dg/abi/empty18.h
> +++ gcc/testsuite/g++.dg/abi/empty18.h
> @@ -0,0 +1,9 @@
> +struct dummy { int d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
> index e69de29bb2d..902860bdc01 100644
> --- gcc/testsuite/g++.dg/abi/empty18a.c
> +++ gcc/testsuite/g++.dg/abi/empty18a.c
> @@ -0,0 +1,6 @@
> +#include "empty18.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
> index e69de29bb2d..84f5b75558b 100644
> --- gcc/testsuite/g++.dg/abi/empty19.C
> +++ gcc/testsuite/g++.dg/abi/empty19.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty19a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty19.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
> index e69de29bb2d..616b87bdd93 100644
> --- gcc/testsuite/g++.dg/abi/empty19.h
> +++ gcc/testsuite/g++.dg/abi/empty19.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
> index e69de29bb2d..767b1eb7320 100644
> --- gcc/testsuite/g++.dg/abi/empty19a.c
> +++ gcc/testsuite/g++.dg/abi/empty19a.c
> @@ -0,0 +1,6 @@
> +#include "empty19.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
> index e69de29bb2d..5022033f669 100644
> --- gcc/testsuite/g++.dg/abi/empty20.C
> +++ gcc/testsuite/g++.dg/abi/empty20.C
> @@ -0,0 +1,19 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11 -O0" }
> +
> +struct A { };
> +
> +void f(A, A) { }	// No warning, trailing parms all empty
> +void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +__attribute__ ((always_inline))
> +inline void f(A a, int i) // No warning, always inlined
> +{
> +  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> +int main()
> +{
> +  A a;
> +  f(a,a);
> +  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(a,42);
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
> index e69de29bb2d..3b2e3b836b1 100644
> --- gcc/testsuite/g++.dg/abi/empty21.C
> +++ gcc/testsuite/g++.dg/abi/empty21.C
> @@ -0,0 +1,23 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct A { };
> +
> +void f(int i, ...)
> +{
> +  va_list ap;
> +  va_start (ap, i);
> +  if (i >= 1)
> +    va_arg (ap, A);
> +  if (i >= 2)
> +    va_arg (ap, int);
> +}
> +
> +int main()
> +{
> +  f(0);
> +  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
> index e69de29bb2d..f4f4a02bf31 100644
> --- gcc/testsuite/g++.dg/abi/empty22.C
> +++ gcc/testsuite/g++.dg/abi/empty22.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty22a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty22.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
> index e69de29bb2d..8d54dc74519 100644
> --- gcc/testsuite/g++.dg/abi/empty22.h
> +++ gcc/testsuite/g++.dg/abi/empty22.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 0;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 0;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 0;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
> index e69de29bb2d..7606c524263 100644
> --- gcc/testsuite/g++.dg/abi/empty22a.c
> +++ gcc/testsuite/g++.dg/abi/empty22a.c
> @@ -0,0 +1,6 @@
> +#include "empty22.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
> index e69de29bb2d..dbeda81fb24 100644
> --- gcc/testsuite/g++.dg/abi/empty23.C
> +++ gcc/testsuite/g++.dg/abi/empty23.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[0];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
> index e69de29bb2d..822ced1ef50 100644
> --- gcc/testsuite/g++.dg/abi/empty24.C
> +++ gcc/testsuite/g++.dg/abi/empty24.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty25.C gcc/testsuite/g++.dg/abi/empty25.C
> index e69de29bb2d..da6ef51ff0d 100644
> --- gcc/testsuite/g++.dg/abi/empty25.C
> +++ gcc/testsuite/g++.dg/abi/empty25.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty25a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty25.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-bogus "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty25.h gcc/testsuite/g++.dg/abi/empty25.h
> index e69de29bb2d..2f22fd5e505 100644
> --- gcc/testsuite/g++.dg/abi/empty25.h
> +++ gcc/testsuite/g++.dg/abi/empty25.h
> @@ -0,0 +1,18 @@
> +#ifdef __cplusplus
> +struct dummy
> +{
> +  virtual void bar (void) { }
> +  unsigned int : 15;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty25a.c gcc/testsuite/g++.dg/abi/empty25a.c
> index e69de29bb2d..8c16e453c75 100644
> --- gcc/testsuite/g++.dg/abi/empty25a.c
> +++ gcc/testsuite/g++.dg/abi/empty25a.c
> @@ -0,0 +1,6 @@
> +#include "empty25.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty26.C gcc/testsuite/g++.dg/abi/empty26.C
> index e69de29bb2d..ab2f54d8dab 100644
> --- gcc/testsuite/g++.dg/abi/empty26.C
> +++ gcc/testsuite/g++.dg/abi/empty26.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty26a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty26.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty26.h gcc/testsuite/g++.dg/abi/empty26.h
> index e69de29bb2d..8d54dc74519 100644
> --- gcc/testsuite/g++.dg/abi/empty26.h
> +++ gcc/testsuite/g++.dg/abi/empty26.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 0;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 0;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 0;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty26a.c gcc/testsuite/g++.dg/abi/empty26a.c
> index e69de29bb2d..bc0ae47ba2c 100644
> --- gcc/testsuite/g++.dg/abi/empty26a.c
> +++ gcc/testsuite/g++.dg/abi/empty26a.c
> @@ -0,0 +1,6 @@
> +#include "empty26.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
> index e69de29bb2d..59447890cec 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-1.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-1.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
> index e69de29bb2d..960cc2307d1 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-10.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-10.C
> @@ -0,0 +1,50 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0 { };
> +struct dummy1 { };
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
> index e69de29bb2d..14cd6d0ff3d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-11.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-11.C
> @@ -0,0 +1,56 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +  void bar (void);
> +};
> +struct dummy1
> +{
> +  void foo (void);
> +};
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
> index e69de29bb2d..09917547930 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-12.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-12.C
> @@ -0,0 +1,57 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +};
> +struct dummy1
> +{
> +  unsigned : 15;
> +};
> +struct dummy : dummy0, dummy1
> +{
> +};
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
> index e69de29bb2d..1c6c3eb8f01 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-2.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-2.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...) // { dg-warning "empty" }
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
> index e69de29bb2d..4157e553b6b 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-3.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-3.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
> index e69de29bb2d..266f67a537d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-4.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-4.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -fabi-version=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count == 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
> index e69de29bb2d..fe838750f55 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-5.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-5.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; struct dummy j; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
> index e69de29bb2d..6e08c8f06fa 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-6.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-6.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i1; struct dummy i2; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
> index e69de29bb2d..3b8b8ba6f35 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-7.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-7.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i[120]; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
> index e69de29bb2d..a1ffb64ef02 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-8.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-8.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct{} a[7][3]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
> index e69de29bb2d..393f02b62f0 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-9.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-9.C
> @@ -0,0 +1,28 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct A1 {}; struct A2 {};
> +struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
> +struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
> +struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
> +struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
> +struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
> +struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
> +struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
> +struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
> +struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
> +struct dummy { J1 a; J2 b; };
> +
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
> index e69de29bb2d..1354fc497b5 100644
> --- gcc/testsuite/g++.dg/abi/pr68355.C
> +++ gcc/testsuite/g++.dg/abi/pr68355.C
> @@ -0,0 +1,24 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +template<typename _Tp, _Tp __v>
> +struct integral_constant
> +{
> +  static constexpr _Tp value = __v;
> +  typedef _Tp value_type;
> +  typedef integral_constant<_Tp, __v> type;
> +  constexpr operator value_type() const { return value; }
> +};
> +
> +typedef integral_constant<bool, true> true_type;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  true_type y;
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
> diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
> index e69de29bb2d..a0a598c0029 100644
> --- gcc/testsuite/g++.dg/lto/pr60336_0.C
> +++ gcc/testsuite/g++.dg/lto/pr60336_0.C
> @@ -0,0 +1,47 @@
> +// { dg-lto-do run }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/tree-core.h gcc/tree-core.h
> index f74f1453de6..13ff5e3db71 100644
> --- gcc/tree-core.h
> +++ gcc/tree-core.h
> @@ -1531,7 +1531,8 @@ struct GTY(()) tree_type_common {
>    unsigned align : 6;
>    unsigned warn_if_not_align : 6;
>    unsigned typeless_storage : 1;
> -  unsigned spare : 18;
> +  unsigned empty_flag : 1;
> +  unsigned spare : 17;
>  
>    alias_set_type alias_set;
>    tree pointer_to;
> @@ -1609,7 +1610,8 @@ struct GTY(()) tree_decl_common {
>    unsigned lang_flag_7 : 1;
>    unsigned lang_flag_8 : 1;
>  
> -  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER.  */
> +  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER
> +     IN TRANSLATION_UNIT_DECL, this is TRANSLATION_UNIT_WARN_EMPTY_P.  */
>    unsigned decl_flag_0 : 1;
>    /* In FIELD_DECL, this is DECL_BIT_FIELD
>       In VAR_DECL and FUNCTION_DECL, this is DECL_EXTERNAL.
> @@ -1619,7 +1621,7 @@ struct GTY(()) tree_decl_common {
>       In VAR_DECL, PARM_DECL and RESULT_DECL, this is
>       DECL_HAS_VALUE_EXPR_P.  */
>    unsigned decl_flag_2 : 1;
> -  /* 1 bit unused.  */
> +  /* In FIELD_DECL, this is DECL_PADDING_P.  */
>    unsigned decl_flag_3 : 1;
>    /* Logically, these two would go in a theoretical base shared by var and
>       parm decl. */
> diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
> index baf0c5bf837..36402c6a39c 100644
> --- gcc/tree-streamer-in.c
> +++ gcc/tree-streamer-in.c
> @@ -251,6 +251,7 @@ unpack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
>      {
>        DECL_PACKED (expr) = (unsigned) bp_unpack_value (bp, 1);
>        DECL_NONADDRESSABLE_P (expr) = (unsigned) bp_unpack_value (bp, 1);
> +      DECL_PADDING_P (expr) = (unsigned) bp_unpack_value (bp, 1);
>        expr->decl_common.off_align = bp_unpack_value (bp, 8);
>      }
>  
> @@ -381,6 +382,7 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>      TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
>    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_PRECISION (expr) = bp_unpack_var_len_unsigned (bp);
>    SET_TYPE_ALIGN (expr, bp_unpack_var_len_unsigned (bp));
>  #ifdef ACCEL_COMPILER
> diff --git gcc/tree-streamer-out.c gcc/tree-streamer-out.c
> index 7f52d455f5e..08c58a4709d 100644
> --- gcc/tree-streamer-out.c
> +++ gcc/tree-streamer-out.c
> @@ -211,6 +211,7 @@ pack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
>      {
>        bp_pack_value (bp, DECL_PACKED (expr), 1);
>        bp_pack_value (bp, DECL_NONADDRESSABLE_P (expr), 1);
> +      bp_pack_value (bp, DECL_PADDING_P (expr), 1);
>        bp_pack_value (bp, expr->decl_common.off_align, 8);
>      }
>  
> @@ -330,6 +331,7 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>      bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
>    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_var_len_unsigned (bp, TYPE_PRECISION (expr));
>    bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
>  }
> diff --git gcc/tree.c gcc/tree.c
> index 28e157f5fd2..e400c45d484 100644
> --- gcc/tree.c
> +++ gcc/tree.c
> @@ -13811,6 +13811,63 @@ get_nonnull_args (const_tree fntype)
>    return argmap;
>  }
>  
> +/* Returns true if TYPE is a type where it and all of its subobjects
> +   (recursively) are of structure, union, or array type.  */
> +
> +static bool
> +default_is_empty_type (tree type)
> +{
> +  if (RECORD_OR_UNION_TYPE_P (type))
> +    {
> +      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
> +	if (TREE_CODE (field) == FIELD_DECL
> +	    && !DECL_PADDING_P (field)
> +	    && !default_is_empty_type (TREE_TYPE (field)))
> +	  return false;
> +      return true;
> +    }
> +  else if (TREE_CODE (type) == ARRAY_TYPE)
> +    return (integer_minus_onep (array_type_nelts (type))
> +	    || TYPE_DOMAIN (type) == NULL_TREE
> +	    || default_is_empty_type (TREE_TYPE (type)));
> +  return false;
> +}
> +
> +/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
> +   that shouldn't be passed via stack.  */
> +
> +bool
> +default_is_empty_record (const_tree type)
> +{
> +  if (!abi_version_at_least (12))
> +    return false;
> +
> +  if (type == error_mark_node)
> +    return false;
> +
> +  /* The front end should have turned this into an invisible reference
> +     already.  */
> +  gcc_assert (!TREE_ADDRESSABLE (type));
> +
> +  return default_is_empty_type (TYPE_MAIN_VARIANT (type));
> +}
> +
> +/* Like int_size_in_bytes, but handle empty records specially.  */
> +
> +HOST_WIDE_INT
> +arg_int_size_in_bytes (const_tree type)
> +{
> +  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
> +}
> +
> +/* Like size_in_bytes, but handle empty records specially.  */
> +
> +tree
> +arg_size_in_bytes (const_tree type)
> +{
> +  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
> +}
> +
>  /* List of pointer types used to declare builtins before we have seen their
>     real declaration.
>  
> diff --git gcc/tree.h gcc/tree.h
> index 277aa919780..fcd015edbd2 100644
> --- gcc/tree.h
> +++ gcc/tree.h
> @@ -696,6 +696,14 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
>     emitted.  */
>  #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
>  
> +/* Nonzero if we should warn about the change in empty class parameter
> +   passing ABI in this TU.  */
> +#define TRANSLATION_UNIT_WARN_EMPTY_P(NODE) \
> +  (TRANSLATION_UNIT_DECL_CHECK (NODE)->decl_common.decl_flag_0)
> +
> +/* Nonzero if this type is "empty" according to the particular psABI.  */
> +#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
> +
>  /* Used to indicate that this TYPE represents a compiler-generated entity.  */
>  #define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
>  
> @@ -2619,6 +2627,12 @@ extern void decl_value_expr_insert (tree, tree);
>  #define DECL_NONADDRESSABLE_P(NODE) \
>    (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_2)
>  
> +/* Used in a FIELD_DECL to indicate that this field is padding for
> +   the purpose of determining whether the record this field is a member
> +   of is considered empty.  */
> +#define DECL_PADDING_P(NODE) \
> +  (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_3)
> +
>  /* A numeric unique identifier for a LABEL_DECL.  The UID allocation is
>     dense, unique within any one function, and may be used to index arrays.
>     If the value is -1, then no UID has been assigned.  */
> @@ -5428,6 +5442,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
>  
>  extern bool nonnull_arg_p (const_tree);
>  extern bool is_redundant_typedef (const_tree);
> +extern bool default_is_empty_record (const_tree);
> +extern HOST_WIDE_INT arg_int_size_in_bytes (const_tree);
> +extern tree arg_size_in_bytes (const_tree);
>  
>  extern location_t
>  set_source_range (tree expr, location_t start, location_t finish);
> 
> 	Marek
> 
>
Jason Merrill Nov. 10, 2017, 4:16 p.m. UTC | #23
On 11/09/2017 10:26 AM, Richard Biener wrote:
>> Moving TYPE_EMPTY_P to finalize_type_size revealed a bug in Cilk+, it was
>> bogusly (I believe) setting TREE_ADDRESSABLE, so the assert fired.  Since
>> Cilk+ is being deprecated and the Cilk+ testsuite still passes, I'm not
>> too worried about this change.

Ah, presumably C++ classes aren't hitting the assert just because they 
set TREE_ADDRESSABLE after finalize_type_size, so the assert is wrong. 
Let's drop the assert and the Cilk+ change.

>> So if you're fine with the DECL_PADDING_P change, all that remains is to
>> check whether this patch is not changing the ABI for other languages than
>> C++.  I suppose one way to check that could be to do sth like
>>
>>    if (strncmp (lang_hooks.name, "GNU C++", 7))
>>      {FILE *f=fopen("/tmp/A", "a");fprintf(f,"%s\n",main_input_filename);fclose(f);}
>>
>> and compare the assembly of the files it prints?  But there were thousands of
>> them I think :(.
> 
> Shouldn't most of GCCs own object files (and target library object files)
> compare 1:1 before and after the patch?  After all it should _only_ affect
> parameter passing of empty aggregates (and the files the patch changes
> of course)?

That makes sense to me.

>> @@ -9295,6 +9315,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
...
>> +  /* Empty records are never passed in memory.  */
>> +  if (type && TYPE_EMPTY_P (type))
>> +    return false;

Since the TYPE_EMPTY_P flag is in target-independent code, let's check 
it in aggregate_value_p rather than here.

>> +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
...
>> +  tree ctx = cum->decl ? DECL_CONTEXT (cum->decl) : NULL_TREE;
>> +  if (ctx != NULL_TREE
>> +      && TREE_CODE (ctx) == TRANSLATION_UNIT_DECL
>> +      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
>> +    return;

This doesn't handle empty classes within a namespace/class/function. 
Maybe move get_ultimate_context out of dwarf2out.c and use that?

>> +/* Used in a FIELD_DECL to indicate that this field is padding for
>> +   the purpose of determining whether the record this field is a member
>> +   of is considered empty.  */

I wouldn't mention "empty" here; this indicates that the field is 
padding, not data.  A target might use this information to avoid passing 
this field even if the class contains data as well.

Jason
Marek Polacek Nov. 13, 2017, 10:32 a.m. UTC | #24
On Fri, Nov 10, 2017 at 11:16:04AM -0500, Jason Merrill wrote:
> On 11/09/2017 10:26 AM, Richard Biener wrote:
> > > Moving TYPE_EMPTY_P to finalize_type_size revealed a bug in Cilk+, it was
> > > bogusly (I believe) setting TREE_ADDRESSABLE, so the assert fired.  Since
> > > Cilk+ is being deprecated and the Cilk+ testsuite still passes, I'm not
> > > too worried about this change.
> 
> Ah, presumably C++ classes aren't hitting the assert just because they set
> TREE_ADDRESSABLE after finalize_type_size, so the assert is wrong. Let's
> drop the assert and the Cilk+ change.
 
Ah, ok, done.

> > > So if you're fine with the DECL_PADDING_P change, all that remains is to
> > > check whether this patch is not changing the ABI for other languages than
> > > C++.  I suppose one way to check that could be to do sth like
> > > 
> > >    if (strncmp (lang_hooks.name, "GNU C++", 7))
> > >      {FILE *f=fopen("/tmp/A", "a");fprintf(f,"%s\n",main_input_filename);fclose(f);}
> > > 
> > > and compare the assembly of the files it prints?  But there were thousands of
> > > them I think :(.
> > 
> > Shouldn't most of GCCs own object files (and target library object files)
> > compare 1:1 before and after the patch?  After all it should _only_ affect
> > parameter passing of empty aggregates (and the files the patch changes
> > of course)?
> 
> That makes sense to me.

Unfortunately, it's not what I see.  I ran two bootstraps, with and without the
patch.  Then I stripped all the .o files and ran cmp on them, but many of them
differ.  objdump -dr reveals why -- I see changes like

  mov    $0xe02,%ecx
vs
  mov    $0xdf6,%ecx

That's probably some sizeof changes?  Any other ideas? :/  Somehow ignore
these and only look if there are new pushes or similar?

> > > @@ -9295,6 +9315,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
> ...
> > > +  /* Empty records are never passed in memory.  */
> > > +  if (type && TYPE_EMPTY_P (type))
> > > +    return false;
> 
> Since the TYPE_EMPTY_P flag is in target-independent code, let's check it in
> aggregate_value_p rather than here.

Done.

> > > +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
> ...
> > > +  tree ctx = cum->decl ? DECL_CONTEXT (cum->decl) : NULL_TREE;
> > > +  if (ctx != NULL_TREE
> > > +      && TREE_CODE (ctx) == TRANSLATION_UNIT_DECL
> > > +      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
> > > +    return;
> 
> This doesn't handle empty classes within a namespace/class/function. Maybe
> move get_ultimate_context out of dwarf2out.c and use that?

Thanks for catching that.  That would result in bogus warnings.  Fixed
as suggested though I'm not sure if we need the BLOCK handling in
get_ultimate_context.  I've also added new testcases.

> > > +/* Used in a FIELD_DECL to indicate that this field is padding for
> > > +   the purpose of determining whether the record this field is a member
> > > +   of is considered empty.  */
> 
> I wouldn't mention "empty" here; this indicates that the field is padding,
> not data.  A target might use this information to avoid passing this field
> even if the class contains data as well.

Okay, fixed.

Bootstrapped/regtested on x86_64-linux.

2017-11-14  Marek Polacek  <polacek@redhat.com>
	    H.J. Lu  <hongjiu.lu@intel.com>
	    Jason Merrill  <jason@redhat.com>

	PR c++/60336
	PR middle-end/67239
	PR target/68355
	* c-decl.c (grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.

	* class.c (layout_class_type): Set DECL_PADDING_P on padding.
	* decl.c (cxx_init_decl_processing): Set TRANSLATION_UNIT_WARN_EMPTY_P.
	(grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.

	* lto.c (compare_tree_sccs_1): Compare TYPE_EMPTY_P and DECL_PADDING_P.

	* calls.c (initialize_argument_information): Call
	warn_parameter_passing_abi target hook.
	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
	argument onto stack.
	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
	* common.opt: Update -fabi-version description.
	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
	int_size_in_bytes.
	(ix86_is_empty_record): New function.
	(ix86_warn_parameter_passing_abi): New function.
	(TARGET_EMPTY_RECORD_P): Redefine.
	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
	* doc/tm.texi: Regenerated.
	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
	* dwarf2out.c (get_ultimate_context): Move to tree.c.
	* explow.c (hard_function_value): Call arg_int_size_in_bytes
	instead of int_size_in_bytes.
	* expr.c (copy_blkmode_to_reg): Likewise.
	* function.c (aggregate_value_p): Return 0 for empty types.
	(assign_parm_find_entry_rtl): Call warn_parameter_passing_abi target hook.
	(locate_and_pad_parm): Call arg size_in_bytes instead
	size_in_bytes.
	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and DECL_PADDING_P.
	* stor-layout.c (finalize_type_size): Set TYPE_EMPTY_P.
	* target.def (empty_record_p, warn_parameter_passing_abi): New target
	hooks.
	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
	(std_gimplify_va_arg_expr): Skip empty records.  Call
	arg_size_in_bytes instead size_in_bytes.
	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
	* tree-core.h (tree_type_common): Add empty_flag.
	(tree_decl_common): Update comments.
	* tree-streamer-in.c (unpack_ts_decl_common_value_fields): Stream
	DECL_PADDING_P.
	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
	* tree-streamer-out.c (pack_ts_decl_common_value_fields): Stream
	DECL_PADDING_P.
	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
	* tree.c (default_is_empty_type): New function.
	(default_is_empty_record): New function.
	(arg_int_size_in_bytes): New function.
	(arg_size_in_bytes): New function.
	(get_ultimate_context): New function.
	* tree.h: Define TYPE_EMPTY_P, DECL_PADDING_P and
	TRANSLATION_UNIT_WARN_EMPTY_P.
	(default_is_empty_record, arg_int_size_in_bytes,
	arg_size_in_bytes, get_ultimate_context): Declare.

	* g++.dg/abi/empty12.C: New test.
	* g++.dg/abi/empty12.h: New test.
	* g++.dg/abi/empty12a.c: New test.
	* g++.dg/abi/empty13.C: New test.
	* g++.dg/abi/empty13.h: New test.
	* g++.dg/abi/empty13a.c: New test.
	* g++.dg/abi/empty14.C: New test.
	* g++.dg/abi/empty14.h: New test.
	* g++.dg/abi/empty14a.c: New test.
	* g++.dg/abi/empty15.C: New test.
	* g++.dg/abi/empty15.h: New test.
	* g++.dg/abi/empty15a.c: New test.
	* g++.dg/abi/empty16.C: New test.
	* g++.dg/abi/empty16.h: New test.
	* g++.dg/abi/empty16a.c: New test.
	* g++.dg/abi/empty17.C: New test.
	* g++.dg/abi/empty17.h: New test.
	* g++.dg/abi/empty17a.c: New test.
	* g++.dg/abi/empty18.C: New test.
	* g++.dg/abi/empty18.h: New test.
	* g++.dg/abi/empty18a.c: New test.
	* g++.dg/abi/empty19.C: New test.
	* g++.dg/abi/empty19.h: New test.
	* g++.dg/abi/empty19a.c: New test.
	* g++.dg/abi/empty20.C: New test.
	* g++.dg/abi/empty21.C: New test.
	* g++.dg/abi/empty22.C: New test.
	* g++.dg/abi/empty22.h: New test.
	* g++.dg/abi/empty22a.c: New test.
	* g++.dg/abi/empty23.C: New test.
	* g++.dg/abi/empty24.C: New test.
	* g++.dg/abi/empty25.C: New test.
	* g++.dg/abi/empty25.h: New test.
	* g++.dg/abi/empty25a.c: New test.
	* g++.dg/abi/empty26.C: New test.
	* g++.dg/abi/empty26.h: New test.
	* g++.dg/abi/empty26a.c: New test.
	* g++.dg/abi/empty27.C: New test.
	* g++.dg/abi/empty28.C: New test.
	* g++.dg/abi/pr60336-1.C: New test.
	* g++.dg/abi/pr60336-10.C: New test.
	* g++.dg/abi/pr60336-11.C: New test.
	* g++.dg/abi/pr60336-12.C: New test.
	* g++.dg/abi/pr60336-2.C: New test.
	* g++.dg/abi/pr60336-3.C: New test.
	* g++.dg/abi/pr60336-4.C: New test.
	* g++.dg/abi/pr60336-5.C: New test.
	* g++.dg/abi/pr60336-6.C: New test.
	* g++.dg/abi/pr60336-7.C: New test.
	* g++.dg/abi/pr60336-8.C: New test.
	* g++.dg/abi/pr60336-9.C: New test.
	* g++.dg/abi/pr68355.C: New test.
	* g++.dg/lto/pr60336_0.C: New test.

diff --git gcc/c/c-decl.c gcc/c/c-decl.c
index d95a2b6ea4f..dd01631e0bd 100644
--- gcc/c/c-decl.c
+++ gcc/c/c-decl.c
@@ -6801,7 +6801,10 @@ grokdeclarator (const struct c_declarator *declarator,
 			   FIELD_DECL, declarator->u.id, type);
 	DECL_NONADDRESSABLE_P (decl) = bitfield;
 	if (bitfield && !declarator->u.id)
-	  TREE_NO_WARNING (decl) = 1;
+	  {
+	    TREE_NO_WARNING (decl) = 1;
+	    DECL_PADDING_P (decl) = 1;
+	  }
 
 	if (size_varies)
 	  C_DECL_VARIABLE_SIZE (decl) = 1;
diff --git gcc/calls.c gcc/calls.c
index 3730f43c7a9..f9a6a5cce13 100644
--- gcc/calls.c
+++ gcc/calls.c
@@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       args[i].unsignedp = unsignedp;
       args[i].mode = mode;
 
+      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
+
       args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
 						argpos < n_named_args);
 
@@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	 Note that in C the default argument promotions
 	 will prevent such mismatches.  */
 
-      size = GET_MODE_SIZE (arg->mode);
+      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
+	size = 0;
+      else
+	size = GET_MODE_SIZE (arg->mode);
+
       /* Compute how much space the push instruction will push.
 	 On many machines, pushing a byte will advance the stack
 	 pointer by a halfword.  */
@@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 
       /* This isn't already where we want it on the stack, so put it there.
 	 This can either be done with push or copy insns.  */
-      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
-		      parm_align, partial, reg, used - size, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
+      if (used
+	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
+			      NULL_RTX, parm_align, partial, reg, used - size,
+			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
+			      reg_parm_stack_space,
+			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
 	sibcall_failure = 1;
 
       /* Unless this is a partially-in-register argument, the argument is now
@@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
 	     for BLKmode is careful to avoid it.  */
 	  excess = (arg->locate.size.constant
-		    - int_size_in_bytes (TREE_TYPE (pval))
+		    - arg_int_size_in_bytes (TREE_TYPE (pval))
 		    + partial);
-	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
+	  size_rtx = expand_expr (arg_size_in_bytes (TREE_TYPE (pval)),
 				  NULL_RTX, TYPE_MODE (sizetype),
 				  EXPAND_NORMAL);
 	}
@@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	    }
 	}
 
-      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
-		      parm_align, partial, reg, excess, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
+      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
+	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
+			parm_align, partial, reg, excess, argblock,
+			ARGS_SIZE_RTX (arg->locate.offset),
+			reg_parm_stack_space,
+			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
 
       /* Unless this is a partially-in-register argument, the argument is now
 	 in the stack.
@@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
   if (TREE_ADDRESSABLE (type))
     return true;
 
+  if (TYPE_EMPTY_P (type))
+    return false;
+
   /* If the padding and mode of the type is such that a copy into
      a register would put it into the wrong part of the register.  */
   if (mode == BLKmode
diff --git gcc/common.opt gcc/common.opt
index f8f2ed3db8a..28a0185f0cf 100644
--- gcc/common.opt
+++ gcc/common.opt
@@ -936,7 +936,7 @@ Driver Undocumented
 ;     Default in G++ 7.
 ;
 ; 12: Corrects the calling convention for classes with only deleted copy/move
-;     constructors.
+;     constructors and changes passing/returning of empty records.
 ;     Default in G++ 8.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
index 769f1898b22..b5f3045d866 100644
--- gcc/config/i386/i386.c
+++ gcc/config/i386/i386.c
@@ -7188,6 +7188,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->force_bnd_pass = 0;
   cum->decl = fndecl;
 
+  cum->warn_empty = !warn_abi || cum->stdarg;
+  if (!cum->warn_empty && fntype)
+    {
+      function_args_iterator iter;
+      tree argtype;
+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+	{
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  if (TYPE_EMPTY_P (argtype))
+	    seen_empty_type = true;
+	  else if (seen_empty_type)
+	    {
+	      cum->warn_empty = true;
+	      break;
+	    }
+	}
+    }
+
   if (!TARGET_64BIT)
     {
       /* If there are variable arguments, then we won't pass anything
@@ -9875,7 +9895,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
   indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
   if (indirect_p)
     type = build_pointer_type (type);
-  size = int_size_in_bytes (type);
+  size = arg_int_size_in_bytes (type);
   rsize = CEIL (size, UNITS_PER_WORD);
 
   nat_mode = type_natural_mode (type, NULL, false);
@@ -28810,6 +28830,46 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Implement TARGET_EMPTY_RECORD_P.  */
+
+static bool
+ix86_is_empty_record (const_tree type)
+{
+  if (!TARGET_64BIT)
+    return false;
+  return default_is_empty_record (type);
+}
+
+/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
+
+static void
+ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
+{
+  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
+
+  if (!cum->warn_empty)
+    return;
+
+  if (!TYPE_EMPTY_P (type))
+    return;
+
+  const_tree ctx = get_ultimate_context (cum->decl);
+  if (ctx != NULL_TREE
+      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
+    return;
+
+  /* If the actual size of the type is zero, then there is no change
+     in how objects of this size are passed.  */
+  if (int_size_in_bytes (type) == 0)
+    return;
+
+  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
+	   "changes in -fabi-version=12 (GCC 8)", type);
+
+  /* Only warn once.  */
+  cum->warn_empty = false;
+}
+
 /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
    the data type, and ALIGN is the alignment that the object would
    ordinarily have.  */
@@ -50357,6 +50417,12 @@ ix86_run_selftests (void)
 #undef TARGET_CONSTANT_ALIGNMENT
 #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
 
+#undef TARGET_EMPTY_RECORD_P
+#define TARGET_EMPTY_RECORD_P ix86_is_empty_record
+
+#undef TARGET_WARN_PARAMETER_PASSING_ABI
+#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
+
 #if CHECKING_P
 #undef TARGET_RUN_TARGET_SELFTESTS
 #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
index 837906b5169..7f5d245b568 100644
--- gcc/config/i386/i386.h
+++ gcc/config/i386/i386.h
@@ -1633,6 +1633,8 @@ typedef struct ix86_args {
   int warn_avx;			/* True when we want to warn about AVX ABI.  */
   int warn_sse;			/* True when we want to warn about SSE ABI.  */
   int warn_mmx;			/* True when we want to warn about MMX ABI.  */
+  int warn_empty;		/* True when we want to warn about empty classes
+				   passing ABI change.  */
   int sse_regno;		/* next available sse register number */
   int mmx_words;		/* # mmx words passed so far */
   int mmx_nregs;		/* # mmx registers available for passing */
diff --git gcc/cp/class.c gcc/cp/class.c
index 98e62c6ad45..c6b0349f8e7 100644
--- gcc/cp/class.c
+++ gcc/cp/class.c
@@ -6198,6 +6198,7 @@ layout_class_type (tree t, tree *virtuals_p)
 	  DECL_CONTEXT (padding_field) = t;
 	  DECL_ARTIFICIAL (padding_field) = 1;
 	  DECL_IGNORED_P (padding_field) = 1;
+	  DECL_PADDING_P (padding_field) = 1;
 	  layout_nonempty_base_or_field (rli, padding_field,
 					 NULL_TREE,
 					 empty_base_offsets);
diff --git gcc/cp/decl.c gcc/cp/decl.c
index 0ce8f2d3435..6f0f1baa179 100644
--- gcc/cp/decl.c
+++ gcc/cp/decl.c
@@ -4012,6 +4012,10 @@ cxx_init_decl_processing (void)
   TREE_PUBLIC (global_namespace) = 1;
   DECL_CONTEXT (global_namespace)
     = build_translation_unit_decl (get_identifier (main_input_filename));
+  /* Remember whether we want the empty class passing ABI change warning
+     in this TU.  */
+  TRANSLATION_UNIT_WARN_EMPTY_P (DECL_CONTEXT (global_namespace))
+    = warn_abi && abi_version_crosses (12);
   debug_hooks->register_main_translation_unit
     (DECL_CONTEXT (global_namespace));
   begin_scope (sk_namespace, global_namespace);
@@ -12091,7 +12095,10 @@ grokdeclarator (const cp_declarator *declarator,
 				   FIELD_DECL, unqualified_id, type);
 		DECL_NONADDRESSABLE_P (decl) = bitfield;
 		if (bitfield && !unqualified_id)
-		  TREE_NO_WARNING (decl) = 1;
+		  {
+		    TREE_NO_WARNING (decl) = 1;
+		    DECL_PADDING_P (decl) = 1;
+		  }
 
 		if (storage_class == sc_mutable)
 		  {
diff --git gcc/doc/tm.texi gcc/doc/tm.texi
index 72606f53f1c..f16e73c31b1 100644
--- gcc/doc/tm.texi
+++ gcc/doc/tm.texi
@@ -4559,6 +4559,16 @@ This target hook returns the mode to be used when accessing raw return registers
 This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
+This target hook returns true if the type is an empty record.  The default
+is to return @code{false}.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
+This target hook warns about the change in empty class parameter passing
+ABI.
+@end deftypefn
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
index e7d4ada290f..39f6fcaaa11 100644
--- gcc/doc/tm.texi.in
+++ gcc/doc/tm.texi.in
@@ -3439,6 +3439,10 @@ nothing when you use @option{-freg-struct-return} mode.
 
 @hook TARGET_GET_RAW_ARG_MODE
 
+@hook TARGET_EMPTY_RECORD_P
+
+@hook TARGET_WARN_PARAMETER_PASSING_ABI
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/dwarf2out.c gcc/dwarf2out.c
index b8f4e4888f1..91d439c2506 100644
--- gcc/dwarf2out.c
+++ gcc/dwarf2out.c
@@ -5097,21 +5097,6 @@ get_AT_file (dw_die_ref die, enum dwarf_attribute attr_kind)
   return a ? AT_file (a) : NULL;
 }
 
-/* Returns the ultimate TRANSLATION_UNIT_DECL context of DECL or NULL.  */
-
-static const_tree
-get_ultimate_context (const_tree decl)
-{
-  while (decl && TREE_CODE (decl) != TRANSLATION_UNIT_DECL)
-    {
-      if (TREE_CODE (decl) == BLOCK)
-	decl = BLOCK_SUPERCONTEXT (decl);
-      else
-	decl = get_containing_scope (decl);
-    }
-  return decl;
-}
-
 /* Return TRUE if the language is C++.  */
 
 static inline bool
diff --git gcc/explow.c gcc/explow.c
index 662865d2808..72c2e01a44e 100644
--- gcc/explow.c
+++ gcc/explow.c
@@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
   if (REG_P (val)
       && GET_MODE (val) == BLKmode)
     {
-      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
+      unsigned HOST_WIDE_INT bytes = arg_int_size_in_bytes (valtype);
       opt_scalar_int_mode tmpmode;
 
       /* int_size_in_bytes can return -1.  We don't need a check here
diff --git gcc/expr.c gcc/expr.c
index 76684c11cc3..07fd06fa0cf 100644
--- gcc/expr.c
+++ gcc/expr.c
@@ -2749,7 +2749,7 @@ copy_blkmode_to_reg (machine_mode mode_in, tree src)
 
   x = expand_normal (src);
 
-  bytes = int_size_in_bytes (TREE_TYPE (src));
+  bytes = arg_int_size_in_bytes (TREE_TYPE (src));
   if (bytes == 0)
     return NULL_RTX;
 
diff --git gcc/function.c gcc/function.c
index fe3d9c1bbf3..1fa538b4b7f 100644
--- gcc/function.c
+++ gcc/function.c
@@ -2084,6 +2084,9 @@ aggregate_value_p (const_tree exp, const_tree fntype)
   if (TREE_ADDRESSABLE (type))
     return 1;
 
+  if (TYPE_EMPTY_P (type))
+    return 0;
+
   if (flag_pcc_struct_return && AGGREGATE_TYPE_P (type))
     return 1;
 
@@ -2528,6 +2531,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
       return;
     }
 
+  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
+					    data->passed_type);
+
   entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
 						    data->promoted_mode,
 						    data->passed_type,
@@ -4140,8 +4146,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
 
   part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
 
-  sizetree
-    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
+  sizetree = (type
+	      ? arg_size_in_bytes (type)
+	      : size_int (GET_MODE_SIZE (passed_mode)));
   where_pad = targetm.calls.function_arg_padding (passed_mode, type);
   boundary = targetm.calls.function_arg_boundary (passed_mode, type);
   round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
index 554f9cc9f01..e127e508f97 100644
--- gcc/lto-streamer-out.c
+++ gcc/lto-streamer-out.c
@@ -1073,6 +1073,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
 	{
 	  hstate.add_flag (DECL_PACKED (t));
 	  hstate.add_flag (DECL_NONADDRESSABLE_P (t));
+	  hstate.add_flag (DECL_PADDING_P (t));
 	  hstate.add_int (DECL_OFFSET_ALIGN (t));
 	}
       else if (code == VAR_DECL)
@@ -1166,6 +1167,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
       hstate.commit_flag ();
       hstate.add_int (TYPE_PRECISION (t));
       hstate.add_int (TYPE_ALIGN (t));
+      hstate.add_int (TYPE_EMPTY_P (t));
     }
 
   if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
diff --git gcc/lto/lto.c gcc/lto/lto.c
index 63ba73c0dbf..748ef02143c 100644
--- gcc/lto/lto.c
+++ gcc/lto/lto.c
@@ -1087,6 +1087,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	{
 	  compare_values (DECL_PACKED);
 	  compare_values (DECL_NONADDRESSABLE_P);
+	  compare_values (DECL_PADDING_P);
 	  compare_values (DECL_OFFSET_ALIGN);
 	}
       else if (code == VAR_DECL)
@@ -1165,6 +1166,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
 	compare_values (TYPE_NONALIASED_COMPONENT);
       if (AGGREGATE_TYPE_P (t1))
 	compare_values (TYPE_TYPELESS_STORAGE);
+      compare_values (TYPE_EMPTY_P);
       compare_values (TYPE_PACKED);
       compare_values (TYPE_RESTRICT);
       compare_values (TYPE_USER_ALIGN);
diff --git gcc/stor-layout.c gcc/stor-layout.c
index 7730ac33e4f..1dd215ec27d 100644
--- gcc/stor-layout.c
+++ gcc/stor-layout.c
@@ -1859,6 +1859,9 @@ finalize_type_size (tree type)
 	  SET_TYPE_MODE (variant, mode);
 	}
     }
+
+  /* Handle empty records as per the x86-64 psABI.  */
+  TYPE_EMPTY_P (type) = targetm.calls.empty_record_p (type);
 }
 
 /* Return a new underlying object for a bitfield started with FIELD.  */
diff --git gcc/target.def gcc/target.def
index 577dad8fe86..81aedee80d9 100644
--- gcc/target.def
+++ gcc/target.def
@@ -5055,6 +5055,22 @@ DEFHOOK
  fixed_size_mode, (int regno),
  default_get_reg_raw_mode)
 
+/* Return true if a type is an empty record.  */
+DEFHOOK
+(empty_record_p,
+ "This target hook returns true if the type is an empty record.  The default\n\
+is to return @code{false}.",
+ bool, (const_tree type),
+ hook_bool_const_tree_false)
+
+/* Warn about the change in empty class parameter passing ABI.  */
+DEFHOOK
+(warn_parameter_passing_abi,
+ "This target hook warns about the change in empty class parameter passing\n\
+ABI.",
+ void, (cumulative_args_t ca, tree type),
+ hook_void_CUMULATIVE_ARGS_tree)
+
 HOOK_VECTOR_END (calls)
 
 DEFHOOK
diff --git gcc/targhooks.c gcc/targhooks.c
index dad1e109d23..0edc57b0a15 100644
--- gcc/targhooks.c
+++ gcc/targhooks.c
@@ -756,6 +756,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
 }
 
 void
+hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
+				tree ATTRIBUTE_UNUSED)
+{
+}
+
+void
 default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
 			      machine_mode mode ATTRIBUTE_UNUSED,
 			      const_tree type ATTRIBUTE_UNUSED,
@@ -2108,6 +2114,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
   /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
      requires greater alignment, we must perform dynamic alignment.  */
   if (boundary > align
+      && !TYPE_EMPTY_P (type)
       && !integer_zerop (TYPE_SIZE (type)))
     {
       t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
@@ -2134,7 +2141,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
     }
 
   /* Compute the rounded size of the type.  */
-  type_size = size_in_bytes (type);
+  type_size = arg_size_in_bytes (type);
   rounded_size = round_up (type_size, align);
 
   /* Reduce rounded_size so it's sharable with the postqueue.  */
diff --git gcc/targhooks.h gcc/targhooks.h
index 15bbf5cdf24..e431934cd60 100644
--- gcc/targhooks.h
+++ gcc/targhooks.h
@@ -135,6 +135,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
   (cumulative_args_t, machine_mode, const_tree, bool);
 extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
   (cumulative_args_t, machine_mode, tree, bool);
+extern void hook_void_CUMULATIVE_ARGS_tree
+  (cumulative_args_t, tree);
 extern const char *hook_invalid_arg_for_unprototyped_fn
   (const_tree, const_tree, const_tree);
 extern void default_function_arg_advance
diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
index e69de29bb2d..20d85ff873e 100644
--- gcc/testsuite/g++.dg/abi/empty12.C
+++ gcc/testsuite/g++.dg/abi/empty12.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty12a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty12.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty12.h
+++ gcc/testsuite/g++.dg/abi/empty12.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
index e69de29bb2d..34a25bad75d 100644
--- gcc/testsuite/g++.dg/abi/empty12a.c
+++ gcc/testsuite/g++.dg/abi/empty12a.c
@@ -0,0 +1,6 @@
+#include "empty12.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
index e69de29bb2d..0cb9a373e35 100644
--- gcc/testsuite/g++.dg/abi/empty13.C
+++ gcc/testsuite/g++.dg/abi/empty13.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-x c -fabi-version=11" }
+// { dg-additional-sources "empty13a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty13.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty13.h
+++ gcc/testsuite/g++.dg/abi/empty13.h
@@ -0,0 +1,9 @@
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
index e69de29bb2d..b4303a63826 100644
--- gcc/testsuite/g++.dg/abi/empty13a.c
+++ gcc/testsuite/g++.dg/abi/empty13a.c
@@ -0,0 +1,6 @@
+#include "empty13.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 == -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
index e69de29bb2d..2868d8ad3f3 100644
--- gcc/testsuite/g++.dg/abi/empty14.C
+++ gcc/testsuite/g++.dg/abi/empty14.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty14a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty14.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
index e69de29bb2d..5842279cf37 100644
--- gcc/testsuite/g++.dg/abi/empty14.h
+++ gcc/testsuite/g++.dg/abi/empty14.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[140]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
index e69de29bb2d..8b3d7800c36 100644
--- gcc/testsuite/g++.dg/abi/empty14a.c
+++ gcc/testsuite/g++.dg/abi/empty14a.c
@@ -0,0 +1,6 @@
+#include "empty14.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
index e69de29bb2d..12385f78c78 100644
--- gcc/testsuite/g++.dg/abi/empty15.C
+++ gcc/testsuite/g++.dg/abi/empty15.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty15a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty15.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
index e69de29bb2d..1c6f26f5ae8 100644
--- gcc/testsuite/g++.dg/abi/empty15.h
+++ gcc/testsuite/g++.dg/abi/empty15.h
@@ -0,0 +1,30 @@
+struct A1 {};
+struct A2 {};
+struct B1 { struct A1 a; struct A2 b; };
+struct B2 { struct A1 a; struct A2 b; };
+struct C1 { struct B1 a; struct B2 b; };
+struct C2 { struct B1 a; struct B2 b; };
+struct D1 { struct C1 a; struct C2 b; };
+struct D2 { struct C1 a; struct C2 b; };
+struct E1 { struct D1 a; struct D2 b; };
+struct E2 { struct D1 a; struct D2 b; };
+struct F1 { struct E1 a; struct E2 b; };
+struct F2 { struct E1 a; struct E2 b; };
+struct G1 { struct F1 a; struct F2 b; };
+struct G2 { struct F1 a; struct F2 b; };
+struct H1 { struct G1 a; struct G2 b; };
+struct H2 { struct G1 a; struct G2 b; };
+struct I1 { struct H1 a; struct H2 b; };
+struct I2 { struct H1 a; struct H2 b; };
+struct J1 { struct I1 a; struct I2 b; };
+struct J2 { struct I1 a; struct I2 b; };
+struct dummy { struct J1 a; struct J2 b; };
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
index e69de29bb2d..325b2c5ba09 100644
--- gcc/testsuite/g++.dg/abi/empty15a.c
+++ gcc/testsuite/g++.dg/abi/empty15a.c
@@ -0,0 +1,6 @@
+#include "empty15.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
index e69de29bb2d..1ca52f9011e 100644
--- gcc/testsuite/g++.dg/abi/empty16.C
+++ gcc/testsuite/g++.dg/abi/empty16.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty16a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty16.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
index e69de29bb2d..7552ae06576 100644
--- gcc/testsuite/g++.dg/abi/empty16.h
+++ gcc/testsuite/g++.dg/abi/empty16.h
@@ -0,0 +1,16 @@
+#ifdef __cplusplus
+struct A1 {};
+struct A2 {};
+struct dummy : A1, A2 {} ;
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
index e69de29bb2d..6cb7fbccecc 100644
--- gcc/testsuite/g++.dg/abi/empty16a.c
+++ gcc/testsuite/g++.dg/abi/empty16a.c
@@ -0,0 +1,6 @@
+#include "empty16.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
index e69de29bb2d..d386e5481af 100644
--- gcc/testsuite/g++.dg/abi/empty17.C
+++ gcc/testsuite/g++.dg/abi/empty17.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty17a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty17.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
index e69de29bb2d..9cf72baca2e 100644
--- gcc/testsuite/g++.dg/abi/empty17.h
+++ gcc/testsuite/g++.dg/abi/empty17.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 15;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 15;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
index e69de29bb2d..24408fde09c 100644
--- gcc/testsuite/g++.dg/abi/empty17a.c
+++ gcc/testsuite/g++.dg/abi/empty17a.c
@@ -0,0 +1,6 @@
+#include "empty17.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
index e69de29bb2d..be69c6a2115 100644
--- gcc/testsuite/g++.dg/abi/empty18.C
+++ gcc/testsuite/g++.dg/abi/empty18.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty18a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty18.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
index e69de29bb2d..86e7ecdd211 100644
--- gcc/testsuite/g++.dg/abi/empty18.h
+++ gcc/testsuite/g++.dg/abi/empty18.h
@@ -0,0 +1,9 @@
+struct dummy { int d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
index e69de29bb2d..902860bdc01 100644
--- gcc/testsuite/g++.dg/abi/empty18a.c
+++ gcc/testsuite/g++.dg/abi/empty18a.c
@@ -0,0 +1,6 @@
+#include "empty18.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
index e69de29bb2d..84f5b75558b 100644
--- gcc/testsuite/g++.dg/abi/empty19.C
+++ gcc/testsuite/g++.dg/abi/empty19.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty19a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty19.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
index e69de29bb2d..616b87bdd93 100644
--- gcc/testsuite/g++.dg/abi/empty19.h
+++ gcc/testsuite/g++.dg/abi/empty19.h
@@ -0,0 +1,10 @@
+struct dummy0 { };
+struct dummy { struct dummy0 d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
index e69de29bb2d..767b1eb7320 100644
--- gcc/testsuite/g++.dg/abi/empty19a.c
+++ gcc/testsuite/g++.dg/abi/empty19a.c
@@ -0,0 +1,6 @@
+#include "empty19.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
index e69de29bb2d..5022033f669 100644
--- gcc/testsuite/g++.dg/abi/empty20.C
+++ gcc/testsuite/g++.dg/abi/empty20.C
@@ -0,0 +1,19 @@
+// PR c++/60336
+// { dg-options "-Wabi=11 -O0" }
+
+struct A { };
+
+void f(A, A) { }	// No warning, trailing parms all empty
+void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+__attribute__ ((always_inline))
+inline void f(A a, int i) // No warning, always inlined
+{
+  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
+int main()
+{
+  A a;
+  f(a,a);
+  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(a,42);
+}
diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
index e69de29bb2d..3b2e3b836b1 100644
--- gcc/testsuite/g++.dg/abi/empty21.C
+++ gcc/testsuite/g++.dg/abi/empty21.C
@@ -0,0 +1,23 @@
+// PR c++/60336
+// { dg-options "-Wabi=11" }
+
+#include <stdarg.h>
+
+struct A { };
+
+void f(int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (i >= 1)
+    va_arg (ap, A);
+  if (i >= 2)
+    va_arg (ap, int);
+}
+
+int main()
+{
+  f(0);
+  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
index e69de29bb2d..f4f4a02bf31 100644
--- gcc/testsuite/g++.dg/abi/empty22.C
+++ gcc/testsuite/g++.dg/abi/empty22.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty22a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty22.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty22.h
+++ gcc/testsuite/g++.dg/abi/empty22.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
index e69de29bb2d..7606c524263 100644
--- gcc/testsuite/g++.dg/abi/empty22a.c
+++ gcc/testsuite/g++.dg/abi/empty22a.c
@@ -0,0 +1,6 @@
+#include "empty22.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
index e69de29bb2d..dbeda81fb24 100644
--- gcc/testsuite/g++.dg/abi/empty23.C
+++ gcc/testsuite/g++.dg/abi/empty23.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[0];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
index e69de29bb2d..822ced1ef50 100644
--- gcc/testsuite/g++.dg/abi/empty24.C
+++ gcc/testsuite/g++.dg/abi/empty24.C
@@ -0,0 +1,25 @@
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty25.C gcc/testsuite/g++.dg/abi/empty25.C
index e69de29bb2d..da6ef51ff0d 100644
--- gcc/testsuite/g++.dg/abi/empty25.C
+++ gcc/testsuite/g++.dg/abi/empty25.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty25a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty25.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-bogus "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty25.h gcc/testsuite/g++.dg/abi/empty25.h
index e69de29bb2d..2f22fd5e505 100644
--- gcc/testsuite/g++.dg/abi/empty25.h
+++ gcc/testsuite/g++.dg/abi/empty25.h
@@ -0,0 +1,18 @@
+#ifdef __cplusplus
+struct dummy
+{
+  virtual void bar (void) { }
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty25a.c gcc/testsuite/g++.dg/abi/empty25a.c
index e69de29bb2d..8c16e453c75 100644
--- gcc/testsuite/g++.dg/abi/empty25a.c
+++ gcc/testsuite/g++.dg/abi/empty25a.c
@@ -0,0 +1,6 @@
+#include "empty25.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty26.C gcc/testsuite/g++.dg/abi/empty26.C
index e69de29bb2d..ab2f54d8dab 100644
--- gcc/testsuite/g++.dg/abi/empty26.C
+++ gcc/testsuite/g++.dg/abi/empty26.C
@@ -0,0 +1,17 @@
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty26a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty26.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty26.h gcc/testsuite/g++.dg/abi/empty26.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty26.h
+++ gcc/testsuite/g++.dg/abi/empty26.h
@@ -0,0 +1,27 @@
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty26a.c gcc/testsuite/g++.dg/abi/empty26a.c
index e69de29bb2d..bc0ae47ba2c 100644
--- gcc/testsuite/g++.dg/abi/empty26a.c
+++ gcc/testsuite/g++.dg/abi/empty26a.c
@@ -0,0 +1,6 @@
+#include "empty26.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty27.C gcc/testsuite/g++.dg/abi/empty27.C
index e69de29bb2d..5d14e7c6a03 100644
--- gcc/testsuite/g++.dg/abi/empty27.C
+++ gcc/testsuite/g++.dg/abi/empty27.C
@@ -0,0 +1,26 @@
+// PR c++/60336
+// { dg-do compile }
+// { dg-options "-Wabi=12" }
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
+
+namespace N {
+  class E { };
+  void fun (class E, struct foo);
+}
+
+int main()
+{
+  N::E d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  N::fun(d, f); // { dg-bogus "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty28.C gcc/testsuite/g++.dg/abi/empty28.C
index e69de29bb2d..7e0765d4468 100644
--- gcc/testsuite/g++.dg/abi/empty28.C
+++ gcc/testsuite/g++.dg/abi/empty28.C
@@ -0,0 +1,28 @@
+// PR c++/60336
+// { dg-do compile }
+// { dg-options "-Wabi=12" }
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
+
+struct N {
+  class E { };
+  void fun (class E, struct foo) { } // { dg-bogus "empty" }
+};
+
+
+int main()
+{
+  struct N n;
+  N::E d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  n.fun(d, f); // { dg-bogus "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
index e69de29bb2d..59447890cec 100644
--- gcc/testsuite/g++.dg/abi/pr60336-1.C
+++ gcc/testsuite/g++.dg/abi/pr60336-1.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
index e69de29bb2d..960cc2307d1 100644
--- gcc/testsuite/g++.dg/abi/pr60336-10.C
+++ gcc/testsuite/g++.dg/abi/pr60336-10.C
@@ -0,0 +1,50 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0 { };
+struct dummy1 { };
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
index e69de29bb2d..14cd6d0ff3d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-11.C
+++ gcc/testsuite/g++.dg/abi/pr60336-11.C
@@ -0,0 +1,56 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+  void bar (void);
+};
+struct dummy1
+{
+  void foo (void);
+};
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
index e69de29bb2d..09917547930 100644
--- gcc/testsuite/g++.dg/abi/pr60336-12.C
+++ gcc/testsuite/g++.dg/abi/pr60336-12.C
@@ -0,0 +1,57 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+};
+struct dummy1
+{
+  unsigned : 15;
+};
+struct dummy : dummy0, dummy1
+{
+};
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
index e69de29bb2d..1c6c3eb8f01 100644
--- gcc/testsuite/g++.dg/abi/pr60336-2.C
+++ gcc/testsuite/g++.dg/abi/pr60336-2.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -Wabi=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...) // { dg-warning "empty" }
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
index e69de29bb2d..4157e553b6b 100644
--- gcc/testsuite/g++.dg/abi/pr60336-3.C
+++ gcc/testsuite/g++.dg/abi/pr60336-3.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
index e69de29bb2d..266f67a537d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-4.C
+++ gcc/testsuite/g++.dg/abi/pr60336-4.C
@@ -0,0 +1,48 @@
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -fabi-version=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count == 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
index e69de29bb2d..fe838750f55 100644
--- gcc/testsuite/g++.dg/abi/pr60336-5.C
+++ gcc/testsuite/g++.dg/abi/pr60336-5.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; struct dummy j; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
index e69de29bb2d..6e08c8f06fa 100644
--- gcc/testsuite/g++.dg/abi/pr60336-6.C
+++ gcc/testsuite/g++.dg/abi/pr60336-6.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i1; struct dummy i2; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
index e69de29bb2d..3b8b8ba6f35 100644
--- gcc/testsuite/g++.dg/abi/pr60336-7.C
+++ gcc/testsuite/g++.dg/abi/pr60336-7.C
@@ -0,0 +1,17 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i[120]; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
index e69de29bb2d..a1ffb64ef02 100644
--- gcc/testsuite/g++.dg/abi/pr60336-8.C
+++ gcc/testsuite/g++.dg/abi/pr60336-8.C
@@ -0,0 +1,15 @@
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct{} a[7][3]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
index e69de29bb2d..393f02b62f0 100644
--- gcc/testsuite/g++.dg/abi/pr60336-9.C
+++ gcc/testsuite/g++.dg/abi/pr60336-9.C
@@ -0,0 +1,28 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct A1 {}; struct A2 {};
+struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
+struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
+struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
+struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
+struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
+struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
+struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
+struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
+struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
+struct dummy { J1 a; J2 b; };
+
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
index e69de29bb2d..1354fc497b5 100644
--- gcc/testsuite/g++.dg/abi/pr68355.C
+++ gcc/testsuite/g++.dg/abi/pr68355.C
@@ -0,0 +1,24 @@
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+template<typename _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr _Tp value = __v;
+  typedef _Tp value_type;
+  typedef integral_constant<_Tp, __v> type;
+  constexpr operator value_type() const { return value; }
+};
+
+typedef integral_constant<bool, true> true_type;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  true_type y;
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
index e69de29bb2d..a0a598c0029 100644
--- gcc/testsuite/g++.dg/lto/pr60336_0.C
+++ gcc/testsuite/g++.dg/lto/pr60336_0.C
@@ -0,0 +1,47 @@
+// { dg-lto-do run }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/tree-core.h gcc/tree-core.h
index f74f1453de6..13ff5e3db71 100644
--- gcc/tree-core.h
+++ gcc/tree-core.h
@@ -1531,7 +1531,8 @@ struct GTY(()) tree_type_common {
   unsigned align : 6;
   unsigned warn_if_not_align : 6;
   unsigned typeless_storage : 1;
-  unsigned spare : 18;
+  unsigned empty_flag : 1;
+  unsigned spare : 17;
 
   alias_set_type alias_set;
   tree pointer_to;
@@ -1609,7 +1610,8 @@ struct GTY(()) tree_decl_common {
   unsigned lang_flag_7 : 1;
   unsigned lang_flag_8 : 1;
 
-  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER.  */
+  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER
+     IN TRANSLATION_UNIT_DECL, this is TRANSLATION_UNIT_WARN_EMPTY_P.  */
   unsigned decl_flag_0 : 1;
   /* In FIELD_DECL, this is DECL_BIT_FIELD
      In VAR_DECL and FUNCTION_DECL, this is DECL_EXTERNAL.
@@ -1619,7 +1621,7 @@ struct GTY(()) tree_decl_common {
      In VAR_DECL, PARM_DECL and RESULT_DECL, this is
      DECL_HAS_VALUE_EXPR_P.  */
   unsigned decl_flag_2 : 1;
-  /* 1 bit unused.  */
+  /* In FIELD_DECL, this is DECL_PADDING_P.  */
   unsigned decl_flag_3 : 1;
   /* Logically, these two would go in a theoretical base shared by var and
      parm decl. */
diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
index baf0c5bf837..36402c6a39c 100644
--- gcc/tree-streamer-in.c
+++ gcc/tree-streamer-in.c
@@ -251,6 +251,7 @@ unpack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
     {
       DECL_PACKED (expr) = (unsigned) bp_unpack_value (bp, 1);
       DECL_NONADDRESSABLE_P (expr) = (unsigned) bp_unpack_value (bp, 1);
+      DECL_PADDING_P (expr) = (unsigned) bp_unpack_value (bp, 1);
       expr->decl_common.off_align = bp_unpack_value (bp, 8);
     }
 
@@ -381,6 +382,7 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
   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_PRECISION (expr) = bp_unpack_var_len_unsigned (bp);
   SET_TYPE_ALIGN (expr, bp_unpack_var_len_unsigned (bp));
 #ifdef ACCEL_COMPILER
diff --git gcc/tree-streamer-out.c gcc/tree-streamer-out.c
index 7f52d455f5e..08c58a4709d 100644
--- gcc/tree-streamer-out.c
+++ gcc/tree-streamer-out.c
@@ -211,6 +211,7 @@ pack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
     {
       bp_pack_value (bp, DECL_PACKED (expr), 1);
       bp_pack_value (bp, DECL_NONADDRESSABLE_P (expr), 1);
+      bp_pack_value (bp, DECL_PADDING_P (expr), 1);
       bp_pack_value (bp, expr->decl_common.off_align, 8);
     }
 
@@ -330,6 +331,7 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
     bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
   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_var_len_unsigned (bp, TYPE_PRECISION (expr));
   bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
 }
diff --git gcc/tree.c gcc/tree.c
index 28e157f5fd2..6c8f74b52e2 100644
--- gcc/tree.c
+++ gcc/tree.c
@@ -8701,6 +8701,21 @@ get_containing_scope (const_tree t)
   return (TYPE_P (t) ? TYPE_CONTEXT (t) : DECL_CONTEXT (t));
 }
 
+/* Returns the ultimate TRANSLATION_UNIT_DECL context of DECL or NULL.  */
+
+const_tree
+get_ultimate_context (const_tree decl)
+{
+  while (decl && TREE_CODE (decl) != TRANSLATION_UNIT_DECL)
+    {
+      if (TREE_CODE (decl) == BLOCK)
+	decl = BLOCK_SUPERCONTEXT (decl);
+      else
+	decl = get_containing_scope (decl);
+    }
+  return decl;
+}
+
 /* Return the innermost context enclosing DECL that is
    a FUNCTION_DECL, or zero if none.  */
 
@@ -13811,6 +13826,62 @@ get_nonnull_args (const_tree fntype)
   return argmap;
 }
 
+/* Returns true if TYPE is a type where it and all of its subobjects
+   (recursively) are of structure, union, or array type.  */
+
+static bool
+default_is_empty_type (tree type)
+{
+  if (RECORD_OR_UNION_TYPE_P (type))
+    {
+      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+	if (TREE_CODE (field) == FIELD_DECL
+	    && !DECL_PADDING_P (field)
+	    && !default_is_empty_type (TREE_TYPE (field)))
+	  return false;
+      return true;
+    }
+  else if (TREE_CODE (type) == ARRAY_TYPE)
+    return (integer_minus_onep (array_type_nelts (type))
+	    || TYPE_DOMAIN (type) == NULL_TREE
+	    || default_is_empty_type (TREE_TYPE (type)));
+  return false;
+}
+
+/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
+   that shouldn't be passed via stack.  */
+
+bool
+default_is_empty_record (const_tree type)
+{
+  if (!abi_version_at_least (12))
+    return false;
+
+  if (type == error_mark_node)
+    return false;
+
+  if (TREE_ADDRESSABLE (type))
+    return false;
+
+  return default_is_empty_type (TYPE_MAIN_VARIANT (type));
+}
+
+/* Like int_size_in_bytes, but handle empty records specially.  */
+
+HOST_WIDE_INT
+arg_int_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
+}
+
+/* Like size_in_bytes, but handle empty records specially.  */
+
+tree
+arg_size_in_bytes (const_tree type)
+{
+  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
+}
+
 /* List of pointer types used to declare builtins before we have seen their
    real declaration.
 
diff --git gcc/tree.h gcc/tree.h
index 277aa919780..2fad4e18d1c 100644
--- gcc/tree.h
+++ gcc/tree.h
@@ -696,6 +696,14 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
    emitted.  */
 #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
 
+/* Nonzero if we should warn about the change in empty class parameter
+   passing ABI in this TU.  */
+#define TRANSLATION_UNIT_WARN_EMPTY_P(NODE) \
+  (TRANSLATION_UNIT_DECL_CHECK (NODE)->decl_common.decl_flag_0)
+
+/* Nonzero if this type is "empty" according to the particular psABI.  */
+#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
+
 /* Used to indicate that this TYPE represents a compiler-generated entity.  */
 #define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
 
@@ -2619,6 +2627,10 @@ extern void decl_value_expr_insert (tree, tree);
 #define DECL_NONADDRESSABLE_P(NODE) \
   (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_2)
 
+/* Used in a FIELD_DECL to indicate that this field is padding.  */
+#define DECL_PADDING_P(NODE) \
+  (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_3)
+
 /* A numeric unique identifier for a LABEL_DECL.  The UID allocation is
    dense, unique within any one function, and may be used to index arrays.
    If the value is -1, then no UID has been assigned.  */
@@ -4512,6 +4524,10 @@ storage_order_barrier_p (const_tree t)
 
 extern tree get_containing_scope (const_tree);
 
+/* Returns the ultimate TRANSLATION_UNIT_DECL context of DECL or NULL.  */
+
+extern const_tree get_ultimate_context (const_tree);
+
 /* Return the FUNCTION_DECL which provides this _DECL with its context,
    or zero if none.  */
 extern tree decl_function_context (const_tree);
@@ -5428,6 +5444,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
 
 extern bool nonnull_arg_p (const_tree);
 extern bool is_redundant_typedef (const_tree);
+extern bool default_is_empty_record (const_tree);
+extern HOST_WIDE_INT arg_int_size_in_bytes (const_tree);
+extern tree arg_size_in_bytes (const_tree);
 
 extern location_t
 set_source_range (tree expr, location_t start, location_t finish);

	Marek
Richard Biener Nov. 13, 2017, 10:44 a.m. UTC | #25
On Mon, 13 Nov 2017, Marek Polacek wrote:

> On Fri, Nov 10, 2017 at 11:16:04AM -0500, Jason Merrill wrote:
> > On 11/09/2017 10:26 AM, Richard Biener wrote:
> > > > Moving TYPE_EMPTY_P to finalize_type_size revealed a bug in Cilk+, it was
> > > > bogusly (I believe) setting TREE_ADDRESSABLE, so the assert fired.  Since
> > > > Cilk+ is being deprecated and the Cilk+ testsuite still passes, I'm not
> > > > too worried about this change.
> > 
> > Ah, presumably C++ classes aren't hitting the assert just because they set
> > TREE_ADDRESSABLE after finalize_type_size, so the assert is wrong. Let's
> > drop the assert and the Cilk+ change.
>  
> Ah, ok, done.
> 
> > > > So if you're fine with the DECL_PADDING_P change, all that remains is to
> > > > check whether this patch is not changing the ABI for other languages than
> > > > C++.  I suppose one way to check that could be to do sth like
> > > > 
> > > >    if (strncmp (lang_hooks.name, "GNU C++", 7))
> > > >      {FILE *f=fopen("/tmp/A", "a");fprintf(f,"%s\n",main_input_filename);fclose(f);}
> > > > 
> > > > and compare the assembly of the files it prints?  But there were thousands of
> > > > them I think :(.
> > > 
> > > Shouldn't most of GCCs own object files (and target library object files)
> > > compare 1:1 before and after the patch?  After all it should _only_ affect
> > > parameter passing of empty aggregates (and the files the patch changes
> > > of course)?
> > 
> > That makes sense to me.
> 
> Unfortunately, it's not what I see.  I ran two bootstraps, with and without the
> patch.  Then I stripped all the .o files and ran cmp on them, but many of them
> differ.  objdump -dr reveals why -- I see changes like
> 
>   mov    $0xe02,%ecx
> vs
>   mov    $0xdf6,%ecx
> 
> That's probably some sizeof changes?  Any other ideas? :/  Somehow ignore
> these and only look if there are new pushes or similar?

sizeof an empty type shouldn't change, no?  Possibly the new target
hook "shifts" target hook offsets.  Maybe you can arrange the new one
to be last... (just for testing).  OTOH for example in target library
objects all the changes of this kind should have no effect.  So, do
objects in target libraries differ similarly?

Richard.

> > > > @@ -9295,6 +9315,10 @@ ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
> > ...
> > > > +  /* Empty records are never passed in memory.  */
> > > > +  if (type && TYPE_EMPTY_P (type))
> > > > +    return false;
> > 
> > Since the TYPE_EMPTY_P flag is in target-independent code, let's check it in
> > aggregate_value_p rather than here.
> 
> Done.
> 
> > > > +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
> > ...
> > > > +  tree ctx = cum->decl ? DECL_CONTEXT (cum->decl) : NULL_TREE;
> > > > +  if (ctx != NULL_TREE
> > > > +      && TREE_CODE (ctx) == TRANSLATION_UNIT_DECL
> > > > +      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
> > > > +    return;
> > 
> > This doesn't handle empty classes within a namespace/class/function. Maybe
> > move get_ultimate_context out of dwarf2out.c and use that?
> 
> Thanks for catching that.  That would result in bogus warnings.  Fixed
> as suggested though I'm not sure if we need the BLOCK handling in
> get_ultimate_context.  I've also added new testcases.
> 
> > > > +/* Used in a FIELD_DECL to indicate that this field is padding for
> > > > +   the purpose of determining whether the record this field is a member
> > > > +   of is considered empty.  */
> > 
> > I wouldn't mention "empty" here; this indicates that the field is padding,
> > not data.  A target might use this information to avoid passing this field
> > even if the class contains data as well.
> 
> Okay, fixed.
> 
> Bootstrapped/regtested on x86_64-linux.
> 
> 2017-11-14  Marek Polacek  <polacek@redhat.com>
> 	    H.J. Lu  <hongjiu.lu@intel.com>
> 	    Jason Merrill  <jason@redhat.com>
> 
> 	PR c++/60336
> 	PR middle-end/67239
> 	PR target/68355
> 	* c-decl.c (grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.
> 
> 	* class.c (layout_class_type): Set DECL_PADDING_P on padding.
> 	* decl.c (cxx_init_decl_processing): Set TRANSLATION_UNIT_WARN_EMPTY_P.
> 	(grokdeclarator): Set DECL_PADDING_P on unnamed bit-fields.
> 
> 	* lto.c (compare_tree_sccs_1): Compare TYPE_EMPTY_P and DECL_PADDING_P.
> 
> 	* calls.c (initialize_argument_information): Call
> 	warn_parameter_passing_abi target hook.
> 	(store_one_arg): Use 0 for empty record size.  Don't push 0 size
> 	argument onto stack.
> 	(must_pass_in_stack_var_size_or_pad): Return false for empty types.
> 	* common.opt: Update -fabi-version description.
> 	* config/i386/i386.c (init_cumulative_args): Set cum->warn_empty.
> 	(ix86_gimplify_va_arg): Call arg_int_size_in_bytes instead of
> 	int_size_in_bytes.
> 	(ix86_is_empty_record): New function.
> 	(ix86_warn_parameter_passing_abi): New function.
> 	(TARGET_EMPTY_RECORD_P): Redefine.
> 	(TARGET_WARN_PARAMETER_PASSING_ABI): Redefine.
> 	* config/i386/i386.h (CUMULATIVE_ARGS): Add warn_empty.
> 	* doc/tm.texi: Regenerated.
> 	* doc/tm.texi.in (TARGET_EMPTY_RECORD_P,
> 	TARGET_WARN_PARAMETER_PASSING_ABI): Add.
> 	* dwarf2out.c (get_ultimate_context): Move to tree.c.
> 	* explow.c (hard_function_value): Call arg_int_size_in_bytes
> 	instead of int_size_in_bytes.
> 	* expr.c (copy_blkmode_to_reg): Likewise.
> 	* function.c (aggregate_value_p): Return 0 for empty types.
> 	(assign_parm_find_entry_rtl): Call warn_parameter_passing_abi target hook.
> 	(locate_and_pad_parm): Call arg size_in_bytes instead
> 	size_in_bytes.
> 	* lto-streamer-out.c (hash_tree): Hash TYPE_EMPTY_P and DECL_PADDING_P.
> 	* stor-layout.c (finalize_type_size): Set TYPE_EMPTY_P.
> 	* target.def (empty_record_p, warn_parameter_passing_abi): New target
> 	hooks.
> 	* targhooks.c (hook_void_CUMULATIVE_ARGS_tree): New hook.
> 	(std_gimplify_va_arg_expr): Skip empty records.  Call
> 	arg_size_in_bytes instead size_in_bytes.
> 	* targhooks.h (hook_void_CUMULATIVE_ARGS_tree): Declare.
> 	* tree-core.h (tree_type_common): Add empty_flag.
> 	(tree_decl_common): Update comments.
> 	* tree-streamer-in.c (unpack_ts_decl_common_value_fields): Stream
> 	DECL_PADDING_P.
> 	(unpack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
> 	* tree-streamer-out.c (pack_ts_decl_common_value_fields): Stream
> 	DECL_PADDING_P.
> 	(pack_ts_type_common_value_fields): Stream TYPE_EMPTY_P.
> 	* tree.c (default_is_empty_type): New function.
> 	(default_is_empty_record): New function.
> 	(arg_int_size_in_bytes): New function.
> 	(arg_size_in_bytes): New function.
> 	(get_ultimate_context): New function.
> 	* tree.h: Define TYPE_EMPTY_P, DECL_PADDING_P and
> 	TRANSLATION_UNIT_WARN_EMPTY_P.
> 	(default_is_empty_record, arg_int_size_in_bytes,
> 	arg_size_in_bytes, get_ultimate_context): Declare.
> 
> 	* g++.dg/abi/empty12.C: New test.
> 	* g++.dg/abi/empty12.h: New test.
> 	* g++.dg/abi/empty12a.c: New test.
> 	* g++.dg/abi/empty13.C: New test.
> 	* g++.dg/abi/empty13.h: New test.
> 	* g++.dg/abi/empty13a.c: New test.
> 	* g++.dg/abi/empty14.C: New test.
> 	* g++.dg/abi/empty14.h: New test.
> 	* g++.dg/abi/empty14a.c: New test.
> 	* g++.dg/abi/empty15.C: New test.
> 	* g++.dg/abi/empty15.h: New test.
> 	* g++.dg/abi/empty15a.c: New test.
> 	* g++.dg/abi/empty16.C: New test.
> 	* g++.dg/abi/empty16.h: New test.
> 	* g++.dg/abi/empty16a.c: New test.
> 	* g++.dg/abi/empty17.C: New test.
> 	* g++.dg/abi/empty17.h: New test.
> 	* g++.dg/abi/empty17a.c: New test.
> 	* g++.dg/abi/empty18.C: New test.
> 	* g++.dg/abi/empty18.h: New test.
> 	* g++.dg/abi/empty18a.c: New test.
> 	* g++.dg/abi/empty19.C: New test.
> 	* g++.dg/abi/empty19.h: New test.
> 	* g++.dg/abi/empty19a.c: New test.
> 	* g++.dg/abi/empty20.C: New test.
> 	* g++.dg/abi/empty21.C: New test.
> 	* g++.dg/abi/empty22.C: New test.
> 	* g++.dg/abi/empty22.h: New test.
> 	* g++.dg/abi/empty22a.c: New test.
> 	* g++.dg/abi/empty23.C: New test.
> 	* g++.dg/abi/empty24.C: New test.
> 	* g++.dg/abi/empty25.C: New test.
> 	* g++.dg/abi/empty25.h: New test.
> 	* g++.dg/abi/empty25a.c: New test.
> 	* g++.dg/abi/empty26.C: New test.
> 	* g++.dg/abi/empty26.h: New test.
> 	* g++.dg/abi/empty26a.c: New test.
> 	* g++.dg/abi/empty27.C: New test.
> 	* g++.dg/abi/empty28.C: New test.
> 	* g++.dg/abi/pr60336-1.C: New test.
> 	* g++.dg/abi/pr60336-10.C: New test.
> 	* g++.dg/abi/pr60336-11.C: New test.
> 	* g++.dg/abi/pr60336-12.C: New test.
> 	* g++.dg/abi/pr60336-2.C: New test.
> 	* g++.dg/abi/pr60336-3.C: New test.
> 	* g++.dg/abi/pr60336-4.C: New test.
> 	* g++.dg/abi/pr60336-5.C: New test.
> 	* g++.dg/abi/pr60336-6.C: New test.
> 	* g++.dg/abi/pr60336-7.C: New test.
> 	* g++.dg/abi/pr60336-8.C: New test.
> 	* g++.dg/abi/pr60336-9.C: New test.
> 	* g++.dg/abi/pr68355.C: New test.
> 	* g++.dg/lto/pr60336_0.C: New test.
> 
> diff --git gcc/c/c-decl.c gcc/c/c-decl.c
> index d95a2b6ea4f..dd01631e0bd 100644
> --- gcc/c/c-decl.c
> +++ gcc/c/c-decl.c
> @@ -6801,7 +6801,10 @@ grokdeclarator (const struct c_declarator *declarator,
>  			   FIELD_DECL, declarator->u.id, type);
>  	DECL_NONADDRESSABLE_P (decl) = bitfield;
>  	if (bitfield && !declarator->u.id)
> -	  TREE_NO_WARNING (decl) = 1;
> +	  {
> +	    TREE_NO_WARNING (decl) = 1;
> +	    DECL_PADDING_P (decl) = 1;
> +	  }
>  
>  	if (size_varies)
>  	  C_DECL_VARIABLE_SIZE (decl) = 1;
> diff --git gcc/calls.c gcc/calls.c
> index 3730f43c7a9..f9a6a5cce13 100644
> --- gcc/calls.c
> +++ gcc/calls.c
> @@ -1850,6 +1850,8 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
>        args[i].unsignedp = unsignedp;
>        args[i].mode = mode;
>  
> +      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
> +
>        args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
>  						argpos < n_named_args);
>  
> @@ -5358,7 +5360,11 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	 Note that in C the default argument promotions
>  	 will prevent such mismatches.  */
>  
> -      size = GET_MODE_SIZE (arg->mode);
> +      if (TYPE_EMPTY_P (TREE_TYPE (pval)))
> +	size = 0;
> +      else
> +	size = GET_MODE_SIZE (arg->mode);
> +
>        /* Compute how much space the push instruction will push.
>  	 On many machines, pushing a byte will advance the stack
>  	 pointer by a halfword.  */
> @@ -5390,10 +5396,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  
>        /* This isn't already where we want it on the stack, so put it there.
>  	 This can either be done with push or copy insns.  */
> -      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
> -		      parm_align, partial, reg, used - size, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
> +      if (used
> +	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
> +			      NULL_RTX, parm_align, partial, reg, used - size,
> +			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
> +			      reg_parm_stack_space,
> +			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
>  	sibcall_failure = 1;
>  
>        /* Unless this is a partially-in-register argument, the argument is now
> @@ -5426,9 +5434,9 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
>  	     for BLKmode is careful to avoid it.  */
>  	  excess = (arg->locate.size.constant
> -		    - int_size_in_bytes (TREE_TYPE (pval))
> +		    - arg_int_size_in_bytes (TREE_TYPE (pval))
>  		    + partial);
> -	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
> +	  size_rtx = expand_expr (arg_size_in_bytes (TREE_TYPE (pval)),
>  				  NULL_RTX, TYPE_MODE (sizetype),
>  				  EXPAND_NORMAL);
>  	}
> @@ -5504,10 +5512,12 @@ store_one_arg (struct arg_data *arg, rtx argblock, int flags,
>  	    }
>  	}
>  
> -      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> -		      parm_align, partial, reg, excess, argblock,
> -		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
> -		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
> +      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
> +	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
> +			parm_align, partial, reg, excess, argblock,
> +			ARGS_SIZE_RTX (arg->locate.offset),
> +			reg_parm_stack_space,
> +			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
>  
>        /* Unless this is a partially-in-register argument, the argument is now
>  	 in the stack.
> @@ -5585,6 +5595,9 @@ must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
>    if (TREE_ADDRESSABLE (type))
>      return true;
>  
> +  if (TYPE_EMPTY_P (type))
> +    return false;
> +
>    /* If the padding and mode of the type is such that a copy into
>       a register would put it into the wrong part of the register.  */
>    if (mode == BLKmode
> diff --git gcc/common.opt gcc/common.opt
> index f8f2ed3db8a..28a0185f0cf 100644
> --- gcc/common.opt
> +++ gcc/common.opt
> @@ -936,7 +936,7 @@ Driver Undocumented
>  ;     Default in G++ 7.
>  ;
>  ; 12: Corrects the calling convention for classes with only deleted copy/move
> -;     constructors.
> +;     constructors and changes passing/returning of empty records.
>  ;     Default in G++ 8.
>  ;
>  ; Additional positive integers will be assigned as new versions of
> diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
> index 769f1898b22..b5f3045d866 100644
> --- gcc/config/i386/i386.c
> +++ gcc/config/i386/i386.c
> @@ -7188,6 +7188,26 @@ init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
>    cum->force_bnd_pass = 0;
>    cum->decl = fndecl;
>  
> +  cum->warn_empty = !warn_abi || cum->stdarg;
> +  if (!cum->warn_empty && fntype)
> +    {
> +      function_args_iterator iter;
> +      tree argtype;
> +      bool seen_empty_type = false;
> +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
> +	{
> +	  if (VOID_TYPE_P (argtype))
> +	    break;
> +	  if (TYPE_EMPTY_P (argtype))
> +	    seen_empty_type = true;
> +	  else if (seen_empty_type)
> +	    {
> +	      cum->warn_empty = true;
> +	      break;
> +	    }
> +	}
> +    }
> +
>    if (!TARGET_64BIT)
>      {
>        /* If there are variable arguments, then we won't pass anything
> @@ -9875,7 +9895,7 @@ ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
>    indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
>    if (indirect_p)
>      type = build_pointer_type (type);
> -  size = int_size_in_bytes (type);
> +  size = arg_int_size_in_bytes (type);
>    rsize = CEIL (size, UNITS_PER_WORD);
>  
>    nat_mode = type_natural_mode (type, NULL, false);
> @@ -28810,6 +28830,46 @@ ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
>    return align;
>  }
>  
> +/* Implement TARGET_EMPTY_RECORD_P.  */
> +
> +static bool
> +ix86_is_empty_record (const_tree type)
> +{
> +  if (!TARGET_64BIT)
> +    return false;
> +  return default_is_empty_record (type);
> +}
> +
> +/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
> +
> +static void
> +ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
> +{
> +  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
> +
> +  if (!cum->warn_empty)
> +    return;
> +
> +  if (!TYPE_EMPTY_P (type))
> +    return;
> +
> +  const_tree ctx = get_ultimate_context (cum->decl);
> +  if (ctx != NULL_TREE
> +      && !TRANSLATION_UNIT_WARN_EMPTY_P (ctx))
> +    return;
> +
> +  /* If the actual size of the type is zero, then there is no change
> +     in how objects of this size are passed.  */
> +  if (int_size_in_bytes (type) == 0)
> +    return;
> +
> +  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
> +	   "changes in -fabi-version=12 (GCC 8)", type);
> +
> +  /* Only warn once.  */
> +  cum->warn_empty = false;
> +}
> +
>  /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
>     the data type, and ALIGN is the alignment that the object would
>     ordinarily have.  */
> @@ -50357,6 +50417,12 @@ ix86_run_selftests (void)
>  #undef TARGET_CONSTANT_ALIGNMENT
>  #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
>  
> +#undef TARGET_EMPTY_RECORD_P
> +#define TARGET_EMPTY_RECORD_P ix86_is_empty_record
> +
> +#undef TARGET_WARN_PARAMETER_PASSING_ABI
> +#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
> +
>  #if CHECKING_P
>  #undef TARGET_RUN_TARGET_SELFTESTS
>  #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
> diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
> index 837906b5169..7f5d245b568 100644
> --- gcc/config/i386/i386.h
> +++ gcc/config/i386/i386.h
> @@ -1633,6 +1633,8 @@ typedef struct ix86_args {
>    int warn_avx;			/* True when we want to warn about AVX ABI.  */
>    int warn_sse;			/* True when we want to warn about SSE ABI.  */
>    int warn_mmx;			/* True when we want to warn about MMX ABI.  */
> +  int warn_empty;		/* True when we want to warn about empty classes
> +				   passing ABI change.  */
>    int sse_regno;		/* next available sse register number */
>    int mmx_words;		/* # mmx words passed so far */
>    int mmx_nregs;		/* # mmx registers available for passing */
> diff --git gcc/cp/class.c gcc/cp/class.c
> index 98e62c6ad45..c6b0349f8e7 100644
> --- gcc/cp/class.c
> +++ gcc/cp/class.c
> @@ -6198,6 +6198,7 @@ layout_class_type (tree t, tree *virtuals_p)
>  	  DECL_CONTEXT (padding_field) = t;
>  	  DECL_ARTIFICIAL (padding_field) = 1;
>  	  DECL_IGNORED_P (padding_field) = 1;
> +	  DECL_PADDING_P (padding_field) = 1;
>  	  layout_nonempty_base_or_field (rli, padding_field,
>  					 NULL_TREE,
>  					 empty_base_offsets);
> diff --git gcc/cp/decl.c gcc/cp/decl.c
> index 0ce8f2d3435..6f0f1baa179 100644
> --- gcc/cp/decl.c
> +++ gcc/cp/decl.c
> @@ -4012,6 +4012,10 @@ cxx_init_decl_processing (void)
>    TREE_PUBLIC (global_namespace) = 1;
>    DECL_CONTEXT (global_namespace)
>      = build_translation_unit_decl (get_identifier (main_input_filename));
> +  /* Remember whether we want the empty class passing ABI change warning
> +     in this TU.  */
> +  TRANSLATION_UNIT_WARN_EMPTY_P (DECL_CONTEXT (global_namespace))
> +    = warn_abi && abi_version_crosses (12);
>    debug_hooks->register_main_translation_unit
>      (DECL_CONTEXT (global_namespace));
>    begin_scope (sk_namespace, global_namespace);
> @@ -12091,7 +12095,10 @@ grokdeclarator (const cp_declarator *declarator,
>  				   FIELD_DECL, unqualified_id, type);
>  		DECL_NONADDRESSABLE_P (decl) = bitfield;
>  		if (bitfield && !unqualified_id)
> -		  TREE_NO_WARNING (decl) = 1;
> +		  {
> +		    TREE_NO_WARNING (decl) = 1;
> +		    DECL_PADDING_P (decl) = 1;
> +		  }
>  
>  		if (storage_class == sc_mutable)
>  		  {
> diff --git gcc/doc/tm.texi gcc/doc/tm.texi
> index 72606f53f1c..f16e73c31b1 100644
> --- gcc/doc/tm.texi
> +++ gcc/doc/tm.texi
> @@ -4559,6 +4559,16 @@ This target hook returns the mode to be used when accessing raw return registers
>  This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
>  @end deftypefn
>  
> +@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
> +This target hook returns true if the type is an empty record.  The default
> +is to return @code{false}.
> +@end deftypefn
> +
> +@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
> +This target hook warns about the change in empty class parameter passing
> +ABI.
> +@end deftypefn
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
> index e7d4ada290f..39f6fcaaa11 100644
> --- gcc/doc/tm.texi.in
> +++ gcc/doc/tm.texi.in
> @@ -3439,6 +3439,10 @@ nothing when you use @option{-freg-struct-return} mode.
>  
>  @hook TARGET_GET_RAW_ARG_MODE
>  
> +@hook TARGET_EMPTY_RECORD_P
> +
> +@hook TARGET_WARN_PARAMETER_PASSING_ABI
> +
>  @node Caller Saves
>  @subsection Caller-Saves Register Allocation
>  
> diff --git gcc/dwarf2out.c gcc/dwarf2out.c
> index b8f4e4888f1..91d439c2506 100644
> --- gcc/dwarf2out.c
> +++ gcc/dwarf2out.c
> @@ -5097,21 +5097,6 @@ get_AT_file (dw_die_ref die, enum dwarf_attribute attr_kind)
>    return a ? AT_file (a) : NULL;
>  }
>  
> -/* Returns the ultimate TRANSLATION_UNIT_DECL context of DECL or NULL.  */
> -
> -static const_tree
> -get_ultimate_context (const_tree decl)
> -{
> -  while (decl && TREE_CODE (decl) != TRANSLATION_UNIT_DECL)
> -    {
> -      if (TREE_CODE (decl) == BLOCK)
> -	decl = BLOCK_SUPERCONTEXT (decl);
> -      else
> -	decl = get_containing_scope (decl);
> -    }
> -  return decl;
> -}
> -
>  /* Return TRUE if the language is C++.  */
>  
>  static inline bool
> diff --git gcc/explow.c gcc/explow.c
> index 662865d2808..72c2e01a44e 100644
> --- gcc/explow.c
> +++ gcc/explow.c
> @@ -2166,7 +2166,7 @@ hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
>    if (REG_P (val)
>        && GET_MODE (val) == BLKmode)
>      {
> -      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
> +      unsigned HOST_WIDE_INT bytes = arg_int_size_in_bytes (valtype);
>        opt_scalar_int_mode tmpmode;
>  
>        /* int_size_in_bytes can return -1.  We don't need a check here
> diff --git gcc/expr.c gcc/expr.c
> index 76684c11cc3..07fd06fa0cf 100644
> --- gcc/expr.c
> +++ gcc/expr.c
> @@ -2749,7 +2749,7 @@ copy_blkmode_to_reg (machine_mode mode_in, tree src)
>  
>    x = expand_normal (src);
>  
> -  bytes = int_size_in_bytes (TREE_TYPE (src));
> +  bytes = arg_int_size_in_bytes (TREE_TYPE (src));
>    if (bytes == 0)
>      return NULL_RTX;
>  
> diff --git gcc/function.c gcc/function.c
> index fe3d9c1bbf3..1fa538b4b7f 100644
> --- gcc/function.c
> +++ gcc/function.c
> @@ -2084,6 +2084,9 @@ aggregate_value_p (const_tree exp, const_tree fntype)
>    if (TREE_ADDRESSABLE (type))
>      return 1;
>  
> +  if (TYPE_EMPTY_P (type))
> +    return 0;
> +
>    if (flag_pcc_struct_return && AGGREGATE_TYPE_P (type))
>      return 1;
>  
> @@ -2528,6 +2531,9 @@ assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
>        return;
>      }
>  
> +  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
> +					    data->passed_type);
> +
>    entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
>  						    data->promoted_mode,
>  						    data->passed_type,
> @@ -4140,8 +4146,9 @@ locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
>  
>    part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
>  
> -  sizetree
> -    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
> +  sizetree = (type
> +	      ? arg_size_in_bytes (type)
> +	      : size_int (GET_MODE_SIZE (passed_mode)));
>    where_pad = targetm.calls.function_arg_padding (passed_mode, type);
>    boundary = targetm.calls.function_arg_boundary (passed_mode, type);
>    round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
> diff --git gcc/lto-streamer-out.c gcc/lto-streamer-out.c
> index 554f9cc9f01..e127e508f97 100644
> --- gcc/lto-streamer-out.c
> +++ gcc/lto-streamer-out.c
> @@ -1073,6 +1073,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
>  	{
>  	  hstate.add_flag (DECL_PACKED (t));
>  	  hstate.add_flag (DECL_NONADDRESSABLE_P (t));
> +	  hstate.add_flag (DECL_PADDING_P (t));
>  	  hstate.add_int (DECL_OFFSET_ALIGN (t));
>  	}
>        else if (code == VAR_DECL)
> @@ -1166,6 +1167,7 @@ hash_tree (struct streamer_tree_cache_d *cache, hash_map<tree, hashval_t> *map,
>        hstate.commit_flag ();
>        hstate.add_int (TYPE_PRECISION (t));
>        hstate.add_int (TYPE_ALIGN (t));
> +      hstate.add_int (TYPE_EMPTY_P (t));
>      }
>  
>    if (CODE_CONTAINS_STRUCT (code, TS_TRANSLATION_UNIT_DECL))
> diff --git gcc/lto/lto.c gcc/lto/lto.c
> index 63ba73c0dbf..748ef02143c 100644
> --- gcc/lto/lto.c
> +++ gcc/lto/lto.c
> @@ -1087,6 +1087,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>  	{
>  	  compare_values (DECL_PACKED);
>  	  compare_values (DECL_NONADDRESSABLE_P);
> +	  compare_values (DECL_PADDING_P);
>  	  compare_values (DECL_OFFSET_ALIGN);
>  	}
>        else if (code == VAR_DECL)
> @@ -1165,6 +1166,7 @@ compare_tree_sccs_1 (tree t1, tree t2, tree **map)
>  	compare_values (TYPE_NONALIASED_COMPONENT);
>        if (AGGREGATE_TYPE_P (t1))
>  	compare_values (TYPE_TYPELESS_STORAGE);
> +      compare_values (TYPE_EMPTY_P);
>        compare_values (TYPE_PACKED);
>        compare_values (TYPE_RESTRICT);
>        compare_values (TYPE_USER_ALIGN);
> diff --git gcc/stor-layout.c gcc/stor-layout.c
> index 7730ac33e4f..1dd215ec27d 100644
> --- gcc/stor-layout.c
> +++ gcc/stor-layout.c
> @@ -1859,6 +1859,9 @@ finalize_type_size (tree type)
>  	  SET_TYPE_MODE (variant, mode);
>  	}
>      }
> +
> +  /* Handle empty records as per the x86-64 psABI.  */
> +  TYPE_EMPTY_P (type) = targetm.calls.empty_record_p (type);
>  }
>  
>  /* Return a new underlying object for a bitfield started with FIELD.  */
> diff --git gcc/target.def gcc/target.def
> index 577dad8fe86..81aedee80d9 100644
> --- gcc/target.def
> +++ gcc/target.def
> @@ -5055,6 +5055,22 @@ DEFHOOK
>   fixed_size_mode, (int regno),
>   default_get_reg_raw_mode)
>  
> +/* Return true if a type is an empty record.  */
> +DEFHOOK
> +(empty_record_p,
> + "This target hook returns true if the type is an empty record.  The default\n\
> +is to return @code{false}.",
> + bool, (const_tree type),
> + hook_bool_const_tree_false)
> +
> +/* Warn about the change in empty class parameter passing ABI.  */
> +DEFHOOK
> +(warn_parameter_passing_abi,
> + "This target hook warns about the change in empty class parameter passing\n\
> +ABI.",
> + void, (cumulative_args_t ca, tree type),
> + hook_void_CUMULATIVE_ARGS_tree)
> +
>  HOOK_VECTOR_END (calls)
>  
>  DEFHOOK
> diff --git gcc/targhooks.c gcc/targhooks.c
> index dad1e109d23..0edc57b0a15 100644
> --- gcc/targhooks.c
> +++ gcc/targhooks.c
> @@ -756,6 +756,12 @@ hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
>  }
>  
>  void
> +hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
> +				tree ATTRIBUTE_UNUSED)
> +{
> +}
> +
> +void
>  default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
>  			      machine_mode mode ATTRIBUTE_UNUSED,
>  			      const_tree type ATTRIBUTE_UNUSED,
> @@ -2108,6 +2114,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>    /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
>       requires greater alignment, we must perform dynamic alignment.  */
>    if (boundary > align
> +      && !TYPE_EMPTY_P (type)
>        && !integer_zerop (TYPE_SIZE (type)))
>      {
>        t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
> @@ -2134,7 +2141,7 @@ std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
>      }
>  
>    /* Compute the rounded size of the type.  */
> -  type_size = size_in_bytes (type);
> +  type_size = arg_size_in_bytes (type);
>    rounded_size = round_up (type_size, align);
>  
>    /* Reduce rounded_size so it's sharable with the postqueue.  */
> diff --git gcc/targhooks.h gcc/targhooks.h
> index 15bbf5cdf24..e431934cd60 100644
> --- gcc/targhooks.h
> +++ gcc/targhooks.h
> @@ -135,6 +135,8 @@ extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
>    (cumulative_args_t, machine_mode, const_tree, bool);
>  extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
>    (cumulative_args_t, machine_mode, tree, bool);
> +extern void hook_void_CUMULATIVE_ARGS_tree
> +  (cumulative_args_t, tree);
>  extern const char *hook_invalid_arg_for_unprototyped_fn
>    (const_tree, const_tree, const_tree);
>  extern void default_function_arg_advance
> diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
> index e69de29bb2d..20d85ff873e 100644
> --- gcc/testsuite/g++.dg/abi/empty12.C
> +++ gcc/testsuite/g++.dg/abi/empty12.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty12a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty12.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty12.h
> +++ gcc/testsuite/g++.dg/abi/empty12.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
> index e69de29bb2d..34a25bad75d 100644
> --- gcc/testsuite/g++.dg/abi/empty12a.c
> +++ gcc/testsuite/g++.dg/abi/empty12a.c
> @@ -0,0 +1,6 @@
> +#include "empty12.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
> index e69de29bb2d..0cb9a373e35 100644
> --- gcc/testsuite/g++.dg/abi/empty13.C
> +++ gcc/testsuite/g++.dg/abi/empty13.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-x c -fabi-version=11" }
> +// { dg-additional-sources "empty13a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty13.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
> index e69de29bb2d..c61afcda0fb 100644
> --- gcc/testsuite/g++.dg/abi/empty13.h
> +++ gcc/testsuite/g++.dg/abi/empty13.h
> @@ -0,0 +1,9 @@
> +struct dummy { };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
> index e69de29bb2d..b4303a63826 100644
> --- gcc/testsuite/g++.dg/abi/empty13a.c
> +++ gcc/testsuite/g++.dg/abi/empty13a.c
> @@ -0,0 +1,6 @@
> +#include "empty13.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 == -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
> index e69de29bb2d..2868d8ad3f3 100644
> --- gcc/testsuite/g++.dg/abi/empty14.C
> +++ gcc/testsuite/g++.dg/abi/empty14.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty14a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty14.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
> index e69de29bb2d..5842279cf37 100644
> --- gcc/testsuite/g++.dg/abi/empty14.h
> +++ gcc/testsuite/g++.dg/abi/empty14.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[140]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
> index e69de29bb2d..8b3d7800c36 100644
> --- gcc/testsuite/g++.dg/abi/empty14a.c
> +++ gcc/testsuite/g++.dg/abi/empty14a.c
> @@ -0,0 +1,6 @@
> +#include "empty14.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
> index e69de29bb2d..12385f78c78 100644
> --- gcc/testsuite/g++.dg/abi/empty15.C
> +++ gcc/testsuite/g++.dg/abi/empty15.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty15a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty15.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
> index e69de29bb2d..1c6f26f5ae8 100644
> --- gcc/testsuite/g++.dg/abi/empty15.h
> +++ gcc/testsuite/g++.dg/abi/empty15.h
> @@ -0,0 +1,30 @@
> +struct A1 {};
> +struct A2 {};
> +struct B1 { struct A1 a; struct A2 b; };
> +struct B2 { struct A1 a; struct A2 b; };
> +struct C1 { struct B1 a; struct B2 b; };
> +struct C2 { struct B1 a; struct B2 b; };
> +struct D1 { struct C1 a; struct C2 b; };
> +struct D2 { struct C1 a; struct C2 b; };
> +struct E1 { struct D1 a; struct D2 b; };
> +struct E2 { struct D1 a; struct D2 b; };
> +struct F1 { struct E1 a; struct E2 b; };
> +struct F2 { struct E1 a; struct E2 b; };
> +struct G1 { struct F1 a; struct F2 b; };
> +struct G2 { struct F1 a; struct F2 b; };
> +struct H1 { struct G1 a; struct G2 b; };
> +struct H2 { struct G1 a; struct G2 b; };
> +struct I1 { struct H1 a; struct H2 b; };
> +struct I2 { struct H1 a; struct H2 b; };
> +struct J1 { struct I1 a; struct I2 b; };
> +struct J2 { struct I1 a; struct I2 b; };
> +struct dummy { struct J1 a; struct J2 b; };
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
> index e69de29bb2d..325b2c5ba09 100644
> --- gcc/testsuite/g++.dg/abi/empty15a.c
> +++ gcc/testsuite/g++.dg/abi/empty15a.c
> @@ -0,0 +1,6 @@
> +#include "empty15.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
> index e69de29bb2d..1ca52f9011e 100644
> --- gcc/testsuite/g++.dg/abi/empty16.C
> +++ gcc/testsuite/g++.dg/abi/empty16.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty16a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty16.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
> index e69de29bb2d..7552ae06576 100644
> --- gcc/testsuite/g++.dg/abi/empty16.h
> +++ gcc/testsuite/g++.dg/abi/empty16.h
> @@ -0,0 +1,16 @@
> +#ifdef __cplusplus
> +struct A1 {};
> +struct A2 {};
> +struct dummy : A1, A2 {} ;
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
> index e69de29bb2d..6cb7fbccecc 100644
> --- gcc/testsuite/g++.dg/abi/empty16a.c
> +++ gcc/testsuite/g++.dg/abi/empty16a.c
> @@ -0,0 +1,6 @@
> +#include "empty16.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
> index e69de29bb2d..d386e5481af 100644
> --- gcc/testsuite/g++.dg/abi/empty17.C
> +++ gcc/testsuite/g++.dg/abi/empty17.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty17a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty17.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
> index e69de29bb2d..9cf72baca2e 100644
> --- gcc/testsuite/g++.dg/abi/empty17.h
> +++ gcc/testsuite/g++.dg/abi/empty17.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 15;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 15;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 15;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
> index e69de29bb2d..24408fde09c 100644
> --- gcc/testsuite/g++.dg/abi/empty17a.c
> +++ gcc/testsuite/g++.dg/abi/empty17a.c
> @@ -0,0 +1,6 @@
> +#include "empty17.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
> index e69de29bb2d..be69c6a2115 100644
> --- gcc/testsuite/g++.dg/abi/empty18.C
> +++ gcc/testsuite/g++.dg/abi/empty18.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty18a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty18.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
> index e69de29bb2d..86e7ecdd211 100644
> --- gcc/testsuite/g++.dg/abi/empty18.h
> +++ gcc/testsuite/g++.dg/abi/empty18.h
> @@ -0,0 +1,9 @@
> +struct dummy { int d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
> index e69de29bb2d..902860bdc01 100644
> --- gcc/testsuite/g++.dg/abi/empty18a.c
> +++ gcc/testsuite/g++.dg/abi/empty18a.c
> @@ -0,0 +1,6 @@
> +#include "empty18.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
> index e69de29bb2d..84f5b75558b 100644
> --- gcc/testsuite/g++.dg/abi/empty19.C
> +++ gcc/testsuite/g++.dg/abi/empty19.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty19a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty19.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
> index e69de29bb2d..616b87bdd93 100644
> --- gcc/testsuite/g++.dg/abi/empty19.h
> +++ gcc/testsuite/g++.dg/abi/empty19.h
> @@ -0,0 +1,10 @@
> +struct dummy0 { };
> +struct dummy { struct dummy0 d[0]; };
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
> index e69de29bb2d..767b1eb7320 100644
> --- gcc/testsuite/g++.dg/abi/empty19a.c
> +++ gcc/testsuite/g++.dg/abi/empty19a.c
> @@ -0,0 +1,6 @@
> +#include "empty19.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
> index e69de29bb2d..5022033f669 100644
> --- gcc/testsuite/g++.dg/abi/empty20.C
> +++ gcc/testsuite/g++.dg/abi/empty20.C
> @@ -0,0 +1,19 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11 -O0" }
> +
> +struct A { };
> +
> +void f(A, A) { }	// No warning, trailing parms all empty
> +void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +__attribute__ ((always_inline))
> +inline void f(A a, int i) // No warning, always inlined
> +{
> +  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> +int main()
> +{
> +  A a;
> +  f(a,a);
> +  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(a,42);
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
> index e69de29bb2d..3b2e3b836b1 100644
> --- gcc/testsuite/g++.dg/abi/empty21.C
> +++ gcc/testsuite/g++.dg/abi/empty21.C
> @@ -0,0 +1,23 @@
> +// PR c++/60336
> +// { dg-options "-Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct A { };
> +
> +void f(int i, ...)
> +{
> +  va_list ap;
> +  va_start (ap, i);
> +  if (i >= 1)
> +    va_arg (ap, A);
> +  if (i >= 2)
> +    va_arg (ap, int);
> +}
> +
> +int main()
> +{
> +  f(0);
> +  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
> index e69de29bb2d..f4f4a02bf31 100644
> --- gcc/testsuite/g++.dg/abi/empty22.C
> +++ gcc/testsuite/g++.dg/abi/empty22.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty22a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty22.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
> index e69de29bb2d..8d54dc74519 100644
> --- gcc/testsuite/g++.dg/abi/empty22.h
> +++ gcc/testsuite/g++.dg/abi/empty22.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 0;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 0;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 0;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
> index e69de29bb2d..7606c524263 100644
> --- gcc/testsuite/g++.dg/abi/empty22a.c
> +++ gcc/testsuite/g++.dg/abi/empty22a.c
> @@ -0,0 +1,6 @@
> +#include "empty22.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
> index e69de29bb2d..dbeda81fb24 100644
> --- gcc/testsuite/g++.dg/abi/empty23.C
> +++ gcc/testsuite/g++.dg/abi/empty23.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[0];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
> index e69de29bb2d..822ced1ef50 100644
> --- gcc/testsuite/g++.dg/abi/empty24.C
> +++ gcc/testsuite/g++.dg/abi/empty24.C
> @@ -0,0 +1,25 @@
> +// PR c++/60336
> +// { dg-do run }
> +// { dg-options "-Wabi=11" }
> +
> +struct S
> +{
> +  struct { } a;
> +  __extension__ int b[];
> +};
> +
> +struct S s;
> +struct S a[5];
> +
> +void
> +foo (struct S, struct S *arg1, struct S) // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +{
> +  if (arg1 != &a[1])
> +    __builtin_abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo (s, &a[1], a[2]); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty25.C gcc/testsuite/g++.dg/abi/empty25.C
> index e69de29bb2d..da6ef51ff0d 100644
> --- gcc/testsuite/g++.dg/abi/empty25.C
> +++ gcc/testsuite/g++.dg/abi/empty25.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty25a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty25.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-bogus "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty25.h gcc/testsuite/g++.dg/abi/empty25.h
> index e69de29bb2d..2f22fd5e505 100644
> --- gcc/testsuite/g++.dg/abi/empty25.h
> +++ gcc/testsuite/g++.dg/abi/empty25.h
> @@ -0,0 +1,18 @@
> +#ifdef __cplusplus
> +struct dummy
> +{
> +  virtual void bar (void) { }
> +  unsigned int : 15;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty25a.c gcc/testsuite/g++.dg/abi/empty25a.c
> index e69de29bb2d..8c16e453c75 100644
> --- gcc/testsuite/g++.dg/abi/empty25a.c
> +++ gcc/testsuite/g++.dg/abi/empty25a.c
> @@ -0,0 +1,6 @@
> +#include "empty25.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty26.C gcc/testsuite/g++.dg/abi/empty26.C
> index e69de29bb2d..ab2f54d8dab 100644
> --- gcc/testsuite/g++.dg/abi/empty26.C
> +++ gcc/testsuite/g++.dg/abi/empty26.C
> @@ -0,0 +1,17 @@
> +// PR c++/60336
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-Wabi=11 -x c" }
> +// { dg-additional-sources "empty26a.c" }
> +// { dg-prune-output "command line option" }
> +
> +#include "empty26.h"
> +extern "C" void fun(struct dummy, struct foo);
> +
> +int main()
> +{
> +  struct dummy d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  fun(d, f); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty26.h gcc/testsuite/g++.dg/abi/empty26.h
> index e69de29bb2d..8d54dc74519 100644
> --- gcc/testsuite/g++.dg/abi/empty26.h
> +++ gcc/testsuite/g++.dg/abi/empty26.h
> @@ -0,0 +1,27 @@
> +#ifdef __cplusplus
> +struct A1
> +{
> +  void foo (void);
> +  unsigned int : 0;
> +};
> +struct A2
> +{
> +  void bar (void);
> +  unsigned int : 0;
> +};
> +struct dummy : A1, A2
> +{
> +  unsigned int : 0;
> +};
> +#else
> +struct dummy {};
> +#endif
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> diff --git gcc/testsuite/g++.dg/abi/empty26a.c gcc/testsuite/g++.dg/abi/empty26a.c
> index e69de29bb2d..bc0ae47ba2c 100644
> --- gcc/testsuite/g++.dg/abi/empty26a.c
> +++ gcc/testsuite/g++.dg/abi/empty26a.c
> @@ -0,0 +1,6 @@
> +#include "empty26.h"
> +void fun(struct dummy d, struct foo f)
> +{
> +  if (f.i1 != -1)
> +    __builtin_abort();
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty27.C gcc/testsuite/g++.dg/abi/empty27.C
> index e69de29bb2d..5d14e7c6a03 100644
> --- gcc/testsuite/g++.dg/abi/empty27.C
> +++ gcc/testsuite/g++.dg/abi/empty27.C
> @@ -0,0 +1,26 @@
> +// PR c++/60336
> +// { dg-do compile }
> +// { dg-options "-Wabi=12" }
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> +
> +namespace N {
> +  class E { };
> +  void fun (class E, struct foo);
> +}
> +
> +int main()
> +{
> +  N::E d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  N::fun(d, f); // { dg-bogus "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/empty28.C gcc/testsuite/g++.dg/abi/empty28.C
> index e69de29bb2d..7e0765d4468 100644
> --- gcc/testsuite/g++.dg/abi/empty28.C
> +++ gcc/testsuite/g++.dg/abi/empty28.C
> @@ -0,0 +1,28 @@
> +// PR c++/60336
> +// { dg-do compile }
> +// { dg-options "-Wabi=12" }
> +
> +struct foo
> +{
> +  int i1;
> +  int i2;
> +  int i3;
> +  int i4;
> +  int i5;
> +};
> +
> +struct N {
> +  class E { };
> +  void fun (class E, struct foo) { } // { dg-bogus "empty" }
> +};
> +
> +
> +int main()
> +{
> +  struct N n;
> +  N::E d;
> +  struct foo f = { -1, -2, -3, -4, -5 };
> +
> +  n.fun(d, f); // { dg-bogus "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
> index e69de29bb2d..59447890cec 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-1.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-1.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
> index e69de29bb2d..960cc2307d1 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-10.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-10.C
> @@ -0,0 +1,50 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0 { };
> +struct dummy1 { };
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
> index e69de29bb2d..14cd6d0ff3d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-11.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-11.C
> @@ -0,0 +1,56 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +  void bar (void);
> +};
> +struct dummy1
> +{
> +  void foo (void);
> +};
> +struct dummy : dummy0, dummy1 { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
> index e69de29bb2d..09917547930 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-12.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-12.C
> @@ -0,0 +1,57 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2" }
> +
> +#include <stdarg.h>
> +
> +struct dummy0
> +{
> +};
> +struct dummy1
> +{
> +  unsigned : 15;
> +};
> +struct dummy : dummy0, dummy1
> +{
> +};
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
> index e69de29bb2d..1c6c3eb8f01 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-2.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-2.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...) // { dg-warning "empty" }
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
> index e69de29bb2d..4157e553b6b 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-3.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-3.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
> index e69de29bb2d..266f67a537d 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-4.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-4.C
> @@ -0,0 +1,48 @@
> +// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +// { dg-options "-O2 -fabi-version=11" }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count == 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
> index e69de29bb2d..fe838750f55 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-5.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-5.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i; struct dummy j; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
> index e69de29bb2d..6e08c8f06fa 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-6.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-6.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i1; struct dummy i2; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
> index e69de29bb2d..3b8b8ba6f35 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-7.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-7.C
> @@ -0,0 +1,17 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct dummy { };
> +struct true_type { struct dummy i[120]; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
> index e69de29bb2d..a1ffb64ef02 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-8.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-8.C
> @@ -0,0 +1,15 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -Wabi=11" }
> +
> +struct dummy { struct{} a[7][3]; };
> +
> +extern void test1 (struct dummy, ...);
> +extern void (*test2) (struct dummy, ...);
> +
> +void
> +foo ()
> +{
> +  struct dummy a0;
> +  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
> +}
> diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
> index e69de29bb2d..393f02b62f0 100644
> --- gcc/testsuite/g++.dg/abi/pr60336-9.C
> +++ gcc/testsuite/g++.dg/abi/pr60336-9.C
> @@ -0,0 +1,28 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +struct A1 {}; struct A2 {};
> +struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
> +struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
> +struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
> +struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
> +struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
> +struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
> +struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
> +struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
> +struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
> +struct dummy { J1 a; J2 b; };
> +
> +struct true_type { struct dummy i; };
> +
> +extern true_type y;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
> diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
> index e69de29bb2d..1354fc497b5 100644
> --- gcc/testsuite/g++.dg/abi/pr68355.C
> +++ gcc/testsuite/g++.dg/abi/pr68355.C
> @@ -0,0 +1,24 @@
> +// { dg-do compile }
> +// { dg-options "-O2 -std=c++11 -fno-pic" }
> +// { dg-require-effective-target fpic }
> +
> +template<typename _Tp, _Tp __v>
> +struct integral_constant
> +{
> +  static constexpr _Tp value = __v;
> +  typedef _Tp value_type;
> +  typedef integral_constant<_Tp, __v> type;
> +  constexpr operator value_type() const { return value; }
> +};
> +
> +typedef integral_constant<bool, true> true_type;
> +extern void xxx (true_type c);
> +
> +void
> +yyy (void)
> +{
> +  true_type y;
> +  xxx (y);
> +}
> +
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
> diff --git gcc/testsuite/g++.dg/lto/pr60336_0.C gcc/testsuite/g++.dg/lto/pr60336_0.C
> index e69de29bb2d..a0a598c0029 100644
> --- gcc/testsuite/g++.dg/lto/pr60336_0.C
> +++ gcc/testsuite/g++.dg/lto/pr60336_0.C
> @@ -0,0 +1,47 @@
> +// { dg-lto-do run }
> +
> +#include <stdarg.h>
> +
> +struct dummy { };
> +
> +void
> +test (struct dummy a, int m, ...)
> +{
> +  va_list va_arglist;
> +  int i;
> +  int count = 0;
> +
> +  if (m == 0)
> +    count++;
> +  va_start (va_arglist, m);
> +  i = va_arg (va_arglist, int);
> +  if (i == 1)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 2)
> +  i = va_arg (va_arglist, int);
> +    count++;
> +  if (i == 3)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 4)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 5)
> +    count++;
> +  i = va_arg (va_arglist, int);
> +  if (i == 6)
> +    count++;
> +  va_end (va_arglist);
> +  if (count != 7)
> +    __builtin_abort ();
> +}
> +
> +struct dummy a0;
> +
> +int
> +main ()
> +{
> +  test (a0, 0, 1, 2, 3, 4, 5, 6);
> +  return 0;
> +}
> diff --git gcc/tree-core.h gcc/tree-core.h
> index f74f1453de6..13ff5e3db71 100644
> --- gcc/tree-core.h
> +++ gcc/tree-core.h
> @@ -1531,7 +1531,8 @@ struct GTY(()) tree_type_common {
>    unsigned align : 6;
>    unsigned warn_if_not_align : 6;
>    unsigned typeless_storage : 1;
> -  unsigned spare : 18;
> +  unsigned empty_flag : 1;
> +  unsigned spare : 17;
>  
>    alias_set_type alias_set;
>    tree pointer_to;
> @@ -1609,7 +1610,8 @@ struct GTY(()) tree_decl_common {
>    unsigned lang_flag_7 : 1;
>    unsigned lang_flag_8 : 1;
>  
> -  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER.  */
> +  /* In VAR_DECL and PARM_DECL, this is DECL_REGISTER
> +     IN TRANSLATION_UNIT_DECL, this is TRANSLATION_UNIT_WARN_EMPTY_P.  */
>    unsigned decl_flag_0 : 1;
>    /* In FIELD_DECL, this is DECL_BIT_FIELD
>       In VAR_DECL and FUNCTION_DECL, this is DECL_EXTERNAL.
> @@ -1619,7 +1621,7 @@ struct GTY(()) tree_decl_common {
>       In VAR_DECL, PARM_DECL and RESULT_DECL, this is
>       DECL_HAS_VALUE_EXPR_P.  */
>    unsigned decl_flag_2 : 1;
> -  /* 1 bit unused.  */
> +  /* In FIELD_DECL, this is DECL_PADDING_P.  */
>    unsigned decl_flag_3 : 1;
>    /* Logically, these two would go in a theoretical base shared by var and
>       parm decl. */
> diff --git gcc/tree-streamer-in.c gcc/tree-streamer-in.c
> index baf0c5bf837..36402c6a39c 100644
> --- gcc/tree-streamer-in.c
> +++ gcc/tree-streamer-in.c
> @@ -251,6 +251,7 @@ unpack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
>      {
>        DECL_PACKED (expr) = (unsigned) bp_unpack_value (bp, 1);
>        DECL_NONADDRESSABLE_P (expr) = (unsigned) bp_unpack_value (bp, 1);
> +      DECL_PADDING_P (expr) = (unsigned) bp_unpack_value (bp, 1);
>        expr->decl_common.off_align = bp_unpack_value (bp, 8);
>      }
>  
> @@ -381,6 +382,7 @@ unpack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>      TYPE_NONALIASED_COMPONENT (expr) = (unsigned) bp_unpack_value (bp, 1);
>    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_PRECISION (expr) = bp_unpack_var_len_unsigned (bp);
>    SET_TYPE_ALIGN (expr, bp_unpack_var_len_unsigned (bp));
>  #ifdef ACCEL_COMPILER
> diff --git gcc/tree-streamer-out.c gcc/tree-streamer-out.c
> index 7f52d455f5e..08c58a4709d 100644
> --- gcc/tree-streamer-out.c
> +++ gcc/tree-streamer-out.c
> @@ -211,6 +211,7 @@ pack_ts_decl_common_value_fields (struct bitpack_d *bp, tree expr)
>      {
>        bp_pack_value (bp, DECL_PACKED (expr), 1);
>        bp_pack_value (bp, DECL_NONADDRESSABLE_P (expr), 1);
> +      bp_pack_value (bp, DECL_PADDING_P (expr), 1);
>        bp_pack_value (bp, expr->decl_common.off_align, 8);
>      }
>  
> @@ -330,6 +331,7 @@ pack_ts_type_common_value_fields (struct bitpack_d *bp, tree expr)
>      bp_pack_value (bp, TYPE_NONALIASED_COMPONENT (expr), 1);
>    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_var_len_unsigned (bp, TYPE_PRECISION (expr));
>    bp_pack_var_len_unsigned (bp, TYPE_ALIGN (expr));
>  }
> diff --git gcc/tree.c gcc/tree.c
> index 28e157f5fd2..6c8f74b52e2 100644
> --- gcc/tree.c
> +++ gcc/tree.c
> @@ -8701,6 +8701,21 @@ get_containing_scope (const_tree t)
>    return (TYPE_P (t) ? TYPE_CONTEXT (t) : DECL_CONTEXT (t));
>  }
>  
> +/* Returns the ultimate TRANSLATION_UNIT_DECL context of DECL or NULL.  */
> +
> +const_tree
> +get_ultimate_context (const_tree decl)
> +{
> +  while (decl && TREE_CODE (decl) != TRANSLATION_UNIT_DECL)
> +    {
> +      if (TREE_CODE (decl) == BLOCK)
> +	decl = BLOCK_SUPERCONTEXT (decl);
> +      else
> +	decl = get_containing_scope (decl);
> +    }
> +  return decl;
> +}
> +
>  /* Return the innermost context enclosing DECL that is
>     a FUNCTION_DECL, or zero if none.  */
>  
> @@ -13811,6 +13826,62 @@ get_nonnull_args (const_tree fntype)
>    return argmap;
>  }
>  
> +/* Returns true if TYPE is a type where it and all of its subobjects
> +   (recursively) are of structure, union, or array type.  */
> +
> +static bool
> +default_is_empty_type (tree type)
> +{
> +  if (RECORD_OR_UNION_TYPE_P (type))
> +    {
> +      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
> +	if (TREE_CODE (field) == FIELD_DECL
> +	    && !DECL_PADDING_P (field)
> +	    && !default_is_empty_type (TREE_TYPE (field)))
> +	  return false;
> +      return true;
> +    }
> +  else if (TREE_CODE (type) == ARRAY_TYPE)
> +    return (integer_minus_onep (array_type_nelts (type))
> +	    || TYPE_DOMAIN (type) == NULL_TREE
> +	    || default_is_empty_type (TREE_TYPE (type)));
> +  return false;
> +}
> +
> +/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
> +   that shouldn't be passed via stack.  */
> +
> +bool
> +default_is_empty_record (const_tree type)
> +{
> +  if (!abi_version_at_least (12))
> +    return false;
> +
> +  if (type == error_mark_node)
> +    return false;
> +
> +  if (TREE_ADDRESSABLE (type))
> +    return false;
> +
> +  return default_is_empty_type (TYPE_MAIN_VARIANT (type));
> +}
> +
> +/* Like int_size_in_bytes, but handle empty records specially.  */
> +
> +HOST_WIDE_INT
> +arg_int_size_in_bytes (const_tree type)
> +{
> +  return TYPE_EMPTY_P (type) ? 0 : int_size_in_bytes (type);
> +}
> +
> +/* Like size_in_bytes, but handle empty records specially.  */
> +
> +tree
> +arg_size_in_bytes (const_tree type)
> +{
> +  return TYPE_EMPTY_P (type) ? size_zero_node : size_in_bytes (type);
> +}
> +
>  /* List of pointer types used to declare builtins before we have seen their
>     real declaration.
>  
> diff --git gcc/tree.h gcc/tree.h
> index 277aa919780..2fad4e18d1c 100644
> --- gcc/tree.h
> +++ gcc/tree.h
> @@ -696,6 +696,14 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
>     emitted.  */
>  #define TREE_NO_WARNING(NODE) ((NODE)->base.nowarning_flag)
>  
> +/* Nonzero if we should warn about the change in empty class parameter
> +   passing ABI in this TU.  */
> +#define TRANSLATION_UNIT_WARN_EMPTY_P(NODE) \
> +  (TRANSLATION_UNIT_DECL_CHECK (NODE)->decl_common.decl_flag_0)
> +
> +/* Nonzero if this type is "empty" according to the particular psABI.  */
> +#define TYPE_EMPTY_P(NODE) (TYPE_CHECK (NODE)->type_common.empty_flag)
> +
>  /* Used to indicate that this TYPE represents a compiler-generated entity.  */
>  #define TYPE_ARTIFICIAL(NODE) (TYPE_CHECK (NODE)->base.nowarning_flag)
>  
> @@ -2619,6 +2627,10 @@ extern void decl_value_expr_insert (tree, tree);
>  #define DECL_NONADDRESSABLE_P(NODE) \
>    (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_2)
>  
> +/* Used in a FIELD_DECL to indicate that this field is padding.  */
> +#define DECL_PADDING_P(NODE) \
> +  (FIELD_DECL_CHECK (NODE)->decl_common.decl_flag_3)
> +
>  /* A numeric unique identifier for a LABEL_DECL.  The UID allocation is
>     dense, unique within any one function, and may be used to index arrays.
>     If the value is -1, then no UID has been assigned.  */
> @@ -4512,6 +4524,10 @@ storage_order_barrier_p (const_tree t)
>  
>  extern tree get_containing_scope (const_tree);
>  
> +/* Returns the ultimate TRANSLATION_UNIT_DECL context of DECL or NULL.  */
> +
> +extern const_tree get_ultimate_context (const_tree);
> +
>  /* Return the FUNCTION_DECL which provides this _DECL with its context,
>     or zero if none.  */
>  extern tree decl_function_context (const_tree);
> @@ -5428,6 +5444,9 @@ extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
>  
>  extern bool nonnull_arg_p (const_tree);
>  extern bool is_redundant_typedef (const_tree);
> +extern bool default_is_empty_record (const_tree);
> +extern HOST_WIDE_INT arg_int_size_in_bytes (const_tree);
> +extern tree arg_size_in_bytes (const_tree);
>  
>  extern location_t
>  set_source_range (tree expr, location_t start, location_t finish);
> 
> 	Marek
> 
>
Jakub Jelinek Nov. 13, 2017, 10:47 a.m. UTC | #26
On Mon, Nov 13, 2017 at 11:44:38AM +0100, Richard Biener wrote:
> > Unfortunately, it's not what I see.  I ran two bootstraps, with and without the
> > patch.  Then I stripped all the .o files and ran cmp on them, but many of them
> > differ.  objdump -dr reveals why -- I see changes like
> > 
> >   mov    $0xe02,%ecx
> > vs
> >   mov    $0xdf6,%ecx
> > 
> > That's probably some sizeof changes?  Any other ideas? :/  Somehow ignore
> > these and only look if there are new pushes or similar?
> 
> sizeof an empty type shouldn't change, no?  Possibly the new target
> hook "shifts" target hook offsets.  Maybe you can arrange the new one
> to be last... (just for testing).  OTOH for example in target library
> objects all the changes of this kind should have no effect.  So, do
> objects in target libraries differ similarly?

If the changes are because of the patch changing stuff in bootstrapped
compiler, the easiest way is to revert the patch and rebuild stage3 inside
of the stage3 directory and then compare what you get with the unpatched
bootstrapped compiler (or, if you first bootstrap patched tree and then
unpatched, similarly, re-apply the patch and rebuild stage3 of the
originally vanilla tree).

	Jakub
Marek Polacek Nov. 13, 2017, 6:02 p.m. UTC | #27
On Mon, Nov 13, 2017 at 11:47:41AM +0100, Jakub Jelinek wrote:
> On Mon, Nov 13, 2017 at 11:44:38AM +0100, Richard Biener wrote:
> > > Unfortunately, it's not what I see.  I ran two bootstraps, with and without the
> > > patch.  Then I stripped all the .o files and ran cmp on them, but many of them
> > > differ.  objdump -dr reveals why -- I see changes like
> > > 
> > >   mov    $0xe02,%ecx
> > > vs
> > >   mov    $0xdf6,%ecx
> > > 
> > > That's probably some sizeof changes?  Any other ideas? :/  Somehow ignore
> > > these and only look if there are new pushes or similar?
> > 
> > sizeof an empty type shouldn't change, no?  Possibly the new target
> > hook "shifts" target hook offsets.  Maybe you can arrange the new one
> > to be last... (just for testing).  OTOH for example in target library
> > objects all the changes of this kind should have no effect.  So, do
> > objects in target libraries differ similarly?
> 
> If the changes are because of the patch changing stuff in bootstrapped
> compiler, the easiest way is to revert the patch and rebuild stage3 inside
> of the stage3 directory and then compare what you get with the unpatched
> bootstrapped compiler (or, if you first bootstrap patched tree and then
> unpatched, similarly, re-apply the patch and rebuild stage3 of the
> originally vanilla tree).

In the end I did two bootstraps with the patch, but modifed one of them
to always return false for ix86_is_empty_record.  Then I compared all the
*.o in both dirs.  The result is attached.  Then I looked at DW_AT_producer
for all these .o that differ; all of them are C++.  Is this enough to
clear our concerns?

And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and didn't
see any warnings.

	Marek
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o.strip differ: byte 49677, line 21
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o.strip differ: byte 49021, line 19
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o.strip differ: byte 41, line 1
trunk/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o.strip trunk2/./stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o.strip differ: byte 56433, line 33
trunk/./prev-gcc/gcov.o.strip trunk2/./prev-gcc/gcov.o.strip differ: byte 41, line 1
trunk/./prev-gcc/cc1plus-checksum.o.strip trunk2/./prev-gcc/cc1plus-checksum.o.strip differ: byte 65, line 1
trunk/./prev-gcc/cc1objplus-checksum.o.strip trunk2/./prev-gcc/cc1objplus-checksum.o.strip differ: byte 65, line 1
trunk/./prev-gcc/go/gogo.o.strip trunk2/./prev-gcc/go/gogo.o.strip differ: byte 41, line 1
trunk/./prev-gcc/go/expressions.o.strip trunk2/./prev-gcc/go/expressions.o.strip differ: byte 41, line 1
trunk/./prev-gcc/go/types.o.strip trunk2/./prev-gcc/go/types.o.strip differ: byte 41, line 1
trunk/./prev-gcc/go/export.o.strip trunk2/./prev-gcc/go/export.o.strip differ: byte 41, line 1
trunk/./prev-gcc/go/statements.o.strip trunk2/./prev-gcc/go/statements.o.strip differ: byte 41, line 1
trunk/./prev-gcc/bb-reorder.o.strip trunk2/./prev-gcc/bb-reorder.o.strip differ: byte 41, line 1
trunk/./prev-gcc/cc1-checksum.o.strip trunk2/./prev-gcc/cc1-checksum.o.strip differ: byte 65, line 1
trunk/./prev-gcc/cc1obj-checksum.o.strip trunk2/./prev-gcc/cc1obj-checksum.o.strip differ: byte 65, line 1
trunk/./prev-gcc/build/genrecog.o.strip trunk2/./prev-gcc/build/genrecog.o.strip differ: byte 41, line 1
trunk/./prev-gcc/i386.o.strip trunk2/./prev-gcc/i386.o.strip differ: byte 41, line 1
trunk/./prev-gcc/tree-loop-distribution.o.strip trunk2/./prev-gcc/tree-loop-distribution.o.strip differ: byte 41, line 1
trunk/./stage1-gcc/cc1plus-checksum.o.strip trunk2/./stage1-gcc/cc1plus-checksum.o.strip differ: byte 65, line 1
trunk/./stage1-gcc/cc1-checksum.o.strip trunk2/./stage1-gcc/cc1-checksum.o.strip differ: byte 65, line 1
trunk/./stage1-gcc/i386.o.strip trunk2/./stage1-gcc/i386.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o.strip differ: byte 49677, line 21
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o.strip differ: byte 49021, line 19
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o.strip differ: byte 41, line 1
trunk/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o.strip trunk2/./prev-x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o.strip differ: byte 56433, line 33
trunk/./gcc/gcov.o.strip trunk2/./gcc/gcov.o.strip differ: byte 41, line 1
trunk/./gcc/cc1plus-checksum.o.strip trunk2/./gcc/cc1plus-checksum.o.strip differ: byte 65, line 1
trunk/./gcc/cc1objplus-checksum.o.strip trunk2/./gcc/cc1objplus-checksum.o.strip differ: byte 65, line 1
trunk/./gcc/go/gogo.o.strip trunk2/./gcc/go/gogo.o.strip differ: byte 41, line 1
trunk/./gcc/go/expressions.o.strip trunk2/./gcc/go/expressions.o.strip differ: byte 41, line 1
trunk/./gcc/go/types.o.strip trunk2/./gcc/go/types.o.strip differ: byte 41, line 1
trunk/./gcc/go/export.o.strip trunk2/./gcc/go/export.o.strip differ: byte 41, line 1
trunk/./gcc/go/statements.o.strip trunk2/./gcc/go/statements.o.strip differ: byte 41, line 1
trunk/./gcc/bb-reorder.o.strip trunk2/./gcc/bb-reorder.o.strip differ: byte 41, line 1
trunk/./gcc/cc1-checksum.o.strip trunk2/./gcc/cc1-checksum.o.strip differ: byte 65, line 1
trunk/./gcc/cc1obj-checksum.o.strip trunk2/./gcc/cc1obj-checksum.o.strip differ: byte 65, line 1
trunk/./gcc/build/genrecog.o.strip trunk2/./gcc/build/genrecog.o.strip differ: byte 41, line 1
trunk/./gcc/i386.o.strip trunk2/./gcc/i386.o.strip differ: byte 41, line 1
trunk/./gcc/tree-loop-distribution.o.strip trunk2/./gcc/tree-loop-distribution.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libsanitizer/asan/asan_memory_profile.o.strip trunk2/./x86_64-pc-linux-gnu/libsanitizer/asan/asan_memory_profile.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libsanitizer/asan/asan_interceptors.o.strip trunk2/./x86_64-pc-linux-gnu/libsanitizer/asan/asan_interceptors.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libsanitizer/asan/.libs/asan_memory_profile.o.strip trunk2/./x86_64-pc-linux-gnu/libsanitizer/asan/.libs/asan_memory_profile.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libsanitizer/asan/.libs/asan_interceptors.o.strip trunk2/./x86_64-pc-linux-gnu/libsanitizer/asan/.libs/asan_interceptors.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libsanitizer/tsan/tsan_interceptors.o.strip trunk2/./x86_64-pc-linux-gnu/libsanitizer/tsan/tsan_interceptors.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libsanitizer/tsan/.libs/tsan_interceptors.o.strip trunk2/./x86_64-pc-linux-gnu/libsanitizer/tsan/.libs/tsan_interceptors.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o.strip differ: byte 49677, line 21
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o.strip differ: byte 49021, line 19
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o.strip differ: byte 41, line 1
trunk/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o.strip trunk2/./x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o.strip differ: byte 56433, line 33
Jason Merrill Nov. 14, 2017, 5:21 a.m. UTC | #28
On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek <polacek@redhat.com> wrote:
> On Mon, Nov 13, 2017 at 11:47:41AM +0100, Jakub Jelinek wrote:
>> On Mon, Nov 13, 2017 at 11:44:38AM +0100, Richard Biener wrote:
>> > > Unfortunately, it's not what I see.  I ran two bootstraps, with and without the
>> > > patch.  Then I stripped all the .o files and ran cmp on them, but many of them
>> > > differ.  objdump -dr reveals why -- I see changes like
>> > >
>> > >   mov    $0xe02,%ecx
>> > > vs
>> > >   mov    $0xdf6,%ecx
>> > >
>> > > That's probably some sizeof changes?  Any other ideas? :/  Somehow ignore
>> > > these and only look if there are new pushes or similar?
>> >
>> > sizeof an empty type shouldn't change, no?  Possibly the new target
>> > hook "shifts" target hook offsets.  Maybe you can arrange the new one
>> > to be last... (just for testing).  OTOH for example in target library
>> > objects all the changes of this kind should have no effect.  So, do
>> > objects in target libraries differ similarly?
>>
>> If the changes are because of the patch changing stuff in bootstrapped
>> compiler, the easiest way is to revert the patch and rebuild stage3 inside
>> of the stage3 directory and then compare what you get with the unpatched
>> bootstrapped compiler (or, if you first bootstrap patched tree and then
>> unpatched, similarly, re-apply the patch and rebuild stage3 of the
>> originally vanilla tree).
>
> In the end I did two bootstraps with the patch, but modifed one of them
> to always return false for ix86_is_empty_record.  Then I compared all the
> *.o in both dirs.  The result is attached.  Then I looked at DW_AT_producer
> for all these .o that differ; all of them are C++.  Is this enough to
> clear our concerns?

Hmm, a bunch of these are right at the beginning, bytes 41 and 65, in
the header.

Did you build them in the different trunk/trunk2 directories?  I think
Jakub was suggesting building them in the same directory.

> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and didn't
> see any warnings.

If there's a codegen change, there ought to be a warning to go along with it.

Jason
Richard Biener Nov. 14, 2017, 6:34 a.m. UTC | #29
On November 14, 2017 6:21:41 AM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
>On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek <polacek@redhat.com>
>wrote:
>> On Mon, Nov 13, 2017 at 11:47:41AM +0100, Jakub Jelinek wrote:
>>> On Mon, Nov 13, 2017 at 11:44:38AM +0100, Richard Biener wrote:
>>> > > Unfortunately, it's not what I see.  I ran two bootstraps, with
>and without the
>>> > > patch.  Then I stripped all the .o files and ran cmp on them,
>but many of them
>>> > > differ.  objdump -dr reveals why -- I see changes like
>>> > >
>>> > >   mov    $0xe02,%ecx
>>> > > vs
>>> > >   mov    $0xdf6,%ecx
>>> > >
>>> > > That's probably some sizeof changes?  Any other ideas? :/ 
>Somehow ignore
>>> > > these and only look if there are new pushes or similar?
>>> >
>>> > sizeof an empty type shouldn't change, no?  Possibly the new
>target
>>> > hook "shifts" target hook offsets.  Maybe you can arrange the new
>one
>>> > to be last... (just for testing).  OTOH for example in target
>library
>>> > objects all the changes of this kind should have no effect.  So,
>do
>>> > objects in target libraries differ similarly?
>>>
>>> If the changes are because of the patch changing stuff in
>bootstrapped
>>> compiler, the easiest way is to revert the patch and rebuild stage3
>inside
>>> of the stage3 directory and then compare what you get with the
>unpatched
>>> bootstrapped compiler (or, if you first bootstrap patched tree and
>then
>>> unpatched, similarly, re-apply the patch and rebuild stage3 of the
>>> originally vanilla tree).
>>
>> In the end I did two bootstraps with the patch, but modifed one of
>them
>> to always return false for ix86_is_empty_record.  Then I compared all
>the
>> *.o in both dirs.  The result is attached.  Then I looked at
>DW_AT_producer
>> for all these .o that differ; all of them are C++.  Is this enough to
>> clear our concerns?
>
>Hmm, a bunch of these are right at the beginning, bytes 41 and 65, in
>the header.
>
>Did you build them in the different trunk/trunk2 directories?  I think
>Jakub was suggesting building them in the same directory.
>
>> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and
>didn't
>> see any warnings.
>
>If there's a codegen change, there ought to be a warning to go along
>with it.

The question was of course also for unintended changes but yes (I was mainly concerned by libstdc++ ABI changes). 

Richard. 

>Jason
Marek Polacek Nov. 16, 2017, 5:41 p.m. UTC | #30
On Tue, Nov 14, 2017 at 07:34:54AM +0100, Richard Biener wrote:
> On November 14, 2017 6:21:41 AM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
> >On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek <polacek@redhat.com>
> >> In the end I did two bootstraps with the patch, but modifed one of
> >them
> >> to always return false for ix86_is_empty_record.  Then I compared all
> >the
> >> *.o in both dirs.  The result is attached.  Then I looked at
> >DW_AT_producer
> >> for all these .o that differ; all of them are C++.  Is this enough to
> >> clear our concerns?
> >
> >Hmm, a bunch of these are right at the beginning, bytes 41 and 65, in
> >the header.
> >
> >Did you build them in the different trunk/trunk2 directories?  I think
> >Jakub was suggesting building them in the same directory.
> >> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and
> >didn't
> >> see any warnings.
> >
> >If there's a codegen change, there ought to be a warning to go along
> >with it.
> 
> The question was of course also for unintended changes but yes (I was mainly concerned by libstdc++ ABI changes). 

Ok, I did two bootstraps in the same dir, one with ix86_is_empty_record always
returning false.  There were a few object files that differ in their assembly
between those two bootstraps.  Previously I didn't see any warnings because
I hadn't thought of -Wsystem-headers.  Also, we intentionally don't warn if
the empty parameter is the last one:

+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+       {
+         if (VOID_TYPE_P (argtype))
+           break;
+         if (TYPE_EMPTY_P (argtype))
+           seen_empty_type = true;
+         else if (seen_empty_type)
+           {
+             cum->warn_empty = true;
+             break;
+           }
+       }

After enabling -Wsystem-headers and tweaking the code above so that we warn
even if the empty parameter is trailing I can see the warnings that correspond
to the assembly changes.  Below is a summary of what I found.  TL;DR: I don't
see any unintended changes.

gcc/gcov.o
  includes #define INCLUDE_ALGORITHM so we'll get bits/stl_algo.h with stuff
  like
  bits/stl_algo.h:1840:5: warning: empty class ‘__gnu_cxx::__ops::_Iter_comp_iter<function_line_start_cmp>’ 
     __insertion_sort(_RandomAccessIterator __first,

gcc/go/gogo.o
  includes bits/stl_vector.h:
  1535           _M_range_insert(__pos, __first, __last,
  1536                           std::__iterator_category(__first));
  warning: empty class ‘std::forward_iterator_tag’

gcc/go/expressions.o
gcc/go/export.o
  includes bits/stl_algo.h:
  std::__insertion_sort(__first, __last, __comp);
  warning: empty class ‘__gnu_cxx::__ops::_Iter_less_iter’

gcc/go/types.o
  includes bits/stl_algo.h:
  std::__final_insertion_sort(__first, __last, __comp);
  warning: empty class ‘__gnu_cxx::__ops::_Iter_comp_iter<Typed_identifier_list_sort>’

gcc/go/statements.o
  bits/hashtable.h:2068:4: warning: empty class ‘std::integral_constant<bool, true>’

gcc/build/genrecog.o
  includes #define INCLUDE_ALGORITHM
  warning: empty class ‘__gnu_cxx::__ops::_Iter_less_iter’
  342           std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value),
  343                              __comp);

gcc/i386.o
gcc/bb-reorder.o
  expected changes

gcc/tree-loop-distribution.o
  uses std::stable_sort

x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/bitmap_allocator.o
  ./src/c++98/bitmap_allocator.cc
  57     iterator __tmp = __lower_bound(__free_list.begin(), __free_list.end(),
  58                                    __sz, _LT_pointer_compare());
  warning: empty class ‘__gnu_cxx::free_list::_LT_pointer_compare’ 

x86_64-pc-linux-gnu/libstdc++-v3/src/c++98/messages_members.o
x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/sstream-inst.o
x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-wlocale-inst.o
  includes bits/basic_string.h:
  236           _M_construct(__beg, __end, _Tag());
  warning: empty class ‘std::forward_iterator_tag’

x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-shim_facets.o
  includes c++11/cxx11-shim_facets.cc:
  271           return __collate_compare(other_abi{}, _M_get(),
  272                                    lo1, hi1, lo2, hi2);
  warning: empty class ‘std::integral_constant<bool, true>’

x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-string-inst.o
x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/string-inst.o
x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cow-wstring-inst.o
  includes basic_string.tcc:
  563       basic_string<_CharT, _Traits, _Alloc>::
  564       _S_construct(_InIterator __beg, _InIterator __end, const _Alloc& __a,
  565                    forward_iterator_tag)
  warning: empty class ‘std::forward_iterator_tag’

x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/cxx11-shim_facets.o
  see above

x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/locale-inst.o
x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wlocale-inst.o
  includes bits/basic_string.h:
  5033           return _S_construct(__beg, __end, __a, _Tag());
  warning: empty class ‘std::forward_iterator_tag’

x86_64-pc-linux-gnu/libstdc++-v3/src/c++11/wstring-inst.o
  includes bits/basic_string.tcc:
  206       basic_string<_CharT, _Traits, _Alloc>::
  207       _M_construct(_InIterator __beg, _InIterator __end,
  208                    std::forward_iterator_tag)
  warning: empty class ‘std::forward_iterator_tag’

x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/ops.o
  includes bits/stl_deque.h:
  2012           _M_range_insert_aux(__pos, __first, __last,
  2013                               std::__iterator_category(__first));
  warning: empty class ‘std::forward_iterator_tag’

x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-path.o
  includes bits/basic_string.tcc:
  1104       basic_string<_CharT, _Traits, _Alloc>::
  1105       _M_replace_dispatch(iterator __i1, iterator __i2, _InputIterator __k1,
  1106                           _InputIterator __k2, __false_type)
  warning: empty class ‘std::__false_type’

x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-ops.o
x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/std-ops.o
x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-ops.o
  includes bits/deque.tcc:
  586       _M_range_insert_aux(iterator __pos,
  587                           _ForwardIterator __first, _ForwardIterator __last,
  588                           std::forward_iterator_tag)
  warning: empty class ‘std::forward_iterator_tag’

x86_64-pc-linux-gnu/libstdc++-v3/src/filesystem/cow-std-path.o
  includes bits/basic_string.h:
  4884           return _M_replace_dispatch(__i1, __i2, __k1, __k2, _Integral());
  warning: empty class ‘std::__false_type’

x86_64-pc-linux-gnu/libsanitizer/asan/asan_memory_profile.o
  warning: empty class ‘__asan::HeapProfile::Print(__sanitizer::uptr, __sanitizer::uptr)::<lambda(const __asan::AllocationSite&, const __asan::AllocationSite&)>’ parameter passing ABI changes in -fabi-version=12 (GCC 8) [-Wabi]
 void InternalSort(Container *v, uptr size, Compare comp) {

x86_64-pc-linux-gnu/libsanitizer/asan/asan_interceptors.o
  sanitizer_common_interceptors_ioctl.inc:477:15: warning: empty class ‘ioctl_desc_compare’ parameter passing ABI changes in -fabi-version=12 (GCC 8) [-Wabi]
   InternalSort(&ioctl_table, ioctl_table_size, ioctl_desc_compare());

x86_64-pc-linux-gnu/libsanitizer/asan/.libs/asan_memory_profile.o
  asan_memory_profile.cc:50:17: warning: empty class ‘__asan::HeapProfile::Print(__sanitizer::uptr, __sanitizer::uptr)::<lambda(const __asan::AllocationSite&, const __asan::AllocationSite&)>’ parameter passing  ABI changes in -fabi-version=12 (GCC 8) [-Wabi]
     InternalSort(&allocations_, allocations_.size(),
     ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  [](const AllocationSite &a, const AllocationSite &b) {
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    return a.total_size > b.total_size;

x86_64-pc-linux-gnu/libsanitizer/asan/.libs/asan_interceptors.o
  sanitizer_common.h:573:6: warning: empty class ‘ioctl_desc_compare’ parameter passing ABI changes in -fabi-version=12 (GCC 8) [-Wabi]
  void InternalSort(Container *v, uptr size, Compare comp) {

x86_64-pc-linux-gnu/libsanitizer/tsan/tsan_interceptors.o
x86_64-pc-linux-gnu/libsanitizer/tsan/.libs/tsan_interceptors.o
  sanitizer_common_interceptors_ioctl.inc:477:15: warning: empty class ‘ioctl_desc_compare’ parameter passing ABI changes in -fabi-version=12 (GCC 8) [-Wabi]
   InternalSort(&ioctl_table, ioctl_table_size, ioctl_desc_compare());

	Marek
Jason Merrill Nov. 16, 2017, 7:20 p.m. UTC | #31
On Thu, Nov 16, 2017 at 12:41 PM, Marek Polacek <polacek@redhat.com> wrote:
> On Tue, Nov 14, 2017 at 07:34:54AM +0100, Richard Biener wrote:
>> On November 14, 2017 6:21:41 AM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
>> >On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek <polacek@redhat.com>
>> >> In the end I did two bootstraps with the patch, but modifed one of
>> >them
>> >> to always return false for ix86_is_empty_record.  Then I compared all
>> >the
>> >> *.o in both dirs.  The result is attached.  Then I looked at
>> >DW_AT_producer
>> >> for all these .o that differ; all of them are C++.  Is this enough to
>> >> clear our concerns?
>> >
>> >Hmm, a bunch of these are right at the beginning, bytes 41 and 65, in
>> >the header.
>> >
>> >Did you build them in the different trunk/trunk2 directories?  I think
>> >Jakub was suggesting building them in the same directory.
>> >> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and
>> >didn't
>> >> see any warnings.
>> >
>> >If there's a codegen change, there ought to be a warning to go along
>> >with it.
>>
>> The question was of course also for unintended changes but yes (I was mainly concerned by libstdc++ ABI changes).
>
> Ok, I did two bootstraps in the same dir, one with ix86_is_empty_record always
> returning false.  There were a few object files that differ in their assembly
> between those two bootstraps.  Previously I didn't see any warnings because
> I hadn't thought of -Wsystem-headers.  Also, we intentionally don't warn if
> the empty parameter is the last one:
>
> +      bool seen_empty_type = false;
> +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
> +       {
> +         if (VOID_TYPE_P (argtype))
> +           break;
> +         if (TYPE_EMPTY_P (argtype))
> +           seen_empty_type = true;
> +         else if (seen_empty_type)
> +           {
> +             cum->warn_empty = true;
> +             break;
> +           }
> +       }
>
> After enabling -Wsystem-headers and tweaking the code above so that we warn
> even if the empty parameter is trailing I can see the warnings that correspond
> to the assembly changes.  Below is a summary of what I found.  TL;DR: I don't
> see any unintended changes.

Looks good to me.

Jason
Marek Polacek Nov. 20, 2017, 3:51 p.m. UTC | #32
On Thu, Nov 16, 2017 at 02:20:59PM -0500, Jason Merrill wrote:
> On Thu, Nov 16, 2017 at 12:41 PM, Marek Polacek <polacek@redhat.com> wrote:
> > On Tue, Nov 14, 2017 at 07:34:54AM +0100, Richard Biener wrote:
> >> On November 14, 2017 6:21:41 AM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
> >> >On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek <polacek@redhat.com>
> >> >> In the end I did two bootstraps with the patch, but modifed one of
> >> >them
> >> >> to always return false for ix86_is_empty_record.  Then I compared all
> >> >the
> >> >> *.o in both dirs.  The result is attached.  Then I looked at
> >> >DW_AT_producer
> >> >> for all these .o that differ; all of them are C++.  Is this enough to
> >> >> clear our concerns?
> >> >
> >> >Hmm, a bunch of these are right at the beginning, bytes 41 and 65, in
> >> >the header.
> >> >
> >> >Did you build them in the different trunk/trunk2 directories?  I think
> >> >Jakub was suggesting building them in the same directory.
> >> >> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and
> >> >didn't
> >> >> see any warnings.
> >> >
> >> >If there's a codegen change, there ought to be a warning to go along
> >> >with it.
> >>
> >> The question was of course also for unintended changes but yes (I was mainly concerned by libstdc++ ABI changes).
> >
> > Ok, I did two bootstraps in the same dir, one with ix86_is_empty_record always
> > returning false.  There were a few object files that differ in their assembly
> > between those two bootstraps.  Previously I didn't see any warnings because
> > I hadn't thought of -Wsystem-headers.  Also, we intentionally don't warn if
> > the empty parameter is the last one:
> >
> > +      bool seen_empty_type = false;
> > +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
> > +       {
> > +         if (VOID_TYPE_P (argtype))
> > +           break;
> > +         if (TYPE_EMPTY_P (argtype))
> > +           seen_empty_type = true;
> > +         else if (seen_empty_type)
> > +           {
> > +             cum->warn_empty = true;
> > +             break;
> > +           }
> > +       }
> >
> > After enabling -Wsystem-headers and tweaking the code above so that we warn
> > even if the empty parameter is trailing I can see the warnings that correspond
> > to the assembly changes.  Below is a summary of what I found.  TL;DR: I don't
> > see any unintended changes.
> 
> Looks good to me.

Thanks!

Richi, are you ok with the patch now?
Honza/Uros, are the config/i386/* changes ok?

The last version of the patch is
https://gcc.gnu.org/ml/gcc-patches/2017-11/msg00969.html

	Marek
Richard Biener Nov. 20, 2017, 5:08 p.m. UTC | #33
On November 20, 2017 4:51:04 PM GMT+01:00, Marek Polacek <polacek@redhat.com> wrote:
>On Thu, Nov 16, 2017 at 02:20:59PM -0500, Jason Merrill wrote:
>> On Thu, Nov 16, 2017 at 12:41 PM, Marek Polacek <polacek@redhat.com>
>wrote:
>> > On Tue, Nov 14, 2017 at 07:34:54AM +0100, Richard Biener wrote:
>> >> On November 14, 2017 6:21:41 AM GMT+01:00, Jason Merrill
><jason@redhat.com> wrote:
>> >> >On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek
><polacek@redhat.com>
>> >> >> In the end I did two bootstraps with the patch, but modifed one
>of
>> >> >them
>> >> >> to always return false for ix86_is_empty_record.  Then I
>compared all
>> >> >the
>> >> >> *.o in both dirs.  The result is attached.  Then I looked at
>> >> >DW_AT_producer
>> >> >> for all these .o that differ; all of them are C++.  Is this
>enough to
>> >> >> clear our concerns?
>> >> >
>> >> >Hmm, a bunch of these are right at the beginning, bytes 41 and
>65, in
>> >> >the header.
>> >> >
>> >> >Did you build them in the different trunk/trunk2 directories?  I
>think
>> >> >Jakub was suggesting building them in the same directory.
>> >> >> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11,
>and
>> >> >didn't
>> >> >> see any warnings.
>> >> >
>> >> >If there's a codegen change, there ought to be a warning to go
>along
>> >> >with it.
>> >>
>> >> The question was of course also for unintended changes but yes (I
>was mainly concerned by libstdc++ ABI changes).
>> >
>> > Ok, I did two bootstraps in the same dir, one with
>ix86_is_empty_record always
>> > returning false.  There were a few object files that differ in
>their assembly
>> > between those two bootstraps.  Previously I didn't see any warnings
>because
>> > I hadn't thought of -Wsystem-headers.  Also, we intentionally don't
>warn if
>> > the empty parameter is the last one:
>> >
>> > +      bool seen_empty_type = false;
>> > +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
>> > +       {
>> > +         if (VOID_TYPE_P (argtype))
>> > +           break;
>> > +         if (TYPE_EMPTY_P (argtype))
>> > +           seen_empty_type = true;
>> > +         else if (seen_empty_type)
>> > +           {
>> > +             cum->warn_empty = true;
>> > +             break;
>> > +           }
>> > +       }
>> >
>> > After enabling -Wsystem-headers and tweaking the code above so that
>we warn
>> > even if the empty parameter is trailing I can see the warnings that
>correspond
>> > to the assembly changes.  Below is a summary of what I found. 
>TL;DR: I don't
>> > see any unintended changes.
>> 
>> Looks good to me.
>
>Thanks!
>
>Richi, are you ok with the patch now?

Yes. 

Richard. 

>Honza/Uros, are the config/i386/* changes ok?
>
>The last version of the patch is
>https://gcc.gnu.org/ml/gcc-patches/2017-11/msg00969.html
>
>	Marek
Uros Bizjak Nov. 21, 2017, 9:02 a.m. UTC | #34
On Mon, Nov 20, 2017 at 4:51 PM, Marek Polacek <polacek@redhat.com> wrote:
> On Thu, Nov 16, 2017 at 02:20:59PM -0500, Jason Merrill wrote:
>> On Thu, Nov 16, 2017 at 12:41 PM, Marek Polacek <polacek@redhat.com> wrote:
>> > On Tue, Nov 14, 2017 at 07:34:54AM +0100, Richard Biener wrote:
>> >> On November 14, 2017 6:21:41 AM GMT+01:00, Jason Merrill <jason@redhat.com> wrote:
>> >> >On Mon, Nov 13, 2017 at 1:02 PM, Marek Polacek <polacek@redhat.com>
>> >> >> In the end I did two bootstraps with the patch, but modifed one of
>> >> >them
>> >> >> to always return false for ix86_is_empty_record.  Then I compared all
>> >> >the
>> >> >> *.o in both dirs.  The result is attached.  Then I looked at
>> >> >DW_AT_producer
>> >> >> for all these .o that differ; all of them are C++.  Is this enough to
>> >> >> clear our concerns?
>> >> >
>> >> >Hmm, a bunch of these are right at the beginning, bytes 41 and 65, in
>> >> >the header.
>> >> >
>> >> >Did you build them in the different trunk/trunk2 directories?  I think
>> >> >Jakub was suggesting building them in the same directory.
>> >> >> And I also ran a bootstrap with --enable-cxx-flags=-Wabi=11, and
>> >> >didn't
>> >> >> see any warnings.
>> >> >
>> >> >If there's a codegen change, there ought to be a warning to go along
>> >> >with it.
>> >>
>> >> The question was of course also for unintended changes but yes (I was mainly concerned by libstdc++ ABI changes).
>> >
>> > Ok, I did two bootstraps in the same dir, one with ix86_is_empty_record always
>> > returning false.  There were a few object files that differ in their assembly
>> > between those two bootstraps.  Previously I didn't see any warnings because
>> > I hadn't thought of -Wsystem-headers.  Also, we intentionally don't warn if
>> > the empty parameter is the last one:
>> >
>> > +      bool seen_empty_type = false;
>> > +      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
>> > +       {
>> > +         if (VOID_TYPE_P (argtype))
>> > +           break;
>> > +         if (TYPE_EMPTY_P (argtype))
>> > +           seen_empty_type = true;
>> > +         else if (seen_empty_type)
>> > +           {
>> > +             cum->warn_empty = true;
>> > +             break;
>> > +           }
>> > +       }
>> >
>> > After enabling -Wsystem-headers and tweaking the code above so that we warn
>> > even if the empty parameter is trailing I can see the warnings that correspond
>> > to the assembly changes.  Below is a summary of what I found.  TL;DR: I don't
>> > see any unintended changes.
>>
>> Looks good to me.
>
> Thanks!
>
> Richi, are you ok with the patch now?
> Honza/Uros, are the config/i386/* changes ok?
>
> The last version of the patch is
> https://gcc.gnu.org/ml/gcc-patches/2017-11/msg00969.html

LGTM for x86 part.

Thanks,
Uros.
Marek Polacek Nov. 21, 2017, 1:57 p.m. UTC | #35
On Tue, Nov 21, 2017 at 10:02:34AM +0100, Uros Bizjak wrote:
> LGTM for x86 part.

Thanks a lot.  I'll commit the patch later today.

	Marek
Jakub Jelinek Nov. 22, 2017, 9:07 p.m. UTC | #36
On Mon, Nov 13, 2017 at 11:32:14AM +0100, Marek Polacek wrote:
> 	* g++.dg/abi/pr68355.C: New test.

This testcase FAILs on i686-linux, my understanding is that the ABI
changed only if TARGET_64BIT on x86_64/i686-linux.

The following patch fixes it for me, ok for trunk?

2017-11-22  Jakub Jelinek  <jakub@redhat.com>

	* g++.dg/abi/pr68355.C: Don't expect tail call on ia32.

--- gcc/testsuite/g++.dg/abi/pr68355.C.jj	2017-11-22 21:37:44.000000000 +0100
+++ gcc/testsuite/g++.dg/abi/pr68355.C	2017-11-22 21:44:13.220962389 +0100
@@ -21,4 +21,4 @@ yyy (void)
   xxx (y);
 }
 
-// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target { { i?86-*-* x86_64-*-* } && { ! ia32 } } } } }


	Jakub
Marek Polacek Nov. 22, 2017, 9:25 p.m. UTC | #37
On Wed, Nov 22, 2017 at 10:07:04PM +0100, Jakub Jelinek wrote:
> On Mon, Nov 13, 2017 at 11:32:14AM +0100, Marek Polacek wrote:
> > 	* g++.dg/abi/pr68355.C: New test.
> 
> This testcase FAILs on i686-linux, my understanding is that the ABI
> changed only if TARGET_64BIT on x86_64/i686-linux.
 
Correct.

> The following patch fixes it for me, ok for trunk?

Ok.  I thought I had adjusted all the tests, but apparently I forgot to
run this one with -m32.

Thanks!

> 2017-11-22  Jakub Jelinek  <jakub@redhat.com>
> 
> 	* g++.dg/abi/pr68355.C: Don't expect tail call on ia32.
> 
> --- gcc/testsuite/g++.dg/abi/pr68355.C.jj	2017-11-22 21:37:44.000000000 +0100
> +++ gcc/testsuite/g++.dg/abi/pr68355.C	2017-11-22 21:44:13.220962389 +0100
> @@ -21,4 +21,4 @@ yyy (void)
>    xxx (y);
>  }
>  
> -// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
> +// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target { { i?86-*-* x86_64-*-* } && { ! ia32 } } } } }

	Marek
diff mbox series

Patch

diff --git gcc/calls.c gcc/calls.c
index 3730f43c7a9..29f547942d8 100644
--- gcc/calls.c
+++ gcc/calls.c
@@ -1850,6 +1850,8 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       args[i].unsignedp = unsignedp;
       args[i].mode = mode;
 
+      targetm.calls.warn_parameter_passing_abi (args_so_far, type);
+
       args[i].reg = targetm.calls.function_arg (args_so_far, mode, type,
 						argpos < n_named_args);
 
@@ -5358,7 +5360,11 @@  store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	 Note that in C the default argument promotions
 	 will prevent such mismatches.  */
 
-      size = GET_MODE_SIZE (arg->mode);
+      if (targetm.calls.empty_record_p (TREE_TYPE (pval)))
+	size = 0;
+      else
+	size = GET_MODE_SIZE (arg->mode);
+
       /* Compute how much space the push instruction will push.
 	 On many machines, pushing a byte will advance the stack
 	 pointer by a halfword.  */
@@ -5390,10 +5396,12 @@  store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 
       /* This isn't already where we want it on the stack, so put it there.
 	 This can either be done with push or copy insns.  */
-      if (!emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), NULL_RTX,
-		      parm_align, partial, reg, used - size, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
+      if (used
+	  && !emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval),
+			      NULL_RTX, parm_align, partial, reg, used - size,
+			      argblock, ARGS_SIZE_RTX (arg->locate.offset),
+			      reg_parm_stack_space,
+			      ARGS_SIZE_RTX (arg->locate.alignment_pad), true))
 	sibcall_failure = 1;
 
       /* Unless this is a partially-in-register argument, the argument is now
@@ -5426,9 +5434,9 @@  store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	  /* PUSH_ROUNDING has no effect on us, because emit_push_insn
 	     for BLKmode is careful to avoid it.  */
 	  excess = (arg->locate.size.constant
-		    - int_size_in_bytes (TREE_TYPE (pval))
+		    - int_maybe_empty_type_size (TREE_TYPE (pval))
 		    + partial);
-	  size_rtx = expand_expr (size_in_bytes (TREE_TYPE (pval)),
+	  size_rtx = expand_expr (maybe_empty_type_size (TREE_TYPE (pval)),
 				  NULL_RTX, TYPE_MODE (sizetype),
 				  EXPAND_NORMAL);
 	}
@@ -5504,10 +5512,12 @@  store_one_arg (struct arg_data *arg, rtx argblock, int flags,
 	    }
 	}
 
-      emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
-		      parm_align, partial, reg, excess, argblock,
-		      ARGS_SIZE_RTX (arg->locate.offset), reg_parm_stack_space,
-		      ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
+      if (!CONST_INT_P (size_rtx) || INTVAL (size_rtx) != 0)
+	emit_push_insn (arg->value, arg->mode, TREE_TYPE (pval), size_rtx,
+			parm_align, partial, reg, excess, argblock,
+			ARGS_SIZE_RTX (arg->locate.offset),
+			reg_parm_stack_space,
+			ARGS_SIZE_RTX (arg->locate.alignment_pad), false);
 
       /* Unless this is a partially-in-register argument, the argument is now
 	 in the stack.
@@ -5585,6 +5595,9 @@  must_pass_in_stack_var_size_or_pad (machine_mode mode, const_tree type)
   if (TREE_ADDRESSABLE (type))
     return true;
 
+  if (targetm.calls.empty_record_p (type))
+    return false;
+
   /* If the padding and mode of the type is such that a copy into
      a register would put it into the wrong part of the register.  */
   if (mode == BLKmode
diff --git gcc/common.opt gcc/common.opt
index 836f05b95a2..41d97ae7475 100644
--- gcc/common.opt
+++ gcc/common.opt
@@ -932,7 +932,7 @@  Driver Undocumented
 ;     Default in G++ 7.
 ;
 ; 12: Corrects the calling convention for classes with only deleted copy/move
-;     constructors.
+;     constructors and changes passing/returning of empty records.
 ;     Default in G++ 8.
 ;
 ; Additional positive integers will be assigned as new versions of
diff --git gcc/config/i386/i386.c gcc/config/i386/i386.c
index 1facf121987..c7058ac5638 100644
--- gcc/config/i386/i386.c
+++ gcc/config/i386/i386.c
@@ -7186,6 +7186,26 @@  init_cumulative_args (CUMULATIVE_ARGS *cum,  /* Argument info to initialize */
   cum->force_bnd_pass = 0;
   cum->decl = fndecl;
 
+  cum->warn_empty = !warn_abi || cum->stdarg;
+  if (!cum->warn_empty && fntype)
+    {
+      function_args_iterator iter;
+      tree argtype;
+      bool seen_empty_type = false;
+      FOREACH_FUNCTION_ARGS (fntype, argtype, iter)
+	{
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  if (targetm.calls.empty_record_p (argtype))
+	    seen_empty_type = true;
+	  else if (seen_empty_type)
+	    {
+	      cum->warn_empty = true;
+	      break;
+	    }
+	}
+    }
+
   if (!TARGET_64BIT)
     {
       /* If there are variable arguments, then we won't pass anything
@@ -8327,6 +8347,10 @@  ix86_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
   if (!cum->caller && cfun->machine->func_type != TYPE_NORMAL)
     return;
 
+  /* Skip empty records because they won't be passed.  */
+  if (type && targetm.calls.empty_record_p (type))
+    return;
+
   if (mode == BLKmode)
     bytes = int_size_in_bytes (type);
   else
@@ -9293,6 +9317,10 @@  ix86_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
   if (POINTER_BOUNDS_TYPE_P (type))
     return false;
 
+  /* Empty records are never passed in memory.  */
+  if (type && targetm.calls.empty_record_p (type))
+    return false;
+
   if (TARGET_64BIT)
     {
       if (ix86_function_type_abi (fntype) == MS_ABI)
@@ -9873,7 +9901,7 @@  ix86_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
   indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, false);
   if (indirect_p)
     type = build_pointer_type (type);
-  size = int_size_in_bytes (type);
+  size = int_maybe_empty_type_size (type);
   rsize = CEIL (size, UNITS_PER_WORD);
 
   nat_mode = type_natural_mode (type, NULL, false);
@@ -28772,6 +28800,44 @@  ix86_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Implement TARGET_EMPTY_RECORD_P.  */
+
+static bool
+ix86_is_empty_record_p (const_tree type)
+{
+  if (!TARGET_64BIT)
+    return false;
+  return is_empty_record_p (type);
+}
+
+/* Implement TARGET_WARN_PARAMETER_PASSING_ABI.  */
+
+static void
+ix86_warn_parameter_passing_abi (cumulative_args_t cum_v, tree type)
+{
+  CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
+
+  if (!cum->warn_empty)
+    return;
+
+  if (!targetm.calls.empty_record_p (type))
+    return;
+
+  if (!lang_hooks.warn_empty_class_abi ())
+    return;
+
+  /* If the actual size of the type is zero, then there is no change
+     in how objects of this size are passed.  */
+  if (int_size_in_bytes (type) == 0)
+    return;
+
+  warning (OPT_Wabi, "empty class %qT parameter passing ABI "
+	   "changes in -fabi-version=12 (GCC 8)", type);
+
+  /* Only warn once.  */
+  cum->warn_empty = false;
+}
+
 /* Compute the alignment for a variable for Intel MCU psABI.  TYPE is
    the data type, and ALIGN is the alignment that the object would
    ordinarily have.  */
@@ -50311,6 +50377,12 @@  ix86_run_selftests (void)
 #undef TARGET_CONSTANT_ALIGNMENT
 #define TARGET_CONSTANT_ALIGNMENT ix86_constant_alignment
 
+#undef TARGET_EMPTY_RECORD_P
+#define TARGET_EMPTY_RECORD_P ix86_is_empty_record_p
+
+#undef TARGET_WARN_PARAMETER_PASSING_ABI
+#define TARGET_WARN_PARAMETER_PASSING_ABI ix86_warn_parameter_passing_abi
+
 #if CHECKING_P
 #undef TARGET_RUN_TARGET_SELFTESTS
 #define TARGET_RUN_TARGET_SELFTESTS selftest::ix86_run_selftests
diff --git gcc/config/i386/i386.h gcc/config/i386/i386.h
index 837906b5169..7f5d245b568 100644
--- gcc/config/i386/i386.h
+++ gcc/config/i386/i386.h
@@ -1633,6 +1633,8 @@  typedef struct ix86_args {
   int warn_avx;			/* True when we want to warn about AVX ABI.  */
   int warn_sse;			/* True when we want to warn about SSE ABI.  */
   int warn_mmx;			/* True when we want to warn about MMX ABI.  */
+  int warn_empty;		/* True when we want to warn about empty classes
+				   passing ABI change.  */
   int sse_regno;		/* next available sse register number */
   int mmx_words;		/* # mmx words passed so far */
   int mmx_nregs;		/* # mmx registers available for passing */
diff --git gcc/cp/cp-lang.c gcc/cp/cp-lang.c
index 805319a4185..78336730a94 100644
--- gcc/cp/cp-lang.c
+++ gcc/cp/cp-lang.c
@@ -26,6 +26,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "langhooks.h"
 #include "langhooks-def.h"
 #include "cp-objcp-common.h"
+#include "flags.h"
 
 enum c_language_kind c_language = clk_cxx;
 static void cp_init_ts (void);
@@ -35,6 +36,7 @@  static tree cp_eh_personality (void);
 static tree get_template_innermost_arguments_folded (const_tree);
 static tree get_template_argument_pack_elems_folded (const_tree);
 static tree cxx_enum_underlying_base_type (const_tree);
+static bool cxx_warn_empty_class_abi (void);
 
 /* Lang hooks common to C++ and ObjC++ are declared in cp/cp-objcp-common.h;
    consequently, there should be very few hooks below.  */
@@ -78,6 +80,8 @@  static tree cxx_enum_underlying_base_type (const_tree);
 #define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type
 #undef LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE
 #define LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE cxx_enum_underlying_base_type
+#undef LANG_HOOKS_WARN_EMPTY_CLASS_ABI
+#define LANG_HOOKS_WARN_EMPTY_CLASS_ABI cxx_warn_empty_class_abi
 
 #if CHECKING_P
 #undef LANG_HOOKS_RUN_LANG_SELFTESTS
@@ -234,6 +238,15 @@  tree cxx_enum_underlying_base_type (const_tree type)
   return underlying_type;
 }
 
+/* Return true if we should warn about the change in empty class parameter
+   passing ABI.  */
+
+static bool
+cxx_warn_empty_class_abi (void)
+{
+  return warn_abi && abi_version_crosses (12);
+}
+
 #if CHECKING_P
 
 namespace selftest {
diff --git gcc/cp/cp-tree.h gcc/cp/cp-tree.h
index f2570b00386..45f5d82be03 100644
--- gcc/cp/cp-tree.h
+++ gcc/cp/cp-tree.h
@@ -6916,7 +6916,6 @@  extern tree canonical_eh_spec			(tree);
 extern tree build_exception_variant		(tree, tree);
 extern tree bind_template_template_parm		(tree, tree);
 extern tree array_type_nelts_total		(tree);
-extern tree array_type_nelts_top		(tree);
 extern tree break_out_target_exprs		(tree);
 extern tree build_ctor_subob_ref		(tree, tree, tree);
 extern tree replace_placeholders		(tree, tree, bool * = NULL);
diff --git gcc/cp/tree.c gcc/cp/tree.c
index 48d40945af3..8a878d70e34 100644
--- gcc/cp/tree.c
+++ gcc/cp/tree.c
@@ -2834,19 +2834,6 @@  cxx_print_statistics (void)
 }
 
 /* Return, as an INTEGER_CST node, the number of elements for TYPE
-   (which is an ARRAY_TYPE).  This counts only elements of the top
-   array.  */
-
-tree
-array_type_nelts_top (tree type)
-{
-  return fold_build2_loc (input_location,
-		      PLUS_EXPR, sizetype,
-		      array_type_nelts (type),
-		      size_one_node);
-}
-
-/* Return, as an INTEGER_CST node, the number of elements for TYPE
    (which is an ARRAY_TYPE).  This one is a recursive count of all
    ARRAY_TYPEs that are clumped together.  */
 
diff --git gcc/doc/tm.texi gcc/doc/tm.texi
index c02f4d35116..9c4405a4cb5 100644
--- gcc/doc/tm.texi
+++ gcc/doc/tm.texi
@@ -4548,6 +4548,16 @@  This target hook returns the mode to be used when accessing raw return registers
 This target hook returns the mode to be used when accessing raw argument registers in @code{__builtin_apply_args}.  Define this macro if the value in @var{reg_raw_mode} is not correct.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_EMPTY_RECORD_P (const_tree @var{type})
+This target hook returns true if the type is an empty record.  The default
+is to return @code{false}.
+@end deftypefn
+
+@deftypefn {Target Hook} void TARGET_WARN_PARAMETER_PASSING_ABI (cumulative_args_t @var{ca}, tree @var{type})
+This target hook warns about the change in empty class parameter passing
+ABI.
+@end deftypefn
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/doc/tm.texi.in gcc/doc/tm.texi.in
index 37308e1e551..8b6b64f542f 100644
--- gcc/doc/tm.texi.in
+++ gcc/doc/tm.texi.in
@@ -3437,6 +3437,10 @@  nothing when you use @option{-freg-struct-return} mode.
 
 @hook TARGET_GET_RAW_ARG_MODE
 
+@hook TARGET_EMPTY_RECORD_P
+
+@hook TARGET_WARN_PARAMETER_PASSING_ABI
+
 @node Caller Saves
 @subsection Caller-Saves Register Allocation
 
diff --git gcc/explow.c gcc/explow.c
index 662865d2808..5c864fedd1b 100644
--- gcc/explow.c
+++ gcc/explow.c
@@ -2166,7 +2166,7 @@  hard_function_value (const_tree valtype, const_tree func, const_tree fntype,
   if (REG_P (val)
       && GET_MODE (val) == BLKmode)
     {
-      unsigned HOST_WIDE_INT bytes = int_size_in_bytes (valtype);
+      unsigned HOST_WIDE_INT bytes = int_maybe_empty_type_size (valtype);
       opt_scalar_int_mode tmpmode;
 
       /* int_size_in_bytes can return -1.  We don't need a check here
diff --git gcc/expr.c gcc/expr.c
index 496d492c9fa..337bd6de579 100644
--- gcc/expr.c
+++ gcc/expr.c
@@ -2746,7 +2746,7 @@  copy_blkmode_to_reg (machine_mode mode, tree src)
 
   x = expand_normal (src);
 
-  bytes = int_size_in_bytes (TREE_TYPE (src));
+  bytes = int_maybe_empty_type_size (TREE_TYPE (src));
   if (bytes == 0)
     return NULL_RTX;
 
diff --git gcc/function.c gcc/function.c
index 339419ee1da..b7f97bf0ae5 100644
--- gcc/function.c
+++ gcc/function.c
@@ -2528,6 +2528,9 @@  assign_parm_find_entry_rtl (struct assign_parm_data_all *all,
       return;
     }
 
+  targetm.calls.warn_parameter_passing_abi (all->args_so_far,
+					    data->passed_type);
+
   entry_parm = targetm.calls.function_incoming_arg (all->args_so_far,
 						    data->promoted_mode,
 						    data->passed_type,
@@ -4140,8 +4143,9 @@  locate_and_pad_parm (machine_mode passed_mode, tree type, int in_regs,
 
   part_size_in_regs = (reg_parm_stack_space == 0 ? partial : 0);
 
-  sizetree
-    = type ? size_in_bytes (type) : size_int (GET_MODE_SIZE (passed_mode));
+  sizetree = (type
+	      ? maybe_empty_type_size (type)
+	      : size_int (GET_MODE_SIZE (passed_mode)));
   where_pad = targetm.calls.function_arg_padding (passed_mode, type);
   boundary = targetm.calls.function_arg_boundary (passed_mode, type);
   round_boundary = targetm.calls.function_arg_round_boundary (passed_mode,
diff --git gcc/langhooks-def.h gcc/langhooks-def.h
index 61b081bd7cc..4cb31a68d3d 100644
--- gcc/langhooks-def.h
+++ gcc/langhooks-def.h
@@ -132,6 +132,7 @@  extern int lhd_type_dwarf_attribute (const_tree, int);
 #define LANG_HOOKS_CUSTOM_FUNCTION_DESCRIPTORS	false
 #define LANG_HOOKS_RUN_LANG_SELFTESTS   lhd_do_nothing
 #define LANG_HOOKS_GET_SUBSTRING_LOCATION lhd_get_substring_location
+#define LANG_HOOKS_WARN_EMPTY_CLASS_ABI hook_bool_void_false
 
 /* Attribute hooks.  */
 #define LANG_HOOKS_ATTRIBUTE_TABLE		NULL
@@ -344,7 +345,8 @@  extern void lhd_end_section (void);
   LANG_HOOKS_DEEP_UNSHARING, \
   LANG_HOOKS_CUSTOM_FUNCTION_DESCRIPTORS, \
   LANG_HOOKS_RUN_LANG_SELFTESTS, \
-  LANG_HOOKS_GET_SUBSTRING_LOCATION \
+  LANG_HOOKS_GET_SUBSTRING_LOCATION, \
+  LANG_HOOKS_WARN_EMPTY_CLASS_ABI \
 }
 
 #endif /* GCC_LANG_HOOKS_DEF_H */
diff --git gcc/langhooks.h gcc/langhooks.h
index d1288f1965d..ed79b2d8626 100644
--- gcc/langhooks.h
+++ gcc/langhooks.h
@@ -538,6 +538,9 @@  struct lang_hooks
   const char *(*get_substring_location) (const substring_loc &,
 					 location_t *out_loc);
 
+  /* Return true when we should warn about the empty class ABI change.  */
+  bool (*warn_empty_class_abi) (void);
+
   /* Whenever you add entries here, make sure you adjust langhooks-def.h
      and langhooks.c accordingly.  */
 };
diff --git gcc/target.def gcc/target.def
index 6a1cd31f2ac..9445a412689 100644
--- gcc/target.def
+++ gcc/target.def
@@ -5043,6 +5043,22 @@  DEFHOOK
  machine_mode, (int regno),
  default_get_reg_raw_mode)
 
+/* Return true if a type is an empty record.  */
+DEFHOOK
+(empty_record_p,
+ "This target hook returns true if the type is an empty record.  The default\n\
+is to return @code{false}.",
+ bool, (const_tree type),
+ hook_bool_const_tree_false)
+
+/* Warn about the change in empty class parameter passing ABI.  */
+DEFHOOK
+(warn_parameter_passing_abi,
+ "This target hook warns about the change in empty class parameter passing\n\
+ABI.",
+ void, (cumulative_args_t ca, tree type),
+ hook_void_CUMULATIVE_ARGS_tree)
+
 HOOK_VECTOR_END (calls)
 
 DEFHOOK
diff --git gcc/targhooks.c gcc/targhooks.c
index 92ecc90d4d4..f117382d565 100644
--- gcc/targhooks.c
+++ gcc/targhooks.c
@@ -734,6 +734,12 @@  hook_int_CUMULATIVE_ARGS_mode_tree_bool_0 (
 }
 
 void
+hook_void_CUMULATIVE_ARGS_tree (cumulative_args_t ca ATTRIBUTE_UNUSED,
+				tree ATTRIBUTE_UNUSED)
+{
+}
+
+void
 default_function_arg_advance (cumulative_args_t ca ATTRIBUTE_UNUSED,
 			      machine_mode mode ATTRIBUTE_UNUSED,
 			      const_tree type ATTRIBUTE_UNUSED,
@@ -2084,6 +2090,7 @@  std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
   /* va_list pointer is aligned to PARM_BOUNDARY.  If argument actually
      requires greater alignment, we must perform dynamic alignment.  */
   if (boundary > align
+      && !targetm.calls.empty_record_p (type)
       && !integer_zerop (TYPE_SIZE (type)))
     {
       t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp,
@@ -2110,7 +2117,7 @@  std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p,
     }
 
   /* Compute the rounded size of the type.  */
-  type_size = size_in_bytes (type);
+  type_size = maybe_empty_type_size (type);
   rounded_size = round_up (type_size, align);
 
   /* Reduce rounded_size so it's sharable with the postqueue.  */
diff --git gcc/targhooks.h gcc/targhooks.h
index f60bca257f7..722608f35cc 100644
--- gcc/targhooks.h
+++ gcc/targhooks.h
@@ -134,6 +134,8 @@  extern bool hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true
   (cumulative_args_t, machine_mode, const_tree, bool);
 extern int hook_int_CUMULATIVE_ARGS_mode_tree_bool_0
   (cumulative_args_t, machine_mode, tree, bool);
+extern void hook_void_CUMULATIVE_ARGS_tree
+  (cumulative_args_t, tree);
 extern const char *hook_invalid_arg_for_unprototyped_fn
   (const_tree, const_tree, const_tree);
 extern void default_function_arg_advance
diff --git gcc/testsuite/g++.dg/abi/empty12.C gcc/testsuite/g++.dg/abi/empty12.C
index e69de29bb2d..20d85ff873e 100644
--- gcc/testsuite/g++.dg/abi/empty12.C
+++ gcc/testsuite/g++.dg/abi/empty12.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty12a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty12.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty12.h gcc/testsuite/g++.dg/abi/empty12.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty12.h
+++ gcc/testsuite/g++.dg/abi/empty12.h
@@ -0,0 +1,9 @@ 
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty12a.c gcc/testsuite/g++.dg/abi/empty12a.c
index e69de29bb2d..34a25bad75d 100644
--- gcc/testsuite/g++.dg/abi/empty12a.c
+++ gcc/testsuite/g++.dg/abi/empty12a.c
@@ -0,0 +1,6 @@ 
+#include "empty12.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.C gcc/testsuite/g++.dg/abi/empty13.C
index e69de29bb2d..0cb9a373e35 100644
--- gcc/testsuite/g++.dg/abi/empty13.C
+++ gcc/testsuite/g++.dg/abi/empty13.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-x c -fabi-version=11" }
+// { dg-additional-sources "empty13a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty13.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty13.h gcc/testsuite/g++.dg/abi/empty13.h
index e69de29bb2d..c61afcda0fb 100644
--- gcc/testsuite/g++.dg/abi/empty13.h
+++ gcc/testsuite/g++.dg/abi/empty13.h
@@ -0,0 +1,9 @@ 
+struct dummy { };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty13a.c gcc/testsuite/g++.dg/abi/empty13a.c
index e69de29bb2d..b4303a63826 100644
--- gcc/testsuite/g++.dg/abi/empty13a.c
+++ gcc/testsuite/g++.dg/abi/empty13a.c
@@ -0,0 +1,6 @@ 
+#include "empty13.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 == -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.C gcc/testsuite/g++.dg/abi/empty14.C
index e69de29bb2d..2868d8ad3f3 100644
--- gcc/testsuite/g++.dg/abi/empty14.C
+++ gcc/testsuite/g++.dg/abi/empty14.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty14a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty14.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty14.h gcc/testsuite/g++.dg/abi/empty14.h
index e69de29bb2d..5842279cf37 100644
--- gcc/testsuite/g++.dg/abi/empty14.h
+++ gcc/testsuite/g++.dg/abi/empty14.h
@@ -0,0 +1,10 @@ 
+struct dummy0 { };
+struct dummy { struct dummy0 d[140]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty14a.c gcc/testsuite/g++.dg/abi/empty14a.c
index e69de29bb2d..8b3d7800c36 100644
--- gcc/testsuite/g++.dg/abi/empty14a.c
+++ gcc/testsuite/g++.dg/abi/empty14a.c
@@ -0,0 +1,6 @@ 
+#include "empty14.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.C gcc/testsuite/g++.dg/abi/empty15.C
index e69de29bb2d..12385f78c78 100644
--- gcc/testsuite/g++.dg/abi/empty15.C
+++ gcc/testsuite/g++.dg/abi/empty15.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty15a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty15.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty15.h gcc/testsuite/g++.dg/abi/empty15.h
index e69de29bb2d..1c6f26f5ae8 100644
--- gcc/testsuite/g++.dg/abi/empty15.h
+++ gcc/testsuite/g++.dg/abi/empty15.h
@@ -0,0 +1,30 @@ 
+struct A1 {};
+struct A2 {};
+struct B1 { struct A1 a; struct A2 b; };
+struct B2 { struct A1 a; struct A2 b; };
+struct C1 { struct B1 a; struct B2 b; };
+struct C2 { struct B1 a; struct B2 b; };
+struct D1 { struct C1 a; struct C2 b; };
+struct D2 { struct C1 a; struct C2 b; };
+struct E1 { struct D1 a; struct D2 b; };
+struct E2 { struct D1 a; struct D2 b; };
+struct F1 { struct E1 a; struct E2 b; };
+struct F2 { struct E1 a; struct E2 b; };
+struct G1 { struct F1 a; struct F2 b; };
+struct G2 { struct F1 a; struct F2 b; };
+struct H1 { struct G1 a; struct G2 b; };
+struct H2 { struct G1 a; struct G2 b; };
+struct I1 { struct H1 a; struct H2 b; };
+struct I2 { struct H1 a; struct H2 b; };
+struct J1 { struct I1 a; struct I2 b; };
+struct J2 { struct I1 a; struct I2 b; };
+struct dummy { struct J1 a; struct J2 b; };
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty15a.c gcc/testsuite/g++.dg/abi/empty15a.c
index e69de29bb2d..325b2c5ba09 100644
--- gcc/testsuite/g++.dg/abi/empty15a.c
+++ gcc/testsuite/g++.dg/abi/empty15a.c
@@ -0,0 +1,6 @@ 
+#include "empty15.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.C gcc/testsuite/g++.dg/abi/empty16.C
index e69de29bb2d..1ca52f9011e 100644
--- gcc/testsuite/g++.dg/abi/empty16.C
+++ gcc/testsuite/g++.dg/abi/empty16.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty16a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty16.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty16.h gcc/testsuite/g++.dg/abi/empty16.h
index e69de29bb2d..7552ae06576 100644
--- gcc/testsuite/g++.dg/abi/empty16.h
+++ gcc/testsuite/g++.dg/abi/empty16.h
@@ -0,0 +1,16 @@ 
+#ifdef __cplusplus
+struct A1 {};
+struct A2 {};
+struct dummy : A1, A2 {} ;
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty16a.c gcc/testsuite/g++.dg/abi/empty16a.c
index e69de29bb2d..6cb7fbccecc 100644
--- gcc/testsuite/g++.dg/abi/empty16a.c
+++ gcc/testsuite/g++.dg/abi/empty16a.c
@@ -0,0 +1,6 @@ 
+#include "empty16.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.C gcc/testsuite/g++.dg/abi/empty17.C
index e69de29bb2d..d386e5481af 100644
--- gcc/testsuite/g++.dg/abi/empty17.C
+++ gcc/testsuite/g++.dg/abi/empty17.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty17a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty17.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty17.h gcc/testsuite/g++.dg/abi/empty17.h
index e69de29bb2d..9cf72baca2e 100644
--- gcc/testsuite/g++.dg/abi/empty17.h
+++ gcc/testsuite/g++.dg/abi/empty17.h
@@ -0,0 +1,27 @@ 
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 15;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 15;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 15;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty17a.c gcc/testsuite/g++.dg/abi/empty17a.c
index e69de29bb2d..24408fde09c 100644
--- gcc/testsuite/g++.dg/abi/empty17a.c
+++ gcc/testsuite/g++.dg/abi/empty17a.c
@@ -0,0 +1,6 @@ 
+#include "empty17.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.C gcc/testsuite/g++.dg/abi/empty18.C
index e69de29bb2d..be69c6a2115 100644
--- gcc/testsuite/g++.dg/abi/empty18.C
+++ gcc/testsuite/g++.dg/abi/empty18.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty18a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty18.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty18.h gcc/testsuite/g++.dg/abi/empty18.h
index e69de29bb2d..86e7ecdd211 100644
--- gcc/testsuite/g++.dg/abi/empty18.h
+++ gcc/testsuite/g++.dg/abi/empty18.h
@@ -0,0 +1,9 @@ 
+struct dummy { int d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty18a.c gcc/testsuite/g++.dg/abi/empty18a.c
index e69de29bb2d..902860bdc01 100644
--- gcc/testsuite/g++.dg/abi/empty18a.c
+++ gcc/testsuite/g++.dg/abi/empty18a.c
@@ -0,0 +1,6 @@ 
+#include "empty18.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.C gcc/testsuite/g++.dg/abi/empty19.C
index e69de29bb2d..84f5b75558b 100644
--- gcc/testsuite/g++.dg/abi/empty19.C
+++ gcc/testsuite/g++.dg/abi/empty19.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty19a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty19.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty19.h gcc/testsuite/g++.dg/abi/empty19.h
index e69de29bb2d..616b87bdd93 100644
--- gcc/testsuite/g++.dg/abi/empty19.h
+++ gcc/testsuite/g++.dg/abi/empty19.h
@@ -0,0 +1,10 @@ 
+struct dummy0 { };
+struct dummy { struct dummy0 d[0]; };
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty19a.c gcc/testsuite/g++.dg/abi/empty19a.c
index e69de29bb2d..767b1eb7320 100644
--- gcc/testsuite/g++.dg/abi/empty19a.c
+++ gcc/testsuite/g++.dg/abi/empty19a.c
@@ -0,0 +1,6 @@ 
+#include "empty19.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty20.C gcc/testsuite/g++.dg/abi/empty20.C
index e69de29bb2d..5022033f669 100644
--- gcc/testsuite/g++.dg/abi/empty20.C
+++ gcc/testsuite/g++.dg/abi/empty20.C
@@ -0,0 +1,19 @@ 
+// PR c++/60336
+// { dg-options "-Wabi=11 -O0" }
+
+struct A { };
+
+void f(A, A) { }	// No warning, trailing parms all empty
+void f(A, A, int) { }	// { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+__attribute__ ((always_inline))
+inline void f(A a, int i) // No warning, always inlined
+{
+  f(a,a,i); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
+int main()
+{
+  A a;
+  f(a,a);
+  f(a,a,42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(a,42);
+}
diff --git gcc/testsuite/g++.dg/abi/empty21.C gcc/testsuite/g++.dg/abi/empty21.C
index e69de29bb2d..3b2e3b836b1 100644
--- gcc/testsuite/g++.dg/abi/empty21.C
+++ gcc/testsuite/g++.dg/abi/empty21.C
@@ -0,0 +1,23 @@ 
+// PR c++/60336
+// { dg-options "-Wabi=11" }
+
+#include <stdarg.h>
+
+struct A { };
+
+void f(int i, ...)
+{
+  va_list ap;
+  va_start (ap, i);
+  if (i >= 1)
+    va_arg (ap, A);
+  if (i >= 2)
+    va_arg (ap, int);
+}
+
+int main()
+{
+  f(0);
+  f(1, A()); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  f(2, A(), 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.C gcc/testsuite/g++.dg/abi/empty22.C
index e69de29bb2d..f4f4a02bf31 100644
--- gcc/testsuite/g++.dg/abi/empty22.C
+++ gcc/testsuite/g++.dg/abi/empty22.C
@@ -0,0 +1,17 @@ 
+// PR c++/60336
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-Wabi=11 -x c" }
+// { dg-additional-sources "empty22a.c" }
+// { dg-prune-output "command line option" }
+
+#include "empty22.h"
+extern "C" void fun(struct dummy, struct foo);
+
+int main()
+{
+  struct dummy d;
+  struct foo f = { -1, -2, -3, -4, -5 };
+
+  fun(d, f); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/empty22.h gcc/testsuite/g++.dg/abi/empty22.h
index e69de29bb2d..8d54dc74519 100644
--- gcc/testsuite/g++.dg/abi/empty22.h
+++ gcc/testsuite/g++.dg/abi/empty22.h
@@ -0,0 +1,27 @@ 
+#ifdef __cplusplus
+struct A1
+{
+  void foo (void);
+  unsigned int : 0;
+};
+struct A2
+{
+  void bar (void);
+  unsigned int : 0;
+};
+struct dummy : A1, A2
+{
+  unsigned int : 0;
+};
+#else
+struct dummy {};
+#endif
+
+struct foo
+{
+  int i1;
+  int i2;
+  int i3;
+  int i4;
+  int i5;
+};
diff --git gcc/testsuite/g++.dg/abi/empty22a.c gcc/testsuite/g++.dg/abi/empty22a.c
index e69de29bb2d..7606c524263 100644
--- gcc/testsuite/g++.dg/abi/empty22a.c
+++ gcc/testsuite/g++.dg/abi/empty22a.c
@@ -0,0 +1,6 @@ 
+#include "empty22.h"
+void fun(struct dummy d, struct foo f)
+{
+  if (f.i1 != -1)
+    __builtin_abort();
+}
diff --git gcc/testsuite/g++.dg/abi/empty23.C gcc/testsuite/g++.dg/abi/empty23.C
index e69de29bb2d..b97d2804529 100644
--- gcc/testsuite/g++.dg/abi/empty23.C
+++ gcc/testsuite/g++.dg/abi/empty23.C
@@ -0,0 +1,25 @@ 
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[0];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S)
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]);
+}
diff --git gcc/testsuite/g++.dg/abi/empty24.C gcc/testsuite/g++.dg/abi/empty24.C
index e69de29bb2d..81deb36ff9f 100644
--- gcc/testsuite/g++.dg/abi/empty24.C
+++ gcc/testsuite/g++.dg/abi/empty24.C
@@ -0,0 +1,25 @@ 
+// PR c++/60336
+// { dg-do run }
+// { dg-options "-Wabi=11" }
+
+struct S
+{
+  struct { } a;
+  __extension__ int b[];
+};
+
+struct S s;
+struct S a[5];
+
+void
+foo (struct S, struct S *arg1, struct S)
+{
+  if (arg1 != &a[1])
+    __builtin_abort ();
+}
+
+int
+main ()
+{
+  foo (s, &a[1], a[2]);
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-1.C gcc/testsuite/g++.dg/abi/pr60336-1.C
index e69de29bb2d..59447890cec 100644
--- gcc/testsuite/g++.dg/abi/pr60336-1.C
+++ gcc/testsuite/g++.dg/abi/pr60336-1.C
@@ -0,0 +1,17 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-10.C gcc/testsuite/g++.dg/abi/pr60336-10.C
index e69de29bb2d..960cc2307d1 100644
--- gcc/testsuite/g++.dg/abi/pr60336-10.C
+++ gcc/testsuite/g++.dg/abi/pr60336-10.C
@@ -0,0 +1,50 @@ 
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0 { };
+struct dummy1 { };
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-11.C gcc/testsuite/g++.dg/abi/pr60336-11.C
index e69de29bb2d..14cd6d0ff3d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-11.C
+++ gcc/testsuite/g++.dg/abi/pr60336-11.C
@@ -0,0 +1,56 @@ 
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+  void bar (void);
+};
+struct dummy1
+{
+  void foo (void);
+};
+struct dummy : dummy0, dummy1 { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-12.C gcc/testsuite/g++.dg/abi/pr60336-12.C
index e69de29bb2d..09917547930 100644
--- gcc/testsuite/g++.dg/abi/pr60336-12.C
+++ gcc/testsuite/g++.dg/abi/pr60336-12.C
@@ -0,0 +1,57 @@ 
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2" }
+
+#include <stdarg.h>
+
+struct dummy0
+{
+};
+struct dummy1
+{
+  unsigned : 15;
+};
+struct dummy : dummy0, dummy1
+{
+};
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-2.C gcc/testsuite/g++.dg/abi/pr60336-2.C
index e69de29bb2d..1c6c3eb8f01 100644
--- gcc/testsuite/g++.dg/abi/pr60336-2.C
+++ gcc/testsuite/g++.dg/abi/pr60336-2.C
@@ -0,0 +1,48 @@ 
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -Wabi=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...) // { dg-warning "empty" }
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count != 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6); // { dg-warning "empty" }
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-3.C gcc/testsuite/g++.dg/abi/pr60336-3.C
index e69de29bb2d..4157e553b6b 100644
--- gcc/testsuite/g++.dg/abi/pr60336-3.C
+++ gcc/testsuite/g++.dg/abi/pr60336-3.C
@@ -0,0 +1,15 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct { } __attribute__((aligned (4))) a[7]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-4.C gcc/testsuite/g++.dg/abi/pr60336-4.C
index e69de29bb2d..266f67a537d 100644
--- gcc/testsuite/g++.dg/abi/pr60336-4.C
+++ gcc/testsuite/g++.dg/abi/pr60336-4.C
@@ -0,0 +1,48 @@ 
+// { dg-do run { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+// { dg-options "-O2 -fabi-version=11" }
+
+#include <stdarg.h>
+
+struct dummy { };
+
+void
+test (struct dummy a, int m, ...)
+{
+  va_list va_arglist;
+  int i;
+  int count = 0;
+
+  if (m == 0)
+    count++;
+  va_start (va_arglist, m);
+  i = va_arg (va_arglist, int);
+  if (i == 1)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 2)
+  i = va_arg (va_arglist, int);
+    count++;
+  if (i == 3)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 4)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 5)
+    count++;
+  i = va_arg (va_arglist, int);
+  if (i == 6)
+    count++;
+  va_end (va_arglist);
+  if (count == 7)
+    __builtin_abort ();
+}
+
+struct dummy a0;
+
+int
+main ()
+{
+  test (a0, 0, 1, 2, 3, 4, 5, 6);
+  return 0;
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-5.C gcc/testsuite/g++.dg/abi/pr60336-5.C
index e69de29bb2d..fe838750f55 100644
--- gcc/testsuite/g++.dg/abi/pr60336-5.C
+++ gcc/testsuite/g++.dg/abi/pr60336-5.C
@@ -0,0 +1,17 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i; struct dummy j; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-6.C gcc/testsuite/g++.dg/abi/pr60336-6.C
index e69de29bb2d..6e08c8f06fa 100644
--- gcc/testsuite/g++.dg/abi/pr60336-6.C
+++ gcc/testsuite/g++.dg/abi/pr60336-6.C
@@ -0,0 +1,17 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i1; struct dummy i2; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-7.C gcc/testsuite/g++.dg/abi/pr60336-7.C
index e69de29bb2d..3b8b8ba6f35 100644
--- gcc/testsuite/g++.dg/abi/pr60336-7.C
+++ gcc/testsuite/g++.dg/abi/pr60336-7.C
@@ -0,0 +1,17 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct dummy { };
+struct true_type { struct dummy i[120]; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr60336-8.C gcc/testsuite/g++.dg/abi/pr60336-8.C
index e69de29bb2d..a1ffb64ef02 100644
--- gcc/testsuite/g++.dg/abi/pr60336-8.C
+++ gcc/testsuite/g++.dg/abi/pr60336-8.C
@@ -0,0 +1,15 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -Wabi=11" }
+
+struct dummy { struct{} a[7][3]; };
+
+extern void test1 (struct dummy, ...);
+extern void (*test2) (struct dummy, ...);
+
+void
+foo ()
+{
+  struct dummy a0;
+  test1 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+  test2 (a0, 42); // { dg-warning "ABI" "" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } }
+}
diff --git gcc/testsuite/g++.dg/abi/pr60336-9.C gcc/testsuite/g++.dg/abi/pr60336-9.C
index e69de29bb2d..393f02b62f0 100644
--- gcc/testsuite/g++.dg/abi/pr60336-9.C
+++ gcc/testsuite/g++.dg/abi/pr60336-9.C
@@ -0,0 +1,28 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+struct A1 {}; struct A2 {};
+struct B1 { A1 a; A2 b; }; struct B2 { A1 a; A2 b; };
+struct C1 { B1 a; B2 b; }; struct C2 { B1 a; B2 b; };
+struct D1 { C1 a; C2 b; }; struct D2 { C1 a; C2 b; };
+struct E1 { D1 a; D2 b; }; struct E2 { D1 a; D2 b; };
+struct F1 { E1 a; E2 b; }; struct F2 { E1 a; E2 b; };
+struct G1 { F1 a; F2 b; }; struct G2 { F1 a; F2 b; };
+struct H1 { G1 a; G2 b; }; struct H2 { G1 a; G2 b; };
+struct I1 { H1 a; H2 b; }; struct I2 { H1 a; H2 b; };
+struct J1 { I1 a; I2 b; }; struct J2 { I1 a; I2 b; };
+struct dummy { J1 a; J2 b; };
+
+struct true_type { struct dummy i; };
+
+extern true_type y;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx9true_type" { target { { i?86-*-* x86_64-*-* } && { ! { ia32 } } } } } }
diff --git gcc/testsuite/g++.dg/abi/pr68355.C gcc/testsuite/g++.dg/abi/pr68355.C
index e69de29bb2d..1354fc497b5 100644
--- gcc/testsuite/g++.dg/abi/pr68355.C
+++ gcc/testsuite/g++.dg/abi/pr68355.C
@@ -0,0 +1,24 @@ 
+// { dg-do compile }
+// { dg-options "-O2 -std=c++11 -fno-pic" }
+// { dg-require-effective-target fpic }
+
+template<typename _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr _Tp value = __v;
+  typedef _Tp value_type;
+  typedef integral_constant<_Tp, __v> type;
+  constexpr operator value_type() const { return value; }
+};
+
+typedef integral_constant<bool, true> true_type;
+extern void xxx (true_type c);
+
+void
+yyy (void)
+{
+  true_type y;
+  xxx (y);
+}
+
+// { dg-final { scan-assembler "jmp\[\t \]+\[^\$\]*?_Z3xxx17integral_constantIbLb1EE" { target i?86-*-* x86_64-*-* } } }
diff --git gcc/tree.c gcc/tree.c
index fa6fcb1da71..a32c238de5d 100644
--- gcc/tree.c
+++ gcc/tree.c
@@ -3135,6 +3135,20 @@  array_type_nelts (const_tree type)
 	  ? max
 	  : fold_build2 (MINUS_EXPR, TREE_TYPE (max), max, min));
 }
+
+/* Return, as an INTEGER_CST node, the number of elements for TYPE
+   (which is an ARRAY_TYPE).  This counts only elements of the top
+   array.  */
+
+tree
+array_type_nelts_top (const_tree type)
+{
+  return fold_build2_loc (input_location,
+			  PLUS_EXPR, sizetype,
+			  array_type_nelts (type),
+			  size_one_node);
+}
+
 
 /* If arg is static -- a reference to an object in static storage -- then
    return the object.  This is not the same as the C meaning of `static'.
@@ -13811,6 +13825,89 @@  get_nonnull_args (const_tree fntype)
   return argmap;
 }
 
+/* Returns true if TYPE is a type where it and all of its subobjects
+   (recursively) are of structure, union, or array type.  */
+
+static bool
+is_empty_type (tree type)
+{
+  if (RECORD_OR_UNION_TYPE_P (type))
+    {
+      for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+	{
+	  if (TREE_CODE (field) == FIELD_DECL)
+	    {
+	      tree ftype = TREE_TYPE (field);
+	      /* Don't consider struct S { struct { } a; int b[0]; };
+		 an empty type.  */
+	      if (TREE_CODE (ftype) == ARRAY_TYPE
+		  && integer_zerop (array_type_nelts_top (ftype)))
+		{
+		  tree t = DECL_CHAIN (field);
+		  bool found = false;
+		  /* See if the zero-length array is followed by another
+		     FIELD_DECL.  */
+		  while (t)
+		    {
+		      if (TREE_CODE (t) == FIELD_DECL)
+			{
+			  found = true;
+			  break;
+			}
+		      t = DECL_CHAIN (t);
+		    }
+		  if (!found)
+		    return false;
+		}
+	      if ((DECL_NAME (field) || RECORD_OR_UNION_TYPE_P (ftype))
+		  && !is_empty_type (ftype))
+		return false;
+	    }
+	}
+      return true;
+    }
+  else if (TREE_CODE (type) == ARRAY_TYPE)
+    return (integer_zerop (array_type_nelts_top (type))
+	    || is_empty_type (TREE_TYPE (type)));
+  return false;
+}
+
+/* Implement TARGET_EMPTY_RECORD_P.  Return true if TYPE is an empty type
+   that shouldn't be passed via stack.  */
+
+bool
+is_empty_record_p (const_tree type)
+{
+  if (!abi_version_at_least (12))
+    return false;
+
+  if (type == error_mark_node)
+    return false;
+
+  if (TREE_ADDRESSABLE (type))
+    return false;
+
+  return is_empty_type (TYPE_MAIN_VARIANT (type));
+}
+
+/* Like int_size_in_bytes, but handle empty records specially.  */
+
+HOST_WIDE_INT
+int_maybe_empty_type_size (const_tree type)
+{
+  return (targetm.calls.empty_record_p (type)
+	  ? 0 : int_size_in_bytes (type));
+}
+
+/* Like size_in_bytes, but handle empty records specially.  */
+
+tree
+maybe_empty_type_size (const_tree type)
+{
+  return (targetm.calls.empty_record_p (type)
+	  ? size_zero_node : size_in_bytes (type));
+}
+
 /* List of pointer types used to declare builtins before we have seen their
    real declaration.
 
diff --git gcc/tree.h gcc/tree.h
index 7214ae2275c..fb8af8bac7d 100644
--- gcc/tree.h
+++ gcc/tree.h
@@ -4099,6 +4099,7 @@  extern tree build_method_type (tree, tree);
 extern tree build_offset_type (tree, tree);
 extern tree build_complex_type (tree, bool named = false);
 extern tree array_type_nelts (const_tree);
+extern tree array_type_nelts_top (const_tree);
 
 extern tree value_member (tree, tree);
 extern tree purpose_member (const_tree, tree);
@@ -5434,6 +5435,9 @@  extern void gt_pch_nx (tree &, gt_pointer_operator, void *);
 
 extern bool nonnull_arg_p (const_tree);
 extern bool is_redundant_typedef (const_tree);
+extern bool is_empty_record_p (const_tree);
+extern HOST_WIDE_INT int_maybe_empty_type_size (const_tree);
+extern tree maybe_empty_type_size (const_tree);
 
 extern location_t
 set_source_range (tree expr, location_t start, location_t finish);