[PATCH][middle-end][i386][version 5]Add -fzero-call-used-regs=[skip|used-gpr-arg|used-arg|all-gpr-arg|all-arg|used-gpr|all-gpr|used|all]

Richard Sandiford richard.sandiford@arm.com
Thu Oct 29 11:09:53 GMT 2020


Qing Zhao via Gcc-patches <gcc-patches@gcc.gnu.org> writes:
> +/* Handle a "zero_call_used_regs" attribute; arguments as in
> +   struct attribute_spec.handler.  */
> +
> +static tree
> +handle_zero_call_used_regs_attribute (tree *node, tree name, tree args,
> +				      int ARG_UNUSED (flags),
> +				      bool *no_add_attrs)
> +{
> +  tree decl = *node;
> +  tree id = TREE_VALUE (args);
> +
> +  if (TREE_CODE (decl) != FUNCTION_DECL)
> +    {
> +      error_at (DECL_SOURCE_LOCATION (decl),
> +		"%qE attribute applies only to functions", name);
> +      *no_add_attrs = true;
> +    }
> +
> +  if (TREE_CODE (id) != STRING_CST)
> +    {
> +      error_at (DECL_SOURCE_LOCATION (decl),
> +		"attribute %qE arguments not a string", name);

The existing message for this seems to be:

  "%qE argument not a string"

(which seems a bit terse, but hey)

> +      *no_add_attrs = true;
> +    }
> +
> +  bool found = false;
> +  for (unsigned int i = 0; zero_call_used_regs_opts[i].name != NULL; ++i)
> +    if (strcmp (TREE_STRING_POINTER (id),
> +		zero_call_used_regs_opts[i].name) == 0)
> +      {
> +	found = true;
> +	break;
> +      }
> +
> +  if (!found)
> +    {
> +      error_at (DECL_SOURCE_LOCATION (decl),
> +		"unrecognized zero_call_used_regs attribute: %qs",
> +		TREE_STRING_POINTER (id));

The attribute name needs to be quoted, and it would be good if it
wasn't hard-coded into the string:

      error_at (DECL_SOURCE_LOCATION (decl),
		"unrecognized %qE attribute argument %qs", name,
		TREE_STRING_POINTER (id));

> @@ -228,6 +228,10 @@ unsigned int flag_sanitize_coverage
>  Variable
>  bool dump_base_name_prefixed = false
>  
> +; What subset of registers should be zeroed

Think it would be useful to add “ on function return.”.

> +Variable
> +unsigned int flag_zero_call_used_regs
> +
>  ###
>  Driver
>  
> diff --git a/gcc/df.h b/gcc/df.h
> index 8b6ca8c..0f098d7 100644
> --- a/gcc/df.h
> +++ b/gcc/df.h
> @@ -1085,6 +1085,7 @@ extern void df_update_entry_exit_and_calls (void);
>  extern bool df_hard_reg_used_p (unsigned int);
>  extern unsigned int df_hard_reg_used_count (unsigned int);
>  extern bool df_regs_ever_live_p (unsigned int);
> +extern bool df_epilogue_uses_p (unsigned int);
>  extern void df_set_regs_ever_live (unsigned int, bool);
>  extern void df_compute_regs_ever_live (bool);
>  extern void df_scan_verify (void);
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index c9f7299..b011c17 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -3992,6 +3992,96 @@ performing a link with relocatable output (i.e.@: @code{ld -r}) on them.
>  A declaration to which @code{weakref} is attached and that is associated
>  with a named @code{target} must be @code{static}.
>  
> +@item zero_call_used_regs ("@var{choice}")
> +@cindex @code{zero_call_used_regs} function attribute
> +
> +The @code{zero_call_used_regs} attribute causes the compiler to zero
> +a subset of all call-used registers at function return according to
> +@var{choice}.

Suggest dropping “according to @var{choice}” here, since it's now
disconnected with the part that talks about what @var{choice} is.

> +This is used to increase the program security by either mitigating

s/the program security/program security/

> +Return-Oriented Programming (ROP) or preventing information leak

leakage

(FWIW, I'm not sure “mitigating ROP” is really correct usage, but I don't
have any better suggestions.)

> +through registers.
> +
> +A ``call-used'' register is a register whose contents can be changed by
> +a function call; therefore, a caller cannot assume that the register has
> +the same contents on return from the function as it had before calling
> +the function.  Such registers are also called ``call-clobbered'',
> +``caller-saved'', or ``volatile''.

Reading it back, perhaps it would be better to put this whole paragraph
in a footnote immediately after the first use of “call-used registers”,
i.e.

…call-used registers@footnote{A ``call-used'' register…}…

It obviously breaks the flow when reading the raw .texi, but I think it
reads better in the final version.

> +In order to satisfy users with different security needs and control the
> +run-time overhead at the same time, GCC provides a flexible way to choose
> +the subset of the call-used registers to be zeroed.

Maybe s/GCC/the @var{choice} parameter/.

> +
> +The three basic values of @var{choice} are:

After which, I think this should be part of the previous paragraph.

> +@itemize @bullet
> +@item
> +@samp{skip} doesn't zero any call-used registers.
> +
> +@item
> +@samp{used} only zeros call-used registers that are used in the function.
> +A ``used'' register is one whose content has been set or referenced in
> +the function.
> +
> +@item
> +@samp{all} zeros all call-used registers.
> +@end itemize
> +
> +In addition to these three basic choices, it is possible to modify
> +@samp{used} or @samp{all} as follows:
> +
> +@itemize @bullet
> +@item
> +Adding @samp{-gpr} restricts the zeroing to general-purpose registers.
> +
> +@item
> +Adding @samp{-arg} restricts the zeroing to registers that can sometimes
> +be used to pass function arguments.  This includes all argument registers
> +defined by the platform's calling conversion, regardless of whether the
> +function uses those registers for function arguments or not.
> +@end itemize
> +
> +The modifiers can be used individually or together.  If they are used
> +together, they must appear in the order above.
> +
> +The full list of @var{choice}s is therefore:
> +
> +@itemize @bullet
> +@item
> +@samp{skip} doesn't zero any call-used register.
> +
> +@item
> +@samp{used} only zeros call-used registers that are used in the function.
> +
> +@item
> +@samp{all} zeros all call-used registers.
> +
> +@item
> +@samp{used-arg} only zeros used call-used registers that pass arguments.
> +
> +@item
> +@samp{used-gpr} only zeros used call-used general purpose registers.
> +
> +@item
> +@samp{used-gpr-arg} only zeros used call-used general purpose registers that
> +pass arguments.
> +
> +@item
> +@samp{all-gpr-arg} zeros all call-used general purpose registers that pass
> +arguments.
> +
> +@item
> +@samp{all-arg} zeros all call-used registers that pass arguments.
> +
> +@item
> +@samp{all-gpr} zeros all call-used general purpose registers.
> +@end itemize

By using a table, I meant:

@table @samp
@item skip
…

@item used
…
etc.
@end @table

Did you try that and think the output looked odd?

I think the order should be more consistent.  E.g. “all” should be listed
with the other “all” options, and the ordering of foos for “used-foo”
and “all-foo” should be the same.  So maybe:

- skip
- used
- used-arg
- used-gpr
- used-gpr-arg
- all
- all-arg
- all-gpr
- all-gpr-arg

> +Among this list, @samp{used-gpr-arg}, @samp{used-arg}, @samp{all-gpr-arg},

IMO s/Among/Of/ reads slightly better.

> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index c049932..c9e3128 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -550,7 +550,7 @@ Objective-C and Objective-C++ Dialects}.
>  -funit-at-a-time  -funroll-all-loops  -funroll-loops @gol
>  -funsafe-math-optimizations  -funswitch-loops @gol
>  -fipa-ra  -fvariable-expansion-in-unroller  -fvect-cost-model  -fvpt @gol
> --fweb  -fwhole-program  -fwpa  -fuse-linker-plugin @gol
> +-fweb  -fwhole-program  -fwpa  -fuse-linker-plugin -fzero-call-used-regs @gol
>  --param @var{name}=@var{value}
>  -O  -O0  -O1  -O2  -O3  -Os  -Ofast  -Og}
>  
> @@ -12550,6 +12550,19 @@ int foo (void)
>  
>  Not all targets support this option.
>  
> +@item -fzero-call-used-regs=@var{choice}
> +@opindex fzero-call-used-regs
> +Zero call-used registers at function return to increase the program

