diff mbox series

Hard register asm constraint

Message ID 20240524091312.209365-1-stefansf@linux.ibm.com
State New
Headers show
Series Hard register asm constraint | expand

Commit Message

Stefan Schulze Frielinghaus May 24, 2024, 9:13 a.m. UTC
This implements hard register constraints for inline asm.  A hard register
constraint is of the form {regname} where regname is any valid register.  This
basically renders register asm superfluous.  For example, the snippet

int test (int x, int y)
{
  register int r4 asm ("r4") = x;
  register int r5 asm ("r5") = y;
  unsigned int copy = y;
  asm ("foo %0,%1,%2" : "+d" (r4) : "d" (r5), "d" (copy));
  return r4;
}

could be rewritten into

int test (int x, int y)
{
  asm ("foo %0,%1,%2" : "+{r4}" (x) : "{r5}" (y), "d" (y));
  return x;
}

As a side-effect this also solves the problem of call-clobbered registers.
That being said, I was wondering whether we could utilize this feature in order
to get rid of local register asm automatically?  For example, converting

// Result will be in r2 on s390
extern int bar (void);

void test (void)
{
  register int x asm ("r2") = 42;
  bar ();
  asm ("foo %0\n" :: "r" (x));
}

into

void test (void)
{
  int x = 42;
  bar ();
  asm ("foo %0\n" :: "{r2}" (x));
}

in order to get rid of the limitation of call-clobbered registers which may
lead to subtle bugs---especially if you think of non-obvious calls e.g.
introduced by sanitizer/tracer/whatever.  Since such a transformation has the
potential to break existing code do you see any edge cases where this might be
problematic or even show stoppers?  Currently, even

int test (void)
{
  register int x asm ("r2") = 42;
  register int y asm ("r2") = 24;
  asm ("foo %0,%1\n" :: "r" (x), "r" (y));
}

is allowed which seems error prone to me.  Thus, if 100% backwards
compatibility would be required, then automatically converting every register
asm to the new mechanism isn't viable.  Still quite a lot could be transformed.
Any thoughts?

Currently I allow multiple alternatives as demonstrated by
gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c.  However, since a hard register
constraint is pretty specific I could also think of erroring out in case of
alternatives.  Are there any real use cases out there for multiple
alternatives where one would like to use hard register constraints?

With the current implementation we have a "user visible change" in the sense
that for

void test (void)
{
  register int x asm ("r2") = 42;
  register int y asm ("r2") = 24;
  asm ("foo	%0,%1\n" : "=r" (x), "=r" (y));
}

we do not get the error

  "invalid hard register usage between output operands"

anymore but rather

  "multiple outputs to hard register: %r2"

This is due to the error handling in gimplify_asm_expr ().  Speaking of errors,
I also error out earlier as before which means that e.g. in pr87600-2.c only
the first error is reported and processing is stopped afterwards which means
the subsequent tests fail.

I've been skimming through all targets and it looks to me as if none is using
curly brackets for their constraints.  Of course, I may have missed something.

Cheers,
Stefan

PS: Current state for Clang: https://reviews.llvm.org/D105142

---
 gcc/cfgexpand.cc                              |  42 -------
 gcc/genpreds.cc                               |   4 +-
 gcc/gimplify.cc                               | 115 +++++++++++++++++-
 gcc/lra-constraints.cc                        |  17 +++
 gcc/recog.cc                                  |  14 ++-
 gcc/stmt.cc                                   | 102 +++++++++++++++-
 gcc/stmt.h                                    |  10 +-
 .../gcc.target/s390/asm-hard-reg-1.c          | 103 ++++++++++++++++
 .../gcc.target/s390/asm-hard-reg-2.c          |  29 +++++
 .../gcc.target/s390/asm-hard-reg-3.c          |  24 ++++
 gcc/testsuite/lib/scanasm.exp                 |   4 +
 11 files changed, 407 insertions(+), 57 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c
 create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
 create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c

Comments

Stefan Schulze Frielinghaus June 10, 2024, 5:19 a.m. UTC | #1
Ping.

