This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

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


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
> 
> 

-- 
Richard Biener <rguenther@suse.de>
SUSE LINUX GmbH, GF: Felix Imendoerffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nuernberg)


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]