s/the program/program/

> +security by either mitigating Return-Oriented Programming (ROP) or
> +preventing information leak through registers.

s/leak/leakage/

> @@ -173,6 +173,9 @@ struct GTY(()) rtl_data {
>          local stack.  */
>    unsigned int stack_alignment_estimated;
>  
> +  /* How to zero call-used regsiters for this routine.  */
> +  unsigned int zero_call_used_regs;
> +

Typo: regsiters.  But I don't think we need to add this to crtl.
It's just data that's passed between pass_zero_call_used_regs::execute
and gen_call_used_regs_seq.

>    /* How many NOP insns to place at each function entry by default.  */
>    unsigned short patch_area_size;
>  
> @@ -285,6 +287,24 @@ enum sanitize_code {
>  				  | SANITIZE_BOUNDS_STRICT
>  };
>  
> +/* Different settings for zeroing subset of registers.  */
> +namespace  zero_regs_code {

Should only be one space after “namespace”.  Having “code” in the name
surprised me, think “flags” would be better.

> @@ -5815,6 +5817,101 @@ make_prologue_seq (void)
>    return seq;
>  }
>  
> +/* Emit a sequence of insns to zero the call-used registers before RET.  */
> +using namespace zero_regs_code;

Making the “using” file-wide is too much, since the file has a lot of code
unrelated to this feature.  It should go in the function body instead.

> +
> +static void
> +gen_call_used_regs_seq (rtx_insn *ret)
> +{
> +  bool gpr_only = true;
> +  bool used_only = true;
> +  bool arg_only = true;
> +
> +  /* No need to zero call-used-regs in main ().  */
> +  if (MAIN_NAME_P (DECL_NAME (current_function_decl)))
> +    return;
> +
> +  /* No need to zero call-used-regs if __builtin_eh_return is called
> +     since it isn't a normal function return.  */
> +  if (crtl->calls_eh_return)
> +    return;
> +
> +  /* If gpr_only is true, only zero call-used registers that are
> +     general-purpose registers; if used_only is true, only zero
> +     call-used registers that are used in the current function;
> +     if arg_only is true, only zero call-used registers that pass
> +     parameters defined by the flatform's calling conversion.  */
> +
> +  gpr_only = crtl->zero_call_used_regs & ONLY_GPR;
> +  used_only = crtl->zero_call_used_regs & ONLY_USED;
> +  arg_only = crtl->zero_call_used_regs & ONLY_ARG;

Guess it would be nice to be consistent about which side the “only”
goes on.  FWIW, I don't mind which way: GPR_ONLY etc. would be
OK with me if you prefer that.

> +
> +  /* For each of the hard registers, we should zero it if:
> +	    1. it is a call-used register;
> +	and 2. it is not a fixed register;
> +	and 3. it is not live at the return of the routine;
> +	and 4. it is general registor if gpr_only is true;
> +	and 5. it is used in the routine if used_only is true;
> +	and 6. it is a register that passes parameter if arg_only is true.  */
> +
> +  /* First, prepare the data flow information.  */
> +  basic_block bb = BLOCK_FOR_INSN (ret);
> +  auto_bitmap live_out;
> +  bitmap_copy (live_out, df_get_live_out (bb));
> +  df_simulate_initialize_backwards (bb, live_out);
> +  df_simulate_one_insn_backwards (bb, ret, live_out);
> +
> +  HARD_REG_SET selected_hardregs;
> +  CLEAR_HARD_REG_SET (selected_hardregs);
> +  for (unsigned int regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
> +    {
> +      if (!crtl->abi->clobbers_full_reg_p (regno))
> +	continue;
> +      if (fixed_regs[regno])
> +	continue;
> +      if (REGNO_REG_SET_P (live_out, regno))
> +	continue;
> +      if (gpr_only
> +	  && !TEST_HARD_REG_BIT (reg_class_contents[GENERAL_REGS], regno))
> +	continue;
> +      if (used_only && !df_regs_ever_live_p (regno))
> +	continue;
> +      if (arg_only && !FUNCTION_ARG_REGNO_P (regno))
> +	continue;
> +
> +      /* Now this is a register that we might want to zero.  */
> +      SET_HARD_REG_BIT (selected_hardregs, regno);
> +    }
> +
> +  if (hard_reg_set_empty_p (selected_hardregs))
> +    return;
> +
> +  /* Now that we have a hard register set that needs to be zeroed, pass it to
> +     target to generate zeroing sequence.  */
> +  HARD_REG_SET zeroed_hardregs;
> +  start_sequence ();
> +  zeroed_hardregs = targetm.calls.zero_call_used_regs (selected_hardregs);
> +  rtx_insn *seq = get_insns ();
> +  end_sequence ();
> +  if (seq)
> +    {
> +      /* Emit the memory blockage and register clobber asm volatile before
> +	 the whole sequence.  */
> +      start_sequence ();
> +      expand_asm_reg_clobber_mem_blockage (zeroed_hardregs);
> +      rtx_insn *seq_barrier = get_insns ();
> +      end_sequence ();
> +
> +      emit_insn_before (seq_barrier, ret);
> +      emit_insn_before (seq, ret);
> +
> +      /* Update the data flow information.  */
> +      crtl->must_be_zero_on_return |= zeroed_hardregs;
> +      df_set_bb_dirty (EXIT_BLOCK_PTR_FOR_FN (cfun));
> +    }
> +}
> +
> +
>  /* Return a sequence to be used as the epilogue for the current function,
>     or NULL.  */
>  
> @@ -6486,7 +6583,100 @@ make_pass_thread_prologue_and_epilogue (gcc::context *ctxt)
>  {
>    return new pass_thread_prologue_and_epilogue (ctxt);
>  }
> -
>
> +
> +namespace {
> +
> +const pass_data pass_data_zero_call_used_regs =
> +{
> +  RTL_PASS, /* type */
> +  "zero_call_used_regs", /* name */
> +  OPTGROUP_NONE, /* optinfo_flags */
> +  TV_NONE, /* tv_id */
> +  0, /* properties_required */
> +  0, /* properties_provided */
> +  0, /* properties_destroyed */
> +  0, /* todo_flags_start */
> +  0, /* todo_flags_finish */
> +};
> +
> +class pass_zero_call_used_regs: public rtl_opt_pass
> +{
> +public:
> +  pass_zero_call_used_regs (gcc::context *ctxt)
> +    : rtl_opt_pass (pass_data_zero_call_used_regs, ctxt)
> +  {}
> +
> +  /* opt_pass methods: */
> +  virtual unsigned int execute (function *);
> +
> +}; // class pass_zero_call_used_regs
> +
> +unsigned int
> +pass_zero_call_used_regs::execute (function *fun)
> +{
> +  unsigned int zero_regs_type = UNSET;
> +  unsigned int attr_zero_regs_type = UNSET;
> +
> +  tree attr_zero_regs
> +	= lookup_attribute ("zero_call_used_regs",
> +			    DECL_ATTRIBUTES (fun->decl));

The “= …” line should be indented by only two extra spaces.  Although
in this case it fits on two lines anyway:

  tree attr_zero_regs = lookup_attribute ("zero_call_used_regs",
					  DECL_ATTRIBUTES (fun->decl));

> +  /* Get the type of zero_call_used_regs from function attribute.  */

“from the function attribute”  Might be worth adding “We have already
filtered out invalid attribute values.”, to explain why there's (rightly)
no failure path.

> +  if (attr_zero_regs)
> +    {
> +      /* The TREE_VALUE of an attribute is a TREE_LIST whose TREE_VALUE
> +	 is the attribute argument's value.  */
> +      attr_zero_regs = TREE_VALUE (attr_zero_regs);
> +      gcc_assert (TREE_CODE (attr_zero_regs) == TREE_LIST);
> +      attr_zero_regs = TREE_VALUE (attr_zero_regs);
> +      gcc_assert (TREE_CODE (attr_zero_regs) == STRING_CST);
> +
> +      for (unsigned int i = 0; zero_call_used_regs_opts[i].name != NULL; ++i)
> +	if (strcmp (TREE_STRING_POINTER (attr_zero_regs),
> +		     zero_call_used_regs_opts[i].name) == 0)
> +	  {
> +	    attr_zero_regs_type = zero_call_used_regs_opts[i].flag;
> + 	    break;
> +	  }
> +    }
> +
> +  zero_regs_type = attr_zero_regs_type;
> +
> +  if (!zero_regs_type)
> +    zero_regs_type = flag_zero_call_used_regs;

Having two variables seems unnecessarily complicated.  I think the
attribute code should assign directly to “zero_regs_type”.

> +
> +  crtl->zero_call_used_regs = zero_regs_type;
> +
> +  /* No need to zero call-used-regs when no user request is present.  */
> +  if (!(zero_regs_type & ENABLED))
> +    return 0;
> +
> +  edge_iterator ei;
> +  edge e;
> +
> +  /* This pass needs data flow information.  */
> +  df_analyze ();
> +
> +  /* Iterate over the function's return instructions and insert any
> +     register zeroing required by the -fzero-call-used-regs command-line
> +     option or the "zero_call_used_regs" function attribute.  */
> +  FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
> +    {
> +      rtx_insn *insn = BB_END (e->src);
> +      if (JUMP_P (insn) && ANY_RETURN_P (JUMP_LABEL (insn)))
> +	gen_call_used_regs_seq (insn);

As noted above, we could just pass “zero_regs_type” here rather than
store it in crtl.

> @@ -987,6 +990,35 @@ default_function_value_regno_p (const unsigned int regno ATTRIBUTE_UNUSED)
>  #endif
>  }
>  
> +/* The default hook for TARGET_ZERO_CALL_USED_REGS.  */
> +
> +HARD_REG_SET
> +default_zero_call_used_regs (HARD_REG_SET need_zeroed_hardregs)
> +{
> +  gcc_assert (!hard_reg_set_empty_p (need_zeroed_hardregs));
> +
> +  for (unsigned int regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
> +    if (TEST_HARD_REG_BIT (need_zeroed_hardregs, regno))
> +      {
> +	rtx_insn *last_insn = get_last_insn ();
> +	machine_mode mode = GET_MODE (regno_reg_rtx[regno]);
> +	rtx zero = CONST0_RTX (mode);
> +	rtx_insn *insn = emit_move_insn (regno_reg_rtx[regno], zero);
> +	if (!valid_insn_p (insn))
> +	  {
> +	    static bool issued_error;
> +	    if (!issued_error)
> +	      {
> +		issued_error = true;
> +		sorry ("%qs not supported on this target",
> +			"fzero-call-used_regs");

"-fzero-call-used_regs"

> +	      }
> +	    delete_insns_since (last_insn);
> +	  }
> +      }
> +  return need_zeroed_hardregs;
> +}
> +
>  rtx
>  default_internal_arg_pointer (void)
>  {
> diff --git a/gcc/targhooks.h b/gcc/targhooks.h
> index 44ab926..e0a925f 100644
> --- a/gcc/targhooks.h
> +++ b/gcc/targhooks.h
> @@ -160,6 +160,7 @@ extern unsigned int default_function_arg_round_boundary (machine_mode,
>  							 const_tree);
>  extern bool hook_bool_const_rtx_commutative_p (const_rtx, int);
>  extern rtx default_function_value (const_tree, const_tree, bool);
> +extern HARD_REG_SET default_zero_call_used_regs (HARD_REG_SET);
>  extern rtx default_libcall_value (machine_mode, const_rtx);
>  extern bool default_function_value_regno_p (const unsigned int);
>  extern rtx default_internal_arg_pointer (void);
> diff --git a/gcc/testsuite/c-c++-common/zero-scratch-regs-1.c b/gcc/testsuite/c-c++-common/zero-scratch-regs-1.c
> new file mode 100644
> index 0000000..2463353
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/zero-scratch-regs-1.c
> @@ -0,0 +1,15 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2 -fzero-call-used-regs=skip" } */
> +
> +volatile int result = 0;
> +int 
> +__attribute__((noipa))
> +foo (int x)
> +{
> +  return x;
> +}
> +int main()
> +{
> +  result = foo (2);
> +  return 0;
> +}
> diff --git a/gcc/testsuite/c-c++-common/zero-scratch-regs-10.c b/gcc/testsuite/c-c++-common/zero-scratch-regs-10.c
> new file mode 100644
> index 0000000..bdaf8e7
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/zero-scratch-regs-10.c
> @@ -0,0 +1,92 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2" } */
> +
> +#include <assert.h>
> +int result = 0;
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("skip")))
> +foo1 (int x)
> +{
> +  return (x + 1);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("used-gpr-arg")))
> +foo2 (int x)
> +{
> +  return (x + 2);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("used-gpr")))
> +foo3 (int x)
> +{
> +  return (x + 3);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("used-arg")))
> +foo4 (int x)
> +{
> +  return (x + 4);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("used")))
> +foo5 (int x)
> +{
> +  return (x + 5);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("all-gpr-arg")))
> +foo6 (int x)
> +{
> +  return (x + 6);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("all-gpr")))
> +foo7 (int x)
> +{
> +  return (x + 7);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("all-arg")))
> +foo8 (int x)
> +{
> +  return (x + 8);
> +}
> +
> +int 
> +__attribute__((noipa))
> +__attribute__ ((zero_call_used_regs("all")))
> +foo9 (int x)
> +{
> +  return (x + 9);
> +}
> +
> +int main()
> +{
> +  result = foo1 (1);
> +  result += foo2 (1);
> +  result += foo3 (1);
> +  result += foo4 (1);
> +  result += foo5 (1);
> +  result += foo6 (1);
> +  result += foo7 (1);
> +  result += foo8 (1);
> +  result += foo9 (1);
> +  assert (result == 54);
> +  return 0;
> +}
> diff --git a/gcc/testsuite/c-c++-common/zero-scratch-regs-11.c b/gcc/testsuite/c-c++-common/zero-scratch-regs-11.c
> new file mode 100644
> index 0000000..6756f57
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/zero-scratch-regs-11.c
> @@ -0,0 +1,92 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2 -fzero-call-used-regs=all" } */
> +
> +#include <assert.h>
> +int result = 0;
>
I think this should just #include "zero-scratch-regs-10.c".