On Fri, May 24, 2024 at 11:13:12AM +0200, Stefan Schulze Frielinghaus wrote:
> This implements hard register constraints for inline asm.  A hard register
> constraint is of the form {regname} where regname is any valid register.  This
> basically renders register asm superfluous.  For example, the snippet
> 
> int test (int x, int y)
> {
>   register int r4 asm ("r4") = x;
>   register int r5 asm ("r5") = y;
>   unsigned int copy = y;
>   asm ("foo %0,%1,%2" : "+d" (r4) : "d" (r5), "d" (copy));
>   return r4;
> }
> 
> could be rewritten into
> 
> int test (int x, int y)
> {
>   asm ("foo %0,%1,%2" : "+{r4}" (x) : "{r5}" (y), "d" (y));
>   return x;
> }
> 
> As a side-effect this also solves the problem of call-clobbered registers.
> That being said, I was wondering whether we could utilize this feature in order
> to get rid of local register asm automatically?  For example, converting
> 
> // Result will be in r2 on s390
> extern int bar (void);
> 
> void test (void)
> {
>   register int x asm ("r2") = 42;
>   bar ();
>   asm ("foo %0\n" :: "r" (x));
> }
> 
> into
> 
> void test (void)
> {
>   int x = 42;
>   bar ();
>   asm ("foo %0\n" :: "{r2}" (x));
> }
> 
> in order to get rid of the limitation of call-clobbered registers which may
> lead to subtle bugs---especially if you think of non-obvious calls e.g.
> introduced by sanitizer/tracer/whatever.  Since such a transformation has the
> potential to break existing code do you see any edge cases where this might be
> problematic or even show stoppers?  Currently, even
> 
> int test (void)
> {
>   register int x asm ("r2") = 42;
>   register int y asm ("r2") = 24;
>   asm ("foo %0,%1\n" :: "r" (x), "r" (y));
> }
> 
> is allowed which seems error prone to me.  Thus, if 100% backwards
> compatibility would be required, then automatically converting every register
> asm to the new mechanism isn't viable.  Still quite a lot could be transformed.
> Any thoughts?
> 
> Currently I allow multiple alternatives as demonstrated by
> gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c.  However, since a hard register
> constraint is pretty specific I could also think of erroring out in case of
> alternatives.  Are there any real use cases out there for multiple
> alternatives where one would like to use hard register constraints?
> 
> With the current implementation we have a "user visible change" in the sense
> that for
> 
> void test (void)
> {
>   register int x asm ("r2") = 42;
>   register int y asm ("r2") = 24;
>   asm ("foo	%0,%1\n" : "=r" (x), "=r" (y));
> }
> 
> we do not get the error
> 
>   "invalid hard register usage between output operands"
> 
> anymore but rather
> 
>   "multiple outputs to hard register: %r2"
> 
> This is due to the error handling in gimplify_asm_expr ().  Speaking of errors,
> I also error out earlier as before which means that e.g. in pr87600-2.c only
> the first error is reported and processing is stopped afterwards which means
> the subsequent tests fail.
> 
> I've been skimming through all targets and it looks to me as if none is using
> curly brackets for their constraints.  Of course, I may have missed something.
> 
> Cheers,
> Stefan
> 
> PS: Current state for Clang: https://reviews.llvm.org/D105142
> 
> ---
>  gcc/cfgexpand.cc                              |  42 -------
>  gcc/genpreds.cc                               |   4 +-
>  gcc/gimplify.cc                               | 115 +++++++++++++++++-
>  gcc/lra-constraints.cc                        |  17 +++
>  gcc/recog.cc                                  |  14 ++-
>  gcc/stmt.cc                                   | 102 +++++++++++++++-
>  gcc/stmt.h                                    |  10 +-
>  .../gcc.target/s390/asm-hard-reg-1.c          | 103 ++++++++++++++++
>  .../gcc.target/s390/asm-hard-reg-2.c          |  29 +++++
>  .../gcc.target/s390/asm-hard-reg-3.c          |  24 ++++
>  gcc/testsuite/lib/scanasm.exp                 |   4 +
>  11 files changed, 407 insertions(+), 57 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c
>  create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
>  create mode 100644 gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c
> 
> diff --git a/gcc/cfgexpand.cc b/gcc/cfgexpand.cc
> index 557cb28733b..47f71a2e803 100644
> --- a/gcc/cfgexpand.cc
> +++ b/gcc/cfgexpand.cc
> @@ -2955,44 +2955,6 @@ expand_asm_loc (tree string, int vol, location_t locus)
>    emit_insn (body);
>  }
>  
> -/* Return the number of times character C occurs in string S.  */
> -static int
> -n_occurrences (int c, const char *s)
> -{
> -  int n = 0;
> -  while (*s)
> -    n += (*s++ == c);
> -  return n;
> -}
> -
> -/* A subroutine of expand_asm_operands.  Check that all operands have
> -   the same number of alternatives.  Return true if so.  */
> -
> -static bool
> -check_operand_nalternatives (const vec<const char *> &constraints)
> -{
> -  unsigned len = constraints.length();
> -  if (len > 0)
> -    {
> -      int nalternatives = n_occurrences (',', constraints[0]);
> -
> -      if (nalternatives + 1 > MAX_RECOG_ALTERNATIVES)
> -	{
> -	  error ("too many alternatives in %<asm%>");
> -	  return false;
> -	}
> -
> -      for (unsigned i = 1; i < len; ++i)
> -	if (n_occurrences (',', constraints[i]) != nalternatives)
> -	  {
> -	    error ("operand constraints for %<asm%> differ "
> -		   "in number of alternatives");
> -	    return false;
> -	  }
> -    }
> -  return true;
> -}
> -
>  /* Check for overlap between registers marked in CLOBBERED_REGS and
>     anything inappropriate in T.  Emit error and return the register
>     variable definition for error, NULL_TREE for ok.  */
> @@ -3158,10 +3120,6 @@ expand_asm_stmt (gasm *stmt)
>  	= TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t)));
>      }
>  
> -  /* ??? Diagnose during gimplification?  */
> -  if (! check_operand_nalternatives (constraints))
> -    return;
> -
>    /* Count the number of meaningful clobbered registers, ignoring what
>       we would ignore later.  */
>    auto_vec<rtx> clobber_rvec;
> diff --git a/gcc/genpreds.cc b/gcc/genpreds.cc
> index 55d149e8a40..f0d59cb0846 100644
> --- a/gcc/genpreds.cc
> +++ b/gcc/genpreds.cc
> @@ -1148,7 +1148,7 @@ write_insn_constraint_len (void)
>    unsigned int i;
>  
>    puts ("static inline size_t\n"
> -	"insn_constraint_len (char fc, const char *str ATTRIBUTE_UNUSED)\n"
> +	"insn_constraint_len (char fc, const char *str)\n"
>  	"{\n"
>  	"  switch (fc)\n"
>  	"    {");
> @@ -1181,6 +1181,8 @@ write_insn_constraint_len (void)
>  
>    puts ("    default: break;\n"
>  	"    }\n"
> +	"  if (str[0] == '{')\n"
> +	"      return ((const char *)rawmemchr (str + 1, '}') - str) + 1;\n"
>  	"  return 1;\n"
>  	"}\n");
>  }
> diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
> index b0ed58ed0f9..b4b16e75023 100644
> --- a/gcc/gimplify.cc
> +++ b/gcc/gimplify.cc
> @@ -70,6 +70,9 @@ along with GCC; see the file COPYING3.  If not see
>  #include "omp-offload.h"
>  #include "context.h"
>  #include "tree-nested.h"
> +#include "insn-config.h"
> +#include "recog.h"
> +#include "output.h"
>  
>  /* Identifier for a basic condition, mapping it to other basic conditions of
>     its Boolean expression.  Basic conditions given the same uid (in the same
> @@ -6952,6 +6955,42 @@ gimplify_addr_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
>    return ret;
>  }
>  
> +/* Return the number of times character C occurs in string S.  */
> +
> +static int
> +num_occurrences (int c, const char *s)
> +{
> +  int n = 0;
> +  while (*s)
> +    n += (*s++ == c);
> +  return n;
> +}
> +
> +/* A subroutine of gimplify_asm_expr.  Check that all operands have
> +   the same number of alternatives.  Return -1 if this is violated.  Otherwise
> +   return the number of alternatives.  */
> +
> +static int
> +num_alternatives (const_tree link)
> +{
> +  if (link == nullptr)
> +    return 0;
> +
> +  const char *constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
> +  int num = num_occurrences (',', constraint);
> +
> +  if (num + 1 > MAX_RECOG_ALTERNATIVES)
> +    return -1;
> +
> +  for (link = TREE_CHAIN (link); link; link = TREE_CHAIN (link))
> +    {
> +      constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
> +      if (num_occurrences (',', constraint) != num)
> +	return -1;
> +    }
> +  return num + 1;
> +}
> +
>  /* Gimplify the operands of an ASM_EXPR.  Input operands should be a gimple
>     value; output operands should be a gimple lvalue.  */
>  
> @@ -6982,6 +7021,35 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
>    clobbers = NULL;
>    labels = NULL;
>  
> +  int num_alternatives_out = num_alternatives (ASM_OUTPUTS (expr));
> +  int num_alternatives_in = num_alternatives (ASM_INPUTS (expr));
> +  if (num_alternatives_out == -1 || num_alternatives_in == -1
> +      || (num_alternatives_out > 0 && num_alternatives_in > 0
> +	  && num_alternatives_out != num_alternatives_in))
> +    {
> +      error ("operand constraints for %<asm%> differ "
> +	     "in number of alternatives");
> +      return GS_ERROR;
> +    }
> +  int num_alternatives = MAX (num_alternatives_out, num_alternatives_in);
> +
> +  /* Regarding hard register constraints ensure that each hard register is used
> +     at most once over all inputs/outputs and each alternative.  Keep track in
> +     hardregs[0] which hard register is used via an asm register over all
> +     inputs/outputs.  hardregs[i] for i >= 2 describes which hard registers are
> +     used for alternative i-2 over all inputs/outputs.  hardregs[1] is a
> +     reduction of all alternatives, i.e., hardregs[1] |= hardregs[i] for i >= 2
> +     and describes whether a hard register is used in any alternative.  This is
> +     just a shortcut instead of recomputing the union over all alternatives;
> +     possibly multiple times.  */
> +  auto_vec<HARD_REG_SET> hardregs (num_alternatives + 2);
> +  for (int i = 0; i < num_alternatives + 2; ++i)
> +    {
> +      HARD_REG_SET hregset;
> +      CLEAR_HARD_REG_SET (hregset);
> +      hardregs.quick_push (hregset);
> +    }
> +
>    ret = GS_ALL_DONE;
>    link_next = NULL_TREE;
>    for (i = 0, link = ASM_OUTPUTS (expr); link; ++i, link = link_next)
> @@ -6998,8 +7066,8 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
>        if (constraint_len == 0)
>          continue;
>  
> -      ok = parse_output_constraint (&constraint, i, 0, 0,
> -				    &allows_mem, &allows_reg, &is_inout);
> +      ok = parse_output_constraint (&constraint, i, 0, 0, &allows_mem,
> +				    &allows_reg, &is_inout, &hardregs);
>        if (!ok)
>  	{
>  	  ret = GS_ERROR;
> @@ -7062,6 +7130,24 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
>  	      TREE_VALUE (link) = tem;
>  	      tret = GS_OK;
>  	    }
> +	  if (VAR_P (op) && DECL_HARD_REGISTER (op))
> +	    {
> +	      tree id = DECL_ASSEMBLER_NAME (op);
> +	      const char *asmspec = IDENTIFIER_POINTER (id) + 1;
> +	      int hardreg = decode_reg_name (asmspec);
> +	      if (hardreg >= 0)
> +		{
> +		  if (TEST_HARD_REG_BIT (hardregs[0], hardreg)
> +		      || TEST_HARD_REG_BIT (hardregs[1], hardreg))
> +		    {
> +		      error ("multiple outputs to hard register: %s",
> +			     reg_names[hardreg]);
> +		      return GS_ERROR;
> +		    }
> +		  else
> +		    SET_HARD_REG_BIT (hardregs[0], hardreg);
> +		}
> +	    }
>  	}
>  
>        vec_safe_push (outputs, link);
> @@ -7161,13 +7247,16 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
>  	}
>      }
>  
> +  for (unsigned int i = 0; i < hardregs.length (); ++i)
> +    CLEAR_HARD_REG_SET (hardregs[i]);
> +
>    link_next = NULL_TREE;
>    for (link = ASM_INPUTS (expr); link; ++i, link = link_next)
>      {
>        link_next = TREE_CHAIN (link);
>        constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
> -      parse_input_constraint (&constraint, 0, 0, noutputs, 0,
> -			      oconstraints, &allows_mem, &allows_reg);
> +      parse_input_constraint (&constraint, 0, 0, noutputs, 0, oconstraints,
> +			      &allows_mem, &allows_reg, &hardregs);
>  
>        /* If we can't make copies, we can only accept memory.  */
>        tree intype = TREE_TYPE (TREE_VALUE (link));
> @@ -7241,6 +7330,24 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
>  				is_gimple_asm_val, fb_rvalue);
>  	  if (tret == GS_ERROR)
>  	    ret = tret;
> +	  tree inputv = TREE_VALUE (link);
> +	  if (VAR_P (inputv) && DECL_HARD_REGISTER (inputv))
> +	    {
> +	      tree id = DECL_ASSEMBLER_NAME (inputv);
> +	      const char *asmspec = IDENTIFIER_POINTER (id) + 1;
> +	      int hardreg = decode_reg_name (asmspec);
> +	      if (hardreg >= 0)
> +		{
> +		  if (TEST_HARD_REG_BIT (hardregs[1], hardreg))
> +		    {
> +		      error ("multiple inputs to hard register: %s",
> +			     reg_names[hardreg]);
> +		      return GS_ERROR;
> +		    }
> +		  else
> +		    SET_HARD_REG_BIT (hardregs[0], hardreg);
> +		}
> +	    }
>  	}
>  
>        TREE_CHAIN (link) = NULL_TREE;
> diff --git a/gcc/lra-constraints.cc b/gcc/lra-constraints.cc
> index e945a4da451..d81753eefaa 100644
> --- a/gcc/lra-constraints.cc
> +++ b/gcc/lra-constraints.cc
> @@ -114,6 +114,7 @@
>  #include "target.h"
>  #include "rtl.h"
>  #include "tree.h"
> +#include "stmt.h"
>  #include "predict.h"
>  #include "df.h"
>  #include "memmodel.h"
> @@ -2165,6 +2166,7 @@ process_alt_operands (int only_alternative)
>    bool costly_p;
>    enum reg_class cl;
>    const HARD_REG_SET *cl_filter;
> +  HARD_REG_SET hregset;
>  
>    /* Calculate some data common for all alternatives to speed up the
>       function.	*/
> @@ -2536,6 +2538,21 @@ process_alt_operands (int only_alternative)
>  		  cl_filter = nullptr;
>  		  goto reg;
>  
> +		case '{':
> +		    {
> +		      /* Currently this form of constraint is only allowed in
> +			 asm statements which are verified during gimplify,
> +			 i.e., regno >= 0 holds for those.  genoutput fails on
> +			 it.  For future proofness assert it.  */
> +		      int regno = parse_constraint_regname (p);
> +		      gcc_assert (regno >= 0);
> +		      cl = REGNO_REG_CLASS (regno);
> +		      CLEAR_HARD_REG_SET (hregset);
> +		      SET_HARD_REG_BIT (hregset, regno);
> +		      cl_filter = &hregset;
> +		      goto reg;
> +		    }
> +
>  		default:
>  		  cn = lookup_constraint (p);
>  		  switch (get_constraint_type (cn))
> diff --git a/gcc/recog.cc b/gcc/recog.cc
> index a6799e3f5e6..8a474cfb8a7 100644
> --- a/gcc/recog.cc
> +++ b/gcc/recog.cc
> @@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "target.h"
>  #include "rtl.h"
>  #include "tree.h"
> +#include "stmt.h"
>  #include "cfghooks.h"
>  #include "df.h"
>  #include "memmodel.h"
> @@ -2296,10 +2297,11 @@ asm_operand_ok (rtx op, const char *constraint, const char **constraints)
>  	  switch (get_constraint_type (cn))
>  	    {
>  	    case CT_REGISTER:
> -	      if (!result
> -		  && reg_class_for_constraint (cn) != NO_REGS
> -		  && GET_MODE (op) != BLKmode
> -		  && register_operand (op, VOIDmode))
> +	      if ((!result
> +		   && reg_class_for_constraint (cn) != NO_REGS
> +		   && GET_MODE (op) != BLKmode
> +		   && register_operand (op, VOIDmode))
> +		  || constraint[0] == '{')
>  		result = 1;
>  	      break;
>  
> @@ -3231,6 +3233,10 @@ constrain_operands (int strict, alternative_mask alternatives)
>  		  win = true;
>  		break;
>  
> +	      case '{':
> +		win = true;
> +		break;
> +
>  	      default:
>  		{
>  		  enum constraint_num cn = lookup_constraint (p);
> diff --git a/gcc/stmt.cc b/gcc/stmt.cc
> index ae1527f0a19..1f20b09f90e 100644
> --- a/gcc/stmt.cc
> +++ b/gcc/stmt.cc
> @@ -39,6 +39,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "emit-rtl.h"
>  #include "pretty-print.h"
>  #include "diagnostic-core.h"
> +#include "output.h"
>  
>  #include "fold-const.h"
>  #include "varasm.h"
> @@ -174,6 +175,32 @@ expand_label (tree label)
>      maybe_set_first_label_num (label_r);
>  }
>  
> +/* Parse a hard register constraint and return its number or -1 in case of an
> +   error.  BEGIN should point to a string of the form "{regname}".  For the
> +   sake of simplicity assume that a register name is not longer than 31
> +   characters, if not error out.  */
> +
> +int
> +parse_constraint_regname (const char *begin)
> +{
> +  if (*begin != '{')
> +    return -1;
> +  ++begin;
> +  const char *end = begin;
> +  while (*end != '}' && *end != '\0')
> +    ++end;
> +  if (*end != '}' || end == begin)
> +    return -1;
> +  ptrdiff_t len = end - begin;
> +  if (len >= 31)
> +    return -1;
> +  char regname[32];
> +  memcpy (regname, begin, len);
> +  regname[len] = '\0';
> +  int regno = decode_reg_name (regname);
> +  return regno;
> +}
> +
>  /* Parse the output constraint pointed to by *CONSTRAINT_P.  It is the
>     OPERAND_NUMth output operand, indexed from zero.  There are NINPUTS
>     inputs and NOUTPUTS outputs to this extended-asm.  Upon return,
> @@ -190,7 +217,8 @@ expand_label (tree label)
>  bool
>  parse_output_constraint (const char **constraint_p, int operand_num,
>  			 int ninputs, int noutputs, bool *allows_mem,
> -			 bool *allows_reg, bool *is_inout)
> +			 bool *allows_reg, bool *is_inout,
> +			 vec<HARD_REG_SET> *hardregs)
>  {
>    const char *constraint = *constraint_p;
>    const char *p;
> @@ -244,6 +272,8 @@ parse_output_constraint (const char **constraint_p, int operand_num,
>        constraint = *constraint_p;
>      }
>  
> +  unsigned int alternative = 2;
> +
>    /* Loop through the constraint string.  */
>    for (p = constraint + 1; *p; )
>      {
> @@ -268,7 +298,11 @@ parse_output_constraint (const char **constraint_p, int operand_num,
>  	case 'E':  case 'F':  case 'G':  case 'H':
>  	case 's':  case 'i':  case 'n':
>  	case 'I':  case 'J':  case 'K':  case 'L':  case 'M':
> -	case 'N':  case 'O':  case 'P':  case ',':
> +	case 'N':  case 'O':  case 'P':
> +	  break;
> +
> +	case ',':
> +	  ++alternative;
>  	  break;
>  
>  	case '0':  case '1':  case '2':  case '3':  case '4':
> @@ -289,6 +323,33 @@ parse_output_constraint (const char **constraint_p, int operand_num,
>  	  *allows_mem = true;
>  	  break;
>  
> +	case '{':
> +	  {
> +	    int regno = parse_constraint_regname (p);
> +	    if (regno < 0)
> +	      {
> +		error ("invalid output constraint: %s", p);
> +		return false;
> +	      }
> +	    if (hardregs)
> +	      {
> +		if (TEST_HARD_REG_BIT ((*hardregs)[0], regno)
> +		    || TEST_HARD_REG_BIT ((*hardregs)[alternative], regno))
> +		  {
> +		    error ("multiple outputs to hard register: %s",
> +			   reg_names[regno]);
> +		    return false;
> +		  }
> +		else
> +		  {
> +		    SET_HARD_REG_BIT ((*hardregs)[1], regno);
> +		    SET_HARD_REG_BIT ((*hardregs)[alternative], regno);
> +		  }
> +	      }
> +	    *allows_reg = true;
> +	    break;
> +	  }
> +
>  	default:
>  	  if (!ISALPHA (*p))
>  	    break;
> @@ -317,7 +378,8 @@ bool
>  parse_input_constraint (const char **constraint_p, int input_num,
>  			int ninputs, int noutputs, int ninout,
>  			const char * const * constraints,
> -			bool *allows_mem, bool *allows_reg)
> +			bool *allows_mem, bool *allows_reg,
> +			vec<HARD_REG_SET> *hardregs)
>  {
>    const char *constraint = *constraint_p;
>    const char *orig_constraint = constraint;
> @@ -332,6 +394,8 @@ parse_input_constraint (const char **constraint_p, int input_num,
>  
>    /* Make sure constraint has neither `=', `+', nor '&'.  */
>  
> +  unsigned int alternative = 2;
> +
>    for (j = 0; j < c_len; j += CONSTRAINT_LEN (constraint[j], constraint+j))
>      switch (constraint[j])
>        {
> @@ -358,7 +422,11 @@ parse_input_constraint (const char **constraint_p, int input_num,
>        case 'E':  case 'F':  case 'G':  case 'H':
>        case 's':  case 'i':  case 'n':
>        case 'I':  case 'J':  case 'K':  case 'L':  case 'M':
> -      case 'N':  case 'O':  case 'P':  case ',':
> +      case 'N':  case 'O':  case 'P':
> +	break;
> +
> +      case ',':
> +	++alternative;
>  	break;
>  
>  	/* Whether or not a numeric constraint allows a register is
> @@ -408,6 +476,32 @@ parse_input_constraint (const char **constraint_p, int input_num,
>  	*allows_mem = true;
>  	break;
>  
> +      case '{':
> +	{
> +	  int regno = parse_constraint_regname (constraint + j);
> +	  if (regno < 0)
> +	    {
> +	      error ("invalid input constraint: %s", constraint + j);
> +	      return false;
> +	    }
> +	  if (hardregs)
> +	    {
> +	      if (TEST_HARD_REG_BIT ((*hardregs)[0], regno)
> +		  || TEST_HARD_REG_BIT ((*hardregs)[alternative], regno))
> +		{
> +		  error ("multiple inputs to hard register: %s",
> +			    reg_names[regno]);
> +		}
> +	      else
> +		{
> +		  SET_HARD_REG_BIT ((*hardregs)[1], regno);
> +		  SET_HARD_REG_BIT ((*hardregs)[alternative], regno);
> +		}
> +	    }
> +	  *allows_reg = true;
> +	  break;
> +	}
> +
>        default:
>  	if (! ISALPHA (constraint[j]))
>  	  {
> diff --git a/gcc/stmt.h b/gcc/stmt.h
> index a2caae7121b..05889ff3798 100644
> --- a/gcc/stmt.h
> +++ b/gcc/stmt.h
> @@ -20,11 +20,17 @@ along with GCC; see the file COPYING3.  If not see
>  #ifndef GCC_STMT_H
>  #define GCC_STMT_H
>  
> +#include "target.h"
> +#include "hard-reg-set.h"
> +
>  extern void expand_label (tree);
>  extern bool parse_output_constraint (const char **, int, int, int,
> -				     bool *, bool *, bool *);
> +				     bool *, bool *, bool *,
> +				     vec<HARD_REG_SET> * = nullptr);
>  extern bool parse_input_constraint (const char **, int, int, int, int,
> -				    const char * const *, bool *, bool *);
> +				    const char * const *, bool *, bool *,
> +				    vec<HARD_REG_SET> * = nullptr);
> +extern int parse_constraint_regname (const char *);
>  extern tree resolve_asm_operand_names (tree, tree, tree, tree);
>  #ifdef HARD_CONST
>  /* Silly ifdef to avoid having all includers depend on hard-reg-set.h.  */
> diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c
> new file mode 100644
> index 00000000000..53895d98663
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c
> @@ -0,0 +1,103 @@
> +/* { dg-do compile { target { lp64 } } } */
> +/* { dg-options "-O2 -march=z13" } */
> +/* { dg-final { check-function-bodies "**" "" "" } } */
> +
> +/*
> +** test_in_1:
> +**     foo	%r2
> +**     br	%r14
> +*/
> +
> +int
> +test_in_1 (int x)
> +{
> +  asm ("foo	%0" :: "{r2}" (x));
> +  return x;
> +}
> +
> +/*
> +** test_in_2:
> +**     lgr	(%r[0-9]+),%r2
> +**     lhi	%r2,42
> +**     foo	%r2
> +**     lgr	%r2,\1
> +**     br	%r14
> +*/
> +
> +int
> +test_in_2 (int x)
> +{
> +  asm ("foo	%0" :: "{r2}" (42));
> +  return x;
> +}
> +
> +/*
> +** test_in_3:
> +**     stmg	%r12,%r15,96\(%r15\)
> +**     lay	%r15,-160\(%r15\)
> +**     lgr	(%r[0-9]+),%r2
> +**     ahi	%r2,1
> +**     lgfr	%r2,%r2
> +**     brasl	%r14,foo@PLT
> +**     lr	%r3,%r2
> +**     lr	%r2,\1
> +**     foo	%r3,%r2
> +**     lgr	%r2,\1
> +**     lmg	%r12,%r15,256\(%r15\)
> +**     br	%r14
> +*/
> +
> +extern int foo (int);
> +
> +int
> +test_in_3 (int x)
> +{
> +  asm ("foo	%0,%1\n" :: "{r3}" (foo (x + 1)), "{r2}" (x));
> +  return x;
> +}
> +
> +/*
> +** test_out_1:
> +**     foo	%r3
> +**     lgfr	%r2,%r3
> +**     br	%r14
> +*/
> +
> +int
> +test_out_1 (void)
> +{
> +  int x;
> +  asm ("foo	%0" : "={r3}" (x));
> +  return x;
> +}
> +
> +/*
> +** test_out_2:
> +**     lgr	(%r[0-9]+),%r2
> +**     foo	%r2
> +**     ark	(%r[0-9]+),\1,%r2
> +**     lgfr	%r2,\2
> +**     br	%r14
> +*/
> +
> +int
> +test_out_2 (int x)
> +{
> +  int y;
> +  asm ("foo	%0" : "={r2}" (y));
> +  return x + y;
> +}
> +
> +/*
> +** test_inout_1:
> +**     foo	%r2
> +**     lgfr	%r2,%r2
> +**     br	%r14
> +*/
> +
> +int
> +test_inout_1 (int x)
> +{
> +  asm ("foo	%0" : "+{r2}" (x));
> +  return x;
> +}
> diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
> new file mode 100644
> index 00000000000..9f3c221b937
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
> @@ -0,0 +1,29 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +/* { dg-final { check-function-bodies "**" "" "" } } */
> +
> +/*
> +** test_1:
> +**     lr	%r5,%r2
> +**     foo	%r5,%r3
> +**     br	%r14
> +*/
> +
> +void
> +test_1 (int x, int *y)
> +{
> +  asm ("foo	%0,%1" :: "m{r4},{r5}" (x), "m,r" (y));
> +}
> +
> +/*
> +** test_2:
> +**     lr	%r4,%r2
> +**     foo	%r4,0\(%r3\)
> +**     br	%r14
> +*/
> +
> +void
> +test_2 (int x, int *y)
> +{
> +  asm ("foo	%0,%1" :: "m{r4},{r5}" (x), "m,r" (*y));
> +}
> diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c
> new file mode 100644
> index 00000000000..0edcdd3cfde
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c
> @@ -0,0 +1,24 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +void
> +test (void)
> +{
> +  int x, y;
> +  register int r4 asm ("r4") = 0;
> +
> +  asm ("" :: "{}" (42)); /* { dg-error "invalid input constraint: \{\}" } */
> +  asm ("" :: "{r4" (42)); /* { dg-error "invalid input constraint: \{r4" } */
> +  asm ("" :: "{r17}" (42)); /* { dg-error "invalid input constraint: \{r17\}" } */
> +
> +  asm ("" :: "r" (r4), "{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" :: "{r4}" (42), "r" (r4)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" :: "{r4}" (42), "{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" :: "{r2},{r4}" (42), "{r4},{r3}" (42));
> +  asm ("" :: "{r2},{r4}" (42), "{r3},{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" :: "{r4},{r2}" (42), "{r4},{r3}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" :: "{r3}{r4}" (42), "{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" : "+{r4}" (x), "={r4}" (y)); /* { dg-error "multiple outputs to hard register: %r4" } */
> +  asm ("" : "={r4}" (y) : "{r4}" (42), "0" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +  asm ("" : "+{r4}" (x) : "{r4}" (y)); /* { dg-error "multiple inputs to hard register: %r4" } */
> +}
> diff --git a/gcc/testsuite/lib/scanasm.exp b/gcc/testsuite/lib/scanasm.exp
> index 6cf9997240d..d09372096a5 100644
> --- a/gcc/testsuite/lib/scanasm.exp
> +++ b/gcc/testsuite/lib/scanasm.exp
> @@ -896,6 +896,10 @@ proc configure_check-function-bodies { config } {
>  	set up_config(fluff) {^\s*(?://)}
>      } elseif { [istarget *-*-darwin*] } {
>  	set up_config(fluff) {^\s*(?:\.|//|@)|^L[0-9ABCESV]}
> +    } elseif { [istarget s390*-*-*] } {
> +	# Additionally to the defaults skip lines beginning with a # resulting
> +	# from inline asm.
> +	set up_config(fluff) {^\s*(?:\.|//|@|$|#)}
>      } else {
>  	# Skip lines beginning with labels ('.L[...]:') or other directives
>  	# ('.align', '.cfi_startproc', '.quad [...]', '.text', etc.), '//' or
> -- 
> 2.45.1
>
diff mbox series

Patch

diff --git a/gcc/cfgexpand.cc b/gcc/cfgexpand.cc
index 557cb28733b..47f71a2e803 100644
--- a/gcc/cfgexpand.cc
+++ b/gcc/cfgexpand.cc
@@ -2955,44 +2955,6 @@  expand_asm_loc (tree string, int vol, location_t locus)
   emit_insn (body);
 }
 
-/* Return the number of times character C occurs in string S.  */
-static int
-n_occurrences (int c, const char *s)
-{
-  int n = 0;
-  while (*s)
-    n += (*s++ == c);
-  return n;
-}
-
-/* A subroutine of expand_asm_operands.  Check that all operands have
-   the same number of alternatives.  Return true if so.  */
-
-static bool
-check_operand_nalternatives (const vec<const char *> &constraints)
-{
-  unsigned len = constraints.length();
-  if (len > 0)
-    {
-      int nalternatives = n_occurrences (',', constraints[0]);
-
-      if (nalternatives + 1 > MAX_RECOG_ALTERNATIVES)
-	{
-	  error ("too many alternatives in %<asm%>");
-	  return false;
-	}
-
-      for (unsigned i = 1; i < len; ++i)
-	if (n_occurrences (',', constraints[i]) != nalternatives)
-	  {
-	    error ("operand constraints for %<asm%> differ "
-		   "in number of alternatives");
-	    return false;
-	  }
-    }
-  return true;
-}
-
 /* Check for overlap between registers marked in CLOBBERED_REGS and
    anything inappropriate in T.  Emit error and return the register
    variable definition for error, NULL_TREE for ok.  */
@@ -3158,10 +3120,6 @@  expand_asm_stmt (gasm *stmt)
 	= TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t)));
     }
 
-  /* ??? Diagnose during gimplification?  */
-  if (! check_operand_nalternatives (constraints))
-    return;
-
   /* Count the number of meaningful clobbered registers, ignoring what
      we would ignore later.  */
   auto_vec<rtx> clobber_rvec;
diff --git a/gcc/genpreds.cc b/gcc/genpreds.cc
index 55d149e8a40..f0d59cb0846 100644
--- a/gcc/genpreds.cc
+++ b/gcc/genpreds.cc
@@ -1148,7 +1148,7 @@  write_insn_constraint_len (void)
   unsigned int i;
 
   puts ("static inline size_t\n"
-	"insn_constraint_len (char fc, const char *str ATTRIBUTE_UNUSED)\n"
+	"insn_constraint_len (char fc, const char *str)\n"
 	"{\n"
 	"  switch (fc)\n"
 	"    {");
@@ -1181,6 +1181,8 @@  write_insn_constraint_len (void)
 
   puts ("    default: break;\n"
 	"    }\n"
+	"  if (str[0] == '{')\n"
+	"      return ((const char *)rawmemchr (str + 1, '}') - str) + 1;\n"
 	"  return 1;\n"
 	"}\n");
 }
diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
index b0ed58ed0f9..b4b16e75023 100644
--- a/gcc/gimplify.cc
+++ b/gcc/gimplify.cc
@@ -70,6 +70,9 @@  along with GCC; see the file COPYING3.  If not see
 #include "omp-offload.h"
 #include "context.h"
 #include "tree-nested.h"
+#include "insn-config.h"
+#include "recog.h"
+#include "output.h"
 
 /* Identifier for a basic condition, mapping it to other basic conditions of
    its Boolean expression.  Basic conditions given the same uid (in the same
@@ -6952,6 +6955,42 @@  gimplify_addr_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
   return ret;
 }
 
+/* Return the number of times character C occurs in string S.  */
+
+static int
+num_occurrences (int c, const char *s)
+{
+  int n = 0;
+  while (*s)
+    n += (*s++ == c);
+  return n;
+}
+
+/* A subroutine of gimplify_asm_expr.  Check that all operands have
+   the same number of alternatives.  Return -1 if this is violated.  Otherwise
+   return the number of alternatives.  */
+
+static int
+num_alternatives (const_tree link)
+{
+  if (link == nullptr)
+    return 0;
+
+  const char *constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
+  int num = num_occurrences (',', constraint);
+
+  if (num + 1 > MAX_RECOG_ALTERNATIVES)
+    return -1;
+
+  for (link = TREE_CHAIN (link); link; link = TREE_CHAIN (link))
+    {
+      constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
+      if (num_occurrences (',', constraint) != num)
+	return -1;
+    }
+  return num + 1;
+}
+
 /* Gimplify the operands of an ASM_EXPR.  Input operands should be a gimple
    value; output operands should be a gimple lvalue.  */
 
@@ -6982,6 +7021,35 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
   clobbers = NULL;
   labels = NULL;
 
+  int num_alternatives_out = num_alternatives (ASM_OUTPUTS (expr));
+  int num_alternatives_in = num_alternatives (ASM_INPUTS (expr));
+  if (num_alternatives_out == -1 || num_alternatives_in == -1
+      || (num_alternatives_out > 0 && num_alternatives_in > 0
+	  && num_alternatives_out != num_alternatives_in))
+    {
+      error ("operand constraints for %<asm%> differ "
+	     "in number of alternatives");
+      return GS_ERROR;
+    }
+  int num_alternatives = MAX (num_alternatives_out, num_alternatives_in);
+
+  /* Regarding hard register constraints ensure that each hard register is used
+     at most once over all inputs/outputs and each alternative.  Keep track in
+     hardregs[0] which hard register is used via an asm register over all
+     inputs/outputs.  hardregs[i] for i >= 2 describes which hard registers are
+     used for alternative i-2 over all inputs/outputs.  hardregs[1] is a
+     reduction of all alternatives, i.e., hardregs[1] |= hardregs[i] for i >= 2
+     and describes whether a hard register is used in any alternative.  This is
+     just a shortcut instead of recomputing the union over all alternatives;
+     possibly multiple times.  */
+  auto_vec<HARD_REG_SET> hardregs (num_alternatives + 2);
+  for (int i = 0; i < num_alternatives + 2; ++i)
+    {
+      HARD_REG_SET hregset;
+      CLEAR_HARD_REG_SET (hregset);
+      hardregs.quick_push (hregset);
+    }
+
   ret = GS_ALL_DONE;
   link_next = NULL_TREE;
   for (i = 0, link = ASM_OUTPUTS (expr); link; ++i, link = link_next)
@@ -6998,8 +7066,8 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (constraint_len == 0)
         continue;
 
-      ok = parse_output_constraint (&constraint, i, 0, 0,
-				    &allows_mem, &allows_reg, &is_inout);
+      ok = parse_output_constraint (&constraint, i, 0, 0, &allows_mem,
+				    &allows_reg, &is_inout, &hardregs);
       if (!ok)
 	{
 	  ret = GS_ERROR;
@@ -7062,6 +7130,24 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	      TREE_VALUE (link) = tem;
 	      tret = GS_OK;
 	    }
+	  if (VAR_P (op) && DECL_HARD_REGISTER (op))
+	    {
+	      tree id = DECL_ASSEMBLER_NAME (op);
+	      const char *asmspec = IDENTIFIER_POINTER (id) + 1;
+	      int hardreg = decode_reg_name (asmspec);
+	      if (hardreg >= 0)
+		{
+		  if (TEST_HARD_REG_BIT (hardregs[0], hardreg)
+		      || TEST_HARD_REG_BIT (hardregs[1], hardreg))
+		    {
+		      error ("multiple outputs to hard register: %s",
+			     reg_names[hardreg]);
+		      return GS_ERROR;
+		    }
+		  else
+		    SET_HARD_REG_BIT (hardregs[0], hardreg);
+		}
+	    }
 	}
 
       vec_safe_push (outputs, link);
@@ -7161,13 +7247,16 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	}
     }
 
+  for (unsigned int i = 0; i < hardregs.length (); ++i)
+    CLEAR_HARD_REG_SET (hardregs[i]);
+
   link_next = NULL_TREE;
   for (link = ASM_INPUTS (expr); link; ++i, link = link_next)
     {
       link_next = TREE_CHAIN (link);
       constraint = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (link)));
-      parse_input_constraint (&constraint, 0, 0, noutputs, 0,
-			      oconstraints, &allows_mem, &allows_reg);
+      parse_input_constraint (&constraint, 0, 0, noutputs, 0, oconstraints,
+			      &allows_mem, &allows_reg, &hardregs);
 
       /* If we can't make copies, we can only accept memory.  */
       tree intype = TREE_TYPE (TREE_VALUE (link));
@@ -7241,6 +7330,24 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 				is_gimple_asm_val, fb_rvalue);
 	  if (tret == GS_ERROR)
 	    ret = tret;
+	  tree inputv = TREE_VALUE (link);
+	  if (VAR_P (inputv) && DECL_HARD_REGISTER (inputv))
+	    {
+	      tree id = DECL_ASSEMBLER_NAME (inputv);
+	      const char *asmspec = IDENTIFIER_POINTER (id) + 1;
+	      int hardreg = decode_reg_name (asmspec);
+	      if (hardreg >= 0)
+		{
+		  if (TEST_HARD_REG_BIT (hardregs[1], hardreg))
+		    {
+		      error ("multiple inputs to hard register: %s",
+			     reg_names[hardreg]);
+		      return GS_ERROR;
+		    }
+		  else
+		    SET_HARD_REG_BIT (hardregs[0], hardreg);
+		}
+	    }
 	}
 
       TREE_CHAIN (link) = NULL_TREE;
diff --git a/gcc/lra-constraints.cc b/gcc/lra-constraints.cc
index e945a4da451..d81753eefaa 100644
--- a/gcc/lra-constraints.cc
+++ b/gcc/lra-constraints.cc
@@ -114,6 +114,7 @@ 
 #include "target.h"
 #include "rtl.h"
 #include "tree.h"
+#include "stmt.h"
 #include "predict.h"
 #include "df.h"
 #include "memmodel.h"
@@ -2165,6 +2166,7 @@  process_alt_operands (int only_alternative)
   bool costly_p;
   enum reg_class cl;
   const HARD_REG_SET *cl_filter;
+  HARD_REG_SET hregset;
 
   /* Calculate some data common for all alternatives to speed up the
      function.	*/
@@ -2536,6 +2538,21 @@  process_alt_operands (int only_alternative)
 		  cl_filter = nullptr;
 		  goto reg;
 
+		case '{':
+		    {
+		      /* Currently this form of constraint is only allowed in
+			 asm statements which are verified during gimplify,
+			 i.e., regno >= 0 holds for those.  genoutput fails on
+			 it.  For future proofness assert it.  */
+		      int regno = parse_constraint_regname (p);
+		      gcc_assert (regno >= 0);
+		      cl = REGNO_REG_CLASS (regno);
+		      CLEAR_HARD_REG_SET (hregset);
+		      SET_HARD_REG_BIT (hregset, regno);
+		      cl_filter = &hregset;
+		      goto reg;
+		    }
+
 		default:
 		  cn = lookup_constraint (p);
 		  switch (get_constraint_type (cn))
diff --git a/gcc/recog.cc b/gcc/recog.cc
index a6799e3f5e6..8a474cfb8a7 100644
--- a/gcc/recog.cc
+++ b/gcc/recog.cc
@@ -25,6 +25,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "target.h"
 #include "rtl.h"
 #include "tree.h"
+#include "stmt.h"
 #include "cfghooks.h"
 #include "df.h"
 #include "memmodel.h"
@@ -2296,10 +2297,11 @@  asm_operand_ok (rtx op, const char *constraint, const char **constraints)
 	  switch (get_constraint_type (cn))
 	    {
 	    case CT_REGISTER:
-	      if (!result
-		  && reg_class_for_constraint (cn) != NO_REGS
-		  && GET_MODE (op) != BLKmode
-		  && register_operand (op, VOIDmode))
+	      if ((!result
+		   && reg_class_for_constraint (cn) != NO_REGS
+		   && GET_MODE (op) != BLKmode
+		   && register_operand (op, VOIDmode))
+		  || constraint[0] == '{')
 		result = 1;
 	      break;
 
@@ -3231,6 +3233,10 @@  constrain_operands (int strict, alternative_mask alternatives)
 		  win = true;
 		break;
 
+	      case '{':
+		win = true;
+		break;
+
 	      default:
 		{
 		  enum constraint_num cn = lookup_constraint (p);
diff --git a/gcc/stmt.cc b/gcc/stmt.cc
index ae1527f0a19..1f20b09f90e 100644
--- a/gcc/stmt.cc
+++ b/gcc/stmt.cc
@@ -39,6 +39,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "emit-rtl.h"
 #include "pretty-print.h"
 #include "diagnostic-core.h"
+#include "output.h"
 
 #include "fold-const.h"
 #include "varasm.h"
@@ -174,6 +175,32 @@  expand_label (tree label)
     maybe_set_first_label_num (label_r);
 }
 
+/* Parse a hard register constraint and return its number or -1 in case of an
+   error.  BEGIN should point to a string of the form "{regname}".  For the
+   sake of simplicity assume that a register name is not longer than 31
+   characters, if not error out.  */
+
+int
+parse_constraint_regname (const char *begin)
+{
+  if (*begin != '{')
+    return -1;
+  ++begin;
+  const char *end = begin;
+  while (*end != '}' && *end != '\0')
+    ++end;
+  if (*end != '}' || end == begin)
+    return -1;
+  ptrdiff_t len = end - begin;
+  if (len >= 31)
+    return -1;
+  char regname[32];
+  memcpy (regname, begin, len);
+  regname[len] = '\0';
+  int regno = decode_reg_name (regname);
+  return regno;
+}
+
 /* Parse the output constraint pointed to by *CONSTRAINT_P.  It is the
    OPERAND_NUMth output operand, indexed from zero.  There are NINPUTS
    inputs and NOUTPUTS outputs to this extended-asm.  Upon return,
@@ -190,7 +217,8 @@  expand_label (tree label)
 bool
 parse_output_constraint (const char **constraint_p, int operand_num,
 			 int ninputs, int noutputs, bool *allows_mem,
-			 bool *allows_reg, bool *is_inout)
+			 bool *allows_reg, bool *is_inout,
+			 vec<HARD_REG_SET> *hardregs)
 {
   const char *constraint = *constraint_p;
   const char *p;
@@ -244,6 +272,8 @@  parse_output_constraint (const char **constraint_p, int operand_num,
       constraint = *constraint_p;
     }
 
+  unsigned int alternative = 2;
+
   /* Loop through the constraint string.  */
   for (p = constraint + 1; *p; )
     {
@@ -268,7 +298,11 @@  parse_output_constraint (const char **constraint_p, int operand_num,
 	case 'E':  case 'F':  case 'G':  case 'H':
 	case 's':  case 'i':  case 'n':
 	case 'I':  case 'J':  case 'K':  case 'L':  case 'M':
-	case 'N':  case 'O':  case 'P':  case ',':
+	case 'N':  case 'O':  case 'P':
+	  break;
+
+	case ',':
+	  ++alternative;
 	  break;
 
 	case '0':  case '1':  case '2':  case '3':  case '4':
@@ -289,6 +323,33 @@  parse_output_constraint (const char **constraint_p, int operand_num,
 	  *allows_mem = true;
 	  break;
 
+	case '{':
+	  {
+	    int regno = parse_constraint_regname (p);
+	    if (regno < 0)
+	      {
+		error ("invalid output constraint: %s", p);
+		return false;
+	      }
+	    if (hardregs)
+	      {
+		if (TEST_HARD_REG_BIT ((*hardregs)[0], regno)
+		    || TEST_HARD_REG_BIT ((*hardregs)[alternative], regno))
+		  {
+		    error ("multiple outputs to hard register: %s",
+			   reg_names[regno]);
+		    return false;
+		  }
+		else
+		  {
+		    SET_HARD_REG_BIT ((*hardregs)[1], regno);
+		    SET_HARD_REG_BIT ((*hardregs)[alternative], regno);
+		  }
+	      }
+	    *allows_reg = true;
+	    break;
+	  }
+
 	default:
 	  if (!ISALPHA (*p))
 	    break;
@@ -317,7 +378,8 @@  bool
 parse_input_constraint (const char **constraint_p, int input_num,
 			int ninputs, int noutputs, int ninout,
 			const char * const * constraints,
-			bool *allows_mem, bool *allows_reg)
+			bool *allows_mem, bool *allows_reg,
+			vec<HARD_REG_SET> *hardregs)
 {
   const char *constraint = *constraint_p;
   const char *orig_constraint = constraint;
@@ -332,6 +394,8 @@  parse_input_constraint (const char **constraint_p, int input_num,
 
   /* Make sure constraint has neither `=', `+', nor '&'.  */
 
+  unsigned int alternative = 2;
+
   for (j = 0; j < c_len; j += CONSTRAINT_LEN (constraint[j], constraint+j))
     switch (constraint[j])
       {
@@ -358,7 +422,11 @@  parse_input_constraint (const char **constraint_p, int input_num,
       case 'E':  case 'F':  case 'G':  case 'H':
       case 's':  case 'i':  case 'n':
       case 'I':  case 'J':  case 'K':  case 'L':  case 'M':
-      case 'N':  case 'O':  case 'P':  case ',':
+      case 'N':  case 'O':  case 'P':
+	break;
+
+      case ',':
+	++alternative;
 	break;
 
 	/* Whether or not a numeric constraint allows a register is
@@ -408,6 +476,32 @@  parse_input_constraint (const char **constraint_p, int input_num,
 	*allows_mem = true;
 	break;
 
+      case '{':
+	{
+	  int regno = parse_constraint_regname (constraint + j);
+	  if (regno < 0)
+	    {
+	      error ("invalid input constraint: %s", constraint + j);
+	      return false;
+	    }
+	  if (hardregs)
+	    {
+	      if (TEST_HARD_REG_BIT ((*hardregs)[0], regno)
+		  || TEST_HARD_REG_BIT ((*hardregs)[alternative], regno))
+		{
+		  error ("multiple inputs to hard register: %s",
+			    reg_names[regno]);
+		}
+	      else
+		{
+		  SET_HARD_REG_BIT ((*hardregs)[1], regno);
+		  SET_HARD_REG_BIT ((*hardregs)[alternative], regno);
+		}
+	    }
+	  *allows_reg = true;
+	  break;
+	}
+
       default:
 	if (! ISALPHA (constraint[j]))
 	  {
diff --git a/gcc/stmt.h b/gcc/stmt.h
index a2caae7121b..05889ff3798 100644
--- a/gcc/stmt.h
+++ b/gcc/stmt.h
@@ -20,11 +20,17 @@  along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_STMT_H
 #define GCC_STMT_H
 
+#include "target.h"
+#include "hard-reg-set.h"
+
 extern void expand_label (tree);
 extern bool parse_output_constraint (const char **, int, int, int,
-				     bool *, bool *, bool *);
+				     bool *, bool *, bool *,
+				     vec<HARD_REG_SET> * = nullptr);
 extern bool parse_input_constraint (const char **, int, int, int, int,
-				    const char * const *, bool *, bool *);
+				    const char * const *, bool *, bool *,
+				    vec<HARD_REG_SET> * = nullptr);
+extern int parse_constraint_regname (const char *);
 extern tree resolve_asm_operand_names (tree, tree, tree, tree);
 #ifdef HARD_CONST
 /* Silly ifdef to avoid having all includers depend on hard-reg-set.h.  */
diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c
new file mode 100644
index 00000000000..53895d98663
--- /dev/null
+++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c
@@ -0,0 +1,103 @@ 
+/* { dg-do compile { target { lp64 } } } */
+/* { dg-options "-O2 -march=z13" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/*
+** test_in_1:
+**     foo	%r2
+**     br	%r14
+*/
+
+int
+test_in_1 (int x)
+{
+  asm ("foo	%0" :: "{r2}" (x));
+  return x;
+}
+
+/*
+** test_in_2:
+**     lgr	(%r[0-9]+),%r2
+**     lhi	%r2,42
+**     foo	%r2
+**     lgr	%r2,\1
+**     br	%r14
+*/
+
+int
+test_in_2 (int x)
+{
+  asm ("foo	%0" :: "{r2}" (42));
+  return x;
+}
+
+/*
+** test_in_3:
+**     stmg	%r12,%r15,96\(%r15\)
+**     lay	%r15,-160\(%r15\)
+**     lgr	(%r[0-9]+),%r2
+**     ahi	%r2,1
+**     lgfr	%r2,%r2
+**     brasl	%r14,foo@PLT
+**     lr	%r3,%r2
+**     lr	%r2,\1
+**     foo	%r3,%r2
+**     lgr	%r2,\1
+**     lmg	%r12,%r15,256\(%r15\)
+**     br	%r14
+*/
+
+extern int foo (int);
+
+int
+test_in_3 (int x)
+{
+  asm ("foo	%0,%1\n" :: "{r3}" (foo (x + 1)), "{r2}" (x));
+  return x;
+}
+
+/*
+** test_out_1:
+**     foo	%r3
+**     lgfr	%r2,%r3
+**     br	%r14
+*/
+
+int
+test_out_1 (void)
+{
+  int x;
+  asm ("foo	%0" : "={r3}" (x));
+  return x;
+}
+
+/*
+** test_out_2:
+**     lgr	(%r[0-9]+),%r2
+**     foo	%r2
+**     ark	(%r[0-9]+),\1,%r2
+**     lgfr	%r2,\2
+**     br	%r14
+*/
+
+int
+test_out_2 (int x)
+{
+  int y;
+  asm ("foo	%0" : "={r2}" (y));
+  return x + y;
+}
+
+/*
+** test_inout_1:
+**     foo	%r2
+**     lgfr	%r2,%r2
+**     br	%r14
+*/
+
+int
+test_inout_1 (int x)
+{
+  asm ("foo	%0" : "+{r2}" (x));
+  return x;
+}
diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
new file mode 100644
index 00000000000..9f3c221b937
--- /dev/null
+++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
@@ -0,0 +1,29 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/*
+** test_1:
+**     lr	%r5,%r2
+**     foo	%r5,%r3
+**     br	%r14
+*/
+
+void
+test_1 (int x, int *y)
+{
+  asm ("foo	%0,%1" :: "m{r4},{r5}" (x), "m,r" (y));
+}
+
+/*
+** test_2:
+**     lr	%r4,%r2
+**     foo	%r4,0\(%r3\)
+**     br	%r14
+*/
+
+void
+test_2 (int x, int *y)
+{
+  asm ("foo	%0,%1" :: "m{r4},{r5}" (x), "m,r" (*y));
+}
diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c
new file mode 100644
index 00000000000..0edcdd3cfde
--- /dev/null
+++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c
@@ -0,0 +1,24 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+void
+test (void)
+{
+  int x, y;
+  register int r4 asm ("r4") = 0;
+
+  asm ("" :: "{}" (42)); /* { dg-error "invalid input constraint: \{\}" } */
+  asm ("" :: "{r4" (42)); /* { dg-error "invalid input constraint: \{r4" } */
+  asm ("" :: "{r17}" (42)); /* { dg-error "invalid input constraint: \{r17\}" } */
+
+  asm ("" :: "r" (r4), "{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" :: "{r4}" (42), "r" (r4)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" :: "{r4}" (42), "{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" :: "{r2},{r4}" (42), "{r4},{r3}" (42));
+  asm ("" :: "{r2},{r4}" (42), "{r3},{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" :: "{r4},{r2}" (42), "{r4},{r3}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" :: "{r3}{r4}" (42), "{r4}" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" : "+{r4}" (x), "={r4}" (y)); /* { dg-error "multiple outputs to hard register: %r4" } */
+  asm ("" : "={r4}" (y) : "{r4}" (42), "0" (42)); /* { dg-error "multiple inputs to hard register: %r4" } */
+  asm ("" : "+{r4}" (x) : "{r4}" (y)); /* { dg-error "multiple inputs to hard register: %r4" } */
+}
diff --git a/gcc/testsuite/lib/scanasm.exp b/gcc/testsuite/lib/scanasm.exp
index 6cf9997240d..d09372096a5 100644
--- a/gcc/testsuite/lib/scanasm.exp
+++ b/gcc/testsuite/lib/scanasm.exp
@@ -896,6 +896,10 @@  proc configure_check-function-bodies { config } {
 	set up_config(fluff) {^\s*(?://)}
     } elseif { [istarget *-*-darwin*] } {
 	set up_config(fluff) {^\s*(?:\.|//|@)|^L[0-9ABCESV]}
+    } elseif { [istarget s390*-*-*] } {
+	# Additionally to the defaults skip lines beginning with a # resulting
+	# from inline asm.
+	set up_config(fluff) {^\s*(?:\.|//|@|$|#)}
     } else {
 	# Skip lines beginning with labels ('.L[...]:') or other directives
 	# ('.align', '.cfi_startproc', '.quad [...]', '.text', etc.), '//' or