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