> diff --git a/gcc/testsuite/c-c++-common/zero-scratch-regs-2.c b/gcc/testsuite/c-c++-common/zero-scratch-regs-2.c
> new file mode 100644
> index 0000000..73c3794
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/zero-scratch-regs-2.c
> @@ -0,0 +1,15 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2 -fzero-call-used-regs=used-gpr-arg" } */
> +
> +volatile int result = 0;
> +int 
> +__attribute__((noipa))
> +foo (int x)
> +{
> +  return x;
> +}
> +int main()
> +{
> +  result = foo (2);
> +  return 0;
> +}

Similarly these can just #include "zero-scratch-regs-1.c".  This makes
it easier to update the tests in a consistent way in future (if necessary).

> diff --git a/gcc/testsuite/c-c++-common/zero-scratch-regs-attr-usages.c b/gcc/testsuite/c-c++-common/zero-scratch-regs-attr-usages.c
> new file mode 100644
> index 0000000..c60e946
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/zero-scratch-regs-attr-usages.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile} */
> +/* { dg-options "-O2" } */
> +
> +int result __attribute__ ((zero_call_used_regs("all"))); /* { dg-error "attribute applies only to functions" } */
> +int
> +__attribute__ ((zero_call_used_regs("gpr-arg-all")))
> +foo1 (int x) /* { dg-error "unrecognized zero_call_used_regs attribute" } */
> +{
> +  return (x + 1);
> +}

Could you also add a test for the string constant check?  E.g. with
__attribute__ ((zero_call_used_regs(1))).

Thanks,
Richard


More information about the Gcc-patches mailing list