diff --git a/gcc/cfgexpand.cc b/gcc/cfgexpand.cc
index 13f8c08d295..fdbbd93f1b5 100644
--- a/gcc/cfgexpand.cc
+++ b/gcc/cfgexpand.cc
@@ -2966,44 +2966,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.  */
@@ -3169,10 +3131,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/gimplify.cc b/gcc/gimplify.cc
index 26a216e151d..08e0b5d047b 100644
--- a/gcc/gimplify.cc
+++ b/gcc/gimplify.cc
@@ -70,6 +70,10 @@ 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"
+#include "gimplify_reg_info.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
@@ -7009,6 +7013,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.  */
 
@@ -7039,6 +7079,20 @@ 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);
+
+  gimplify_reg_info reg_info (num_alternatives, noutputs);
+
   ret = GS_ALL_DONE;
   link_next = NULL_TREE;
   for (i = 0, link = ASM_OUTPUTS (expr); link; ++i, link = link_next)
@@ -7055,8 +7109,9 @@ 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);
+      reg_info.operand = TREE_VALUE (link);
+      ok = parse_output_constraint (&constraint, i, 0, 0, &allows_mem,
+				    &allows_reg, &is_inout, &reg_info);
       if (!ok)
 	{
 	  ret = GS_ERROR;
@@ -7227,12 +7282,20 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
     }
 
   link_next = NULL_TREE;
-  for (link = ASM_INPUTS (expr); link; ++i, link = link_next)
+  int input_num = 0;
+  for (link = ASM_INPUTS (expr); link; ++input_num, ++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);
+      reg_info.operand = TREE_VALUE (link);
+      bool ok = parse_input_constraint (&constraint, input_num, 0, noutputs, 0,
+					oconstraints, &allows_mem, &allows_reg,
+					&reg_info);
+      if (!ok)
+	{
+	  ret = GS_ERROR;
+	  is_inout = false;
+	}
 
       /* If we can't make copies, we can only accept memory.  */
       tree intype = TREE_TYPE (TREE_VALUE (link));
diff --git a/gcc/gimplify_reg_info.h b/gcc/gimplify_reg_info.h
new file mode 100644
index 00000000000..d3a7572b435
--- /dev/null
+++ b/gcc/gimplify_reg_info.h
@@ -0,0 +1,130 @@
+/* gimplify_reg_info is used during gimplification while walking over
+   operands and their corresponding constraints of asm statements in order to
+   detect errors.
+
+   m_outputs is a mapping between output operands and their potentially used
+   registers, i.e., it describes the union of all register usages over all
+   alternatives and in case of a register asm operand also this one.
+
+   m_alt_out is a mapping describing which registers are potentially used
+   in which alternative over all outputs.  Similarly for m_alt_in but over all
+   inputs.
+
+   m_early_clobbered_in_alt is a mapping describing which register is early
+   clobbered in which alternative over all outputs.
+
+   m_early_clobbered_in_output is the counter part to the prior one, i.e., it
+   is a mapping describing which register is early clobbered in which operand
+   over all alternatives.  */
+
+#include "regs.h"
+
+class gimplify_reg_info
+{
+  HARD_REG_SET *m_buf;
+  HARD_REG_SET *m_outputs;
+  HARD_REG_SET *m_alt_out;
+  HARD_REG_SET *m_alt_in;
+  HARD_REG_SET *m_early_clobbered_in_alt;
+  HARD_REG_SET *m_early_clobbered_in_output;
+  const unsigned m_num_alternatives;
+  const unsigned m_num_outputs;
+
+public:
+  tree operand;
+
+  gimplify_reg_info (unsigned num_alternatives, unsigned num_outputs)
+    : m_num_alternatives{num_alternatives}, m_num_outputs{num_outputs}
+  {
+    /* If there are no alternatives, then there are no outputs/inputs and there
+       is nothing to do on our end.  Thus, we are dealing with a basic asm.  */
+    if (num_alternatives == 0)
+      return;
+
+    unsigned buf_size = num_alternatives * 3 + num_outputs * 2;
+    m_buf = new HARD_REG_SET[buf_size];
+    for (unsigned i = 0; i < buf_size; ++i)
+      CLEAR_HARD_REG_SET (m_buf[i]);
+    m_alt_out = &m_buf[0];
+    m_alt_in = &m_buf[num_alternatives];
+    m_early_clobbered_in_alt = &m_buf[num_alternatives * 2];
+    if (num_outputs > 0)
+      {
+	m_outputs = &m_buf[num_alternatives * 3];
+	m_early_clobbered_in_output = &m_buf[num_alternatives * 3 + num_outputs];
+      }
+    else
+      {
+	m_outputs = nullptr;
+	m_early_clobbered_in_output = nullptr;
+      }
+  }
+
+  ~gimplify_reg_info ()
+  {
+    if (m_num_alternatives > 0)
+      delete[] m_buf;
+  }
+
+  void set_out (unsigned alt, unsigned output, int regno)
+  {
+    machine_mode mode = TYPE_MODE (TREE_TYPE (operand));
+    add_to_hard_reg_set (&m_alt_out[alt], mode, regno);
+    add_to_hard_reg_set (&m_outputs[output], mode, regno);
+  }
+
+  void set_in (unsigned alt, int regno)
+  {
+    machine_mode mode = TYPE_MODE (TREE_TYPE (operand));
+    add_to_hard_reg_set (&m_alt_in[alt], mode, regno);
+  }
+
+  bool used_in_alt_out (unsigned alt, int regno) const
+  {
+    gcc_checking_assert (alt < m_num_alternatives);
+    return TEST_HARD_REG_BIT (m_alt_out[alt], regno);
+  }
+
+  bool used_in_alt_in (unsigned alt, int regno) const
+  {
+    gcc_checking_assert (alt < m_num_alternatives);
+    return TEST_HARD_REG_BIT (m_alt_in[alt], regno);
+  }
+
+  void set_early_clobbered (unsigned alt, unsigned output, int regno) const
+  {
+    gcc_checking_assert (alt < m_num_alternatives);
+    gcc_checking_assert (output < m_num_outputs);
+    machine_mode mode = TYPE_MODE (TREE_TYPE (operand));
+    add_to_hard_reg_set (&m_early_clobbered_in_alt[alt], mode, regno);
+    add_to_hard_reg_set (&m_early_clobbered_in_output[output], mode, regno);
+  }
+
+  bool is_early_clobbered_in_alt (unsigned alt, int regno) const
+  {
+    gcc_checking_assert (alt < m_num_alternatives);
+    return TEST_HARD_REG_BIT (m_early_clobbered_in_alt[alt], regno);
+  }
+
+  bool is_early_clobbered_in_any_output_unequal (unsigned operand,
+						 int regno) const
+  {
+    gcc_checking_assert (operand < m_num_outputs);
+    for (unsigned op = 0; op < m_num_outputs; ++op)
+      if (op != operand
+	  && TEST_HARD_REG_BIT (m_early_clobbered_in_output[op], regno))
+	return true;
+    return false;
+  }
+
+  bool output_coincides (unsigned output, int regno) const
+  {
+    gcc_checking_assert (output < m_num_outputs);
+    HARD_REG_SET singleton;
+    CLEAR_HARD_REG_SET (singleton);
+    machine_mode mode = TYPE_MODE (TREE_TYPE (operand));
+    add_to_hard_reg_set (&singleton, mode, regno);
+    return hard_reg_set_empty_p (m_outputs[output])
+	   || m_outputs[output] == singleton;
+  }
+};
diff --git a/gcc/stmt.cc b/gcc/stmt.cc
index 915969ee116..06d95af794a 100644
--- a/gcc/stmt.cc
+++ b/gcc/stmt.cc
@@ -39,6 +39,8 @@ 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 "gimplify_reg_info.h"
 
 #include "fold-const.h"
 #include "varasm.h"
@@ -201,6 +203,51 @@ decode_hard_reg_constraint (const char *begin)
   return regno;
 }
 
+static bool
+eliminable_regno_p (int regnum)
+{
+  static const struct
+  {
+    const int from;
+    const int to;
+  } eliminables[] = ELIMINABLE_REGS;
+  for (size_t i = 0; i < ARRAY_SIZE (eliminables); i++)
+    if (regnum == eliminables[i].from)
+      return true;
+  return false;
+}
+
+/* Perform a similar check as done in make_decl_rtl().  */
+
+static bool
+hardreg_ok_p (int reg_number, machine_mode mode, int operand_num)
+{
+  if (mode == BLKmode)
+    error ("data type isn%'t suitable for register %s of operand %i",
+	   reg_names[reg_number], operand_num);
+  else if (!in_hard_reg_set_p (accessible_reg_set, mode, reg_number))
+    error ("register %s for operand %i cannot be accessed"
+	   " by the current target", reg_names[reg_number], operand_num);
+  else if (!in_hard_reg_set_p (operand_reg_set, mode, reg_number))
+    error ("register %s for operand %i is not general enough"
+	   " to be used as a register variable", reg_names[reg_number], operand_num);
+  else if (!targetm.hard_regno_mode_ok (reg_number, mode))
+    error ("register %s for operand %i isn%'t suitable for data type",
+	   reg_names[reg_number], operand_num);
+  else if (reg_number != HARD_FRAME_POINTER_REGNUM
+	   && (reg_number == FRAME_POINTER_REGNUM
+#ifdef RETURN_ADDRESS_POINTER_REGNUM
+	       || reg_number == RETURN_ADDRESS_POINTER_REGNUM
+#endif
+	       || reg_number == ARG_POINTER_REGNUM)
+	   && eliminable_regno_p (reg_number))
+    error ("register for operand %i is an internal GCC "
+	   "implementation detail", operand_num);
+  else
+    return true;
+  return false;
+}
+
 /* 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,
@@ -217,7 +264,8 @@ decode_hard_reg_constraint (const char *begin)
 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,
+			 gimplify_reg_info *reg_info)
 {
   const char *constraint = *constraint_p;
   const char *p;
@@ -271,6 +319,9 @@ parse_output_constraint (const char **constraint_p, int operand_num,
       constraint = *constraint_p;
     }
 
+  unsigned int alt = 0;
+  bool early_clobbered = false;
+
   /* Loop through the constraint string.  */
   for (p = constraint + 1; *p; )
     {
@@ -290,12 +341,21 @@ parse_output_constraint (const char **constraint_p, int operand_num,
 	    }
 	  break;
 
-	case '?':  case '!':  case '*':  case '&':  case '#':
+	case '&':
+	  early_clobbered = true;
+	  break;
+
+	case '?':  case '!':  case '*':  case '#':
 	case '$':  case '^':
 	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 ',':
+	  ++alt;
+	  early_clobbered = false;
 	  break;
 
 	case '0':  case '1':  case '2':  case '3':  case '4':
@@ -318,6 +378,47 @@ parse_output_constraint (const char **constraint_p, int operand_num,
 
 	case '{':
 	  {
+	    if (!targetm.lra_p ())
+	      {
+		error ("hard register constraints are only supported while using LRA");
+		return false;
+	      }
+	    if (reg_info)
+	      {
+		int regno = decode_hard_reg_constraint (p);
+		if (regno < 0)
+		  {
+		    error ("invalid output constraint: %s", p);
+		    return false;
+		  }
+		if (reg_info->used_in_alt_out (alt, regno))
+		  {
+		    error ("multiple outputs to hard register: %s",
+			   reg_names[regno]);
+		    return false;
+		  }
+		else
+		  {
+		    reg_info->set_out (alt, operand_num, regno);
+		    if (early_clobbered)
+		      reg_info->set_early_clobbered (alt, operand_num, regno);
+		  }
+		if (VAR_P (reg_info->operand)
+		    && DECL_HARD_REGISTER (reg_info->operand))
+		  {
+		      tree id = DECL_ASSEMBLER_NAME (reg_info->operand);
+		      const char *asmspec = IDENTIFIER_POINTER (id) + 1;
+		      int regno_op = decode_reg_name (asmspec);
+		      if (regno != regno_op)
+			{
+			  error ("constraint and register %<asm%> for output operand %i are unsatisfiable", operand_num);
+			  return false;
+			}
+		  }
+		machine_mode mode = TYPE_MODE (TREE_TYPE (reg_info->operand));
+		if (!hardreg_ok_p (regno, mode, operand_num))
+		  return false;
+	      }
 	    *allows_reg = true;
 	    break;
 	  }
@@ -333,6 +434,34 @@ parse_output_constraint (const char **constraint_p, int operand_num,
 	    *allows_mem = true;
 	  else
 	    insn_extra_constraint_allows_reg_mem (cn, allows_reg, allows_mem);
+	  if (reg_info && *allows_reg
+	      && VAR_P (reg_info->operand)
+	      && DECL_HARD_REGISTER (reg_info->operand))
+	    {
+		tree id = DECL_ASSEMBLER_NAME (reg_info->operand);
+		const char *asmspec = IDENTIFIER_POINTER (id) + 1;
+		int regno = decode_reg_name (asmspec);
+		if (regno < 0)
+		  {
+		    error ("invalid register name for %q+D", reg_info->operand);
+		    return false;
+		  }
+		if (reg_info->used_in_alt_out (alt, regno))
+		  {
+		    error ("multiple outputs to hard register: %s",
+			   reg_names[regno]);
+		    return false;
+		  }
+		else
+		  {
+		    reg_info->set_out (alt, operand_num, regno);
+		    if (early_clobbered)
+		      reg_info->set_early_clobbered (alt, operand_num, regno);
+		  }
+		machine_mode mode = TYPE_MODE (TREE_TYPE (reg_info->operand));
+		if (!hardreg_ok_p (regno, mode, operand_num))
+		  return false;
+	    }
 	  break;
 	}
 
@@ -350,7 +479,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,
+			gimplify_reg_info *reg_info)
 {
   const char *constraint = *constraint_p;
   const char *orig_constraint = constraint;
@@ -365,6 +495,9 @@ parse_input_constraint (const char **constraint_p, int input_num,
 
   /* Make sure constraint has neither `=', `+', nor '&'.  */
 
+  unsigned int alt = 0;
+  unsigned long match = 0;
+
   for (j = 0; j < c_len; j += CONSTRAINT_LEN (constraint[j], constraint+j))
     switch (constraint[j])
       {
@@ -391,7 +524,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 ',':
+	++alt;
 	break;
 
 	/* Whether or not a numeric constraint allows a register is
@@ -403,7 +540,6 @@ parse_input_constraint (const char **constraint_p, int input_num,
       case '5':  case '6':  case '7':  case '8':  case '9':
 	{
 	  char *end;
-	  unsigned long match;
 
 	  saw_match = true;
 
@@ -443,6 +579,49 @@ parse_input_constraint (const char **constraint_p, int input_num,
 
       case '{':
 	{
+	  if (!targetm.lra_p ())
+	    {
+	      error ("hard register constraints are only supported while using LRA");
+	      return false;
+	    }
+	  if (reg_info)
+	    {
+	      int regno = decode_hard_reg_constraint (constraint + j);
+	      if (regno < 0)
+		{
+		  error ("invalid input constraint: %s", constraint + j);
+		  return false;
+		}
+	      if (reg_info->used_in_alt_in (alt, regno))
+		{
+		  error ("multiple inputs to hard register: %s",
+			    reg_names[regno]);
+		  return false;
+		}
+	      else
+		reg_info->set_in (alt, regno);
+	      if (constraint == orig_constraint
+		  && reg_info->is_early_clobbered_in_alt (alt, regno))
+		{
+		  error ("invalid hard register usage between earlyclobber operand and input operand");
+		  return false;
+		}
+	      if (VAR_P (reg_info->operand)
+		  && DECL_HARD_REGISTER (reg_info->operand))
+		{
+		    tree id = DECL_ASSEMBLER_NAME (reg_info->operand);
+		    const char *asmspec = IDENTIFIER_POINTER (id) + 1;
+		    int regno_op = decode_reg_name (asmspec);
+		    if (regno != regno_op)
+		      {
+			error ("constraint and register %<asm%> for input operand %i are unsatisfiable", input_num);
+			return false;
+		      }
+		}
+	      machine_mode mode = TYPE_MODE (TREE_TYPE (reg_info->operand));
+	      if (!hardreg_ok_p (regno, mode, input_num))
+		return false;
+	    }
 	  *allows_reg = true;
 	  break;
 	}
@@ -463,6 +642,44 @@ parse_input_constraint (const char **constraint_p, int input_num,
 	  *allows_mem = true;
 	else
 	  insn_extra_constraint_allows_reg_mem (cn, allows_reg, allows_mem);
+	if (reg_info && *allows_reg
+	    && VAR_P (reg_info->operand)
+	    && DECL_HARD_REGISTER (reg_info->operand))
+	  {
+	      tree id = DECL_ASSEMBLER_NAME (reg_info->operand);
+	      const char *asmspec = IDENTIFIER_POINTER (id) + 1;
+	      int regno = decode_reg_name (asmspec);
+	      if (regno < 0)
+		{
+		  error ("invalid register name for %q+D", reg_info->operand);
+		  return false;
+		}
+	      if (reg_info->used_in_alt_in (alt, regno))
+		{
+		  error ("multiple inputs to hard register: %s",
+			 reg_names[regno]);
+		  return false;
+		}
+	      else
+		reg_info->set_in (alt, regno);
+	      if ((constraint == orig_constraint
+		   && reg_info->is_early_clobbered_in_alt (alt, regno))
+		  || (constraint != orig_constraint
+		      && reg_info->is_early_clobbered_in_any_output_unequal
+			  (match, regno)))
+		{
+		  error ("invalid hard register usage between earlyclobber operand and input operand");
+		  return false;
+		}
+	      if (saw_match && !reg_info->output_coincides (match, regno))
+		{
+		  error ("invalid hard register usage between output operand and matching constraint operand");
+		  return false;
+		}
+	      machine_mode mode = TYPE_MODE (TREE_TYPE (reg_info->operand));
+	      if (!hardreg_ok_p (regno, mode, input_num))
+		return false;
+	  }
 	break;
       }
 
diff --git a/gcc/stmt.h b/gcc/stmt.h
index 7d79d682645..21116411d71 100644
--- a/gcc/stmt.h
+++ b/gcc/stmt.h
@@ -20,11 +20,15 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_STMT_H
 #define GCC_STMT_H
 
+class gimplify_reg_info;
+
 extern void expand_label (tree);
 extern bool parse_output_constraint (const char **, int, int, int,
-				     bool *, bool *, bool *);
+				     bool *, bool *, bool *,
+				     gimplify_reg_info * = nullptr);
 extern bool parse_input_constraint (const char **, int, int, int, int,
-				    const char * const *, bool *, bool *);
+				    const char * const *, bool *, bool *,
+				    gimplify_reg_info * = nullptr);
 extern int decode_hard_reg_constraint (const char *);
 extern tree resolve_asm_operand_names (tree, tree, tree, tree);
 #ifdef HARD_CONST
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c
new file mode 100644
index 00000000000..0d7c2f210d8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c
@@ -0,0 +1,83 @@
+/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */
+
+#if defined (__aarch64__)
+# define GPR1_RAW "x0"
+# define GPR2 "{x1}"
+# define GPR3 "{x2}"
+# define INVALID_GPR_A "{x31}"
+#elif defined (__arm__)
+# define GPR1_RAW "r0"
+# define GPR2 "{r1}"
+# define GPR3 "{r2}"
+# define INVALID_GPR_A "{r16}"
+#elif defined (__i386__)
+# define GPR1_RAW "%eax"
+# define GPR2 "{%ebx}"
+# define GPR3 "{%edx}"
+# define INVALID_GPR_A "{%eex}"
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define GPR1_RAW "r4"
+# define GPR2 "{r5}"
+# define GPR3 "{r6}"
+# define INVALID_GPR_A "{r33}"
+#elif defined (__riscv)
+# define GPR1_RAW "t4"
+# define GPR2 "{t5}"
+# define GPR3 "{t6}"
+# define INVALID_GPR_A "{t7}"
+#elif defined (__s390__)
+# define GPR1_RAW "r4"
+# define GPR2 "{r5}"
+# define GPR3 "{r6}"
+# define INVALID_GPR_A "{r17}"
+#elif defined (__x86_64__)
+# define GPR1_RAW "rax"
+# define GPR2 "{rbx}"
+# define GPR3 "{rcx}"
+# define INVALID_GPR_A "{rex}"
+#endif
+
+#define GPR1 "{"GPR1_RAW"}"
+#define INVALID_GPR_B "{"GPR1_RAW
+
+struct { int a[128]; } s = {0};
+
+void
+test (void)
+{
+  int x, y;
+  register int gpr1 __asm__ (GPR1_RAW) = 0;
+
+  __asm__ ("" :: "{}" (42)); /* { dg-error "invalid input constraint: \{\}" } */
+  __asm__ ("" :: INVALID_GPR_A (42)); /* { dg-error "invalid input constraint" } */
+  __asm__ ("" :: INVALID_GPR_B (42)); /* { dg-error "invalid input constraint" } */
+
+  __asm__ ("" :: GPR1 (s)); /* { dg-error "data type isn't suitable for register .* of operand 0" } */
+
+  __asm__ ("" :: "r" (gpr1), GPR1 (42)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" :: GPR1 (42), "r" (gpr1)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" :: GPR1 (42), GPR1 (42)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" :: GPR1","GPR2 (42), GPR2","GPR3 (42));
+  __asm__ ("" :: GPR1","GPR2 (42), GPR3","GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" :: GPR1","GPR2 (42), GPR1","GPR3 (42)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" :: GPR1 GPR2 (42), GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" : "+"GPR1 (x), "="GPR1 (y)); /* { dg-error "multiple outputs to hard register" } */
+  __asm__ ("" : "="GPR1 (y) : GPR1 (42), "0" (42)); /* { dg-error "multiple inputs to hard register" } */
+  __asm__ ("" : "+"GPR1 (x) : GPR1 (42)); /* { dg-error "multiple inputs to hard register" } */
+
+  __asm__ ("" : "="GPR1 (gpr1));
+  __asm__ ("" : "="GPR2 (gpr1)); /* { dg-error "constraint and register 'asm' for output operand 0 are unsatisfiable" } */
+  __asm__ ("" :: GPR2 (gpr1)); /* { dg-error "constraint and register 'asm' for input operand 0 are unsatisfiable" } */
+  __asm__ ("" : "="GPR1 (x) : "0" (gpr1));
+  __asm__ ("" : "="GPR1 GPR2 (x) : "0" (gpr1)); /* { dg-error "constraint and register 'asm' for input operand 0 are unsatisfiable" } */
+
+  __asm__ ("" : "=&"GPR1 (x) : "0" (gpr1));
+  __asm__ ("" : "=&"GPR1 (x) : "0" (42));
+  __asm__ ("" : "=&"GPR2","GPR1 (x) : "r,"GPR1 (42));
+  __asm__ ("" : "="GPR2",&"GPR1 (x) : "r,"GPR1 (42)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+  __asm__ ("" : "=&"GPR1 (x) : GPR1 (42)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+  __asm__ ("" : "=&"GPR2","GPR1 (x) : "r,r" (gpr1));
+  __asm__ ("" : "="GPR2",&"GPR1 (x) : "r,r" (gpr1)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+  __asm__ ("" : "=&r" (gpr1) : GPR1 (42)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+  __asm__ ("" : "=&"GPR1 (x),  "=r" (y) : "1" (gpr1)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c
new file mode 100644
index 00000000000..87c80786340
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c
@@ -0,0 +1,20 @@
+/* { dg-do compile { target { { aarch64*-*-* s390x-*-* } && int128 } } } */
+/* { dg-options "-O2" } get rid of -ansi since we use __int128 */
+
+/* Test register pairs.  */
+
+#if defined (__aarch64__)
+# define GPR1 "{x4}"
+# define GPR2 "{x5}"
+#elif defined (__s390__)
+# define GPR1 "{r4}"
+# define GPR2 "{r5}"
+#endif
+
+void
+test (void)
+{
+  __asm__ ("" :: GPR1 ((__int128) 42));
+  __asm__ ("" :: GPR2 ((__int128) 42)); /* { dg-error "register .* for operand 0 isn't suitable for data type" } */
+  __asm__ ("" :: GPR1 ((__int128) 42), GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c b/gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c
new file mode 100644
index 00000000000..c44d9f83186
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c
@@ -0,0 +1,21 @@
+/* { dg-do compile { target arm*-*-* s390-*-* } } */
+/* { dg-options "-std=c99" } we need long long */
+/* { dg-additional-options "-march=armv8-a" { target arm*-*-* } } */
+
+/* Test register pairs.  */
+
+#if defined (__arm__)
+# define GPR1 "{r4}"
+# define GPR2 "{r5}"
+#elif defined (__s390__)
+# define GPR1 "{r4}"
+# define GPR2 "{r5}"
+#endif
+
+void
+test (void)
+{
+  __asm__ ("" :: GPR1 (42ll));
+  __asm__ ("" :: GPR2 (42ll)); /* { dg-error "register .* for operand 0 isn't suitable for data type" } */
+  __asm__ ("" :: GPR1 (42ll), GPR2 (42)); /* { dg-error "multiple inputs to hard register" } */
+}
diff --git a/gcc/testsuite/gcc.dg/pr87600-2.c b/gcc/testsuite/gcc.dg/pr87600-2.c
index e8a9f194b73..860d3f965ef 100644
--- a/gcc/testsuite/gcc.dg/pr87600-2.c
+++ b/gcc/testsuite/gcc.dg/pr87600-2.c
@@ -11,34 +11,6 @@ test0 (void)
 {
   register long var1 asm (REG1);
   register long var2 asm (REG1);
-  asm ("blah %0 %1" : "=r" (var1), "=r" (var2)); /* { dg-error "invalid hard register usage between output operands" } */
+  asm ("blah %0 %1" : "=r" (var1), "=r" (var2)); /* { dg-error "multiple outputs to hard register" } */
   return var1;
 }
-
-long
-test1 (void)
-{
-  register long var1 asm (REG1);
-  register long var2 asm (REG2);
-  asm ("blah %0 %1" : "=r" (var1) : "0" (var2)); /* { dg-error "invalid hard register usage between output operand and matching constraint operand" } */
-  return var1;
-}
-
-long
-test2 (void)
-{
-  register long var1 asm (REG1);
-  register long var2 asm (REG1);
-  asm ("blah %0 %1" : "=&r" (var1) : "r" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
-  return var1;
-}
-
-long
-test3 (void)
-{
-  register long var1 asm (REG1);
-  register long var2 asm (REG1);
-  long var3;
-  asm ("blah %0 %1" : "=&r" (var1), "=r" (var3) : "1" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
-  return var1 + var3;
-}
diff --git a/gcc/testsuite/gcc.dg/pr87600-3.c b/gcc/testsuite/gcc.dg/pr87600-3.c
new file mode 100644
index 00000000000..2673d004130
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr87600-3.c
@@ -0,0 +1,35 @@
+/* PR rtl-optimization/87600  */
+/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* s390*-*-* x86_64-*-* } } */
+/* { dg-options "-O2" } */
+
+#include "pr87600.h"
+
+/* The following are all invalid uses of local register variables.  */
+
+long
+test1 (void)
+{
+  register long var1 asm (REG1);
+  register long var2 asm (REG2);
+  asm ("blah %0 %1" : "=r" (var1) : "0" (var2)); /* { dg-error "invalid hard register usage between output operand and matching constraint operand" } */
+  return var1;
+}
+
+long
+test2 (void)
+{
+  register long var1 asm (REG1);
+  register long var2 asm (REG1);
+  asm ("blah %0 %1" : "=&r" (var1) : "r" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+  return var1;
+}
+
+long
+test3 (void)
+{
+  register long var1 asm (REG1);
+  register long var2 asm (REG1);
+  long var3;
+  asm ("blah %0 %1" : "=&r" (var1), "=r" (var3) : "1" (var2)); /* { dg-error "invalid hard register usage between earlyclobber operand and input operand" } */
+  return var1 + var3;
+}
diff --git a/gcc/testsuite/gcc.dg/pr87600-3.s b/gcc/testsuite/gcc.dg/pr87600-3.s
new file mode 100644
index 00000000000..e69de29bb2d
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..671c0ede6ef
--- /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 -mzarch" } */
+/* { 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
+**     lr	%r2,%r3
+**     foo	%r2
+**     lgr	%r2,\1
+**     br	%r14
+*/
+
+int
+test_in_2 (int x, int y)
+{
+  asm ("foo	%0" :: "{r2}" (y));
+  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..a892fe8f0aa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c
@@ -0,0 +1,43 @@
+/* { dg-do compile { target { lp64 } } } */
+/* { dg-options "-O2 -march=z13 -mzarch" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+/* { dg-final { scan-assembler {\.LC0:\n\t\.long\t1078523331\n} } } */
+
+
+/*
+** test_float_into_gpr:
+**     lrl	%r4,.LC0
+**     foo	%r4
+**     br	%r14
+*/
+
+void
+test_float_into_gpr (void)
+{
+  // This is the counterpart to
+  //   register float x asm ("r4") = 3.14f;
+  //   asm ("foo	%0" :: "r" (x));
+  // where the bit-pattern of 3.14f is loaded into GPR.
+  asm ("foo	%0" :: "{r4}" (3.14f));
+}
+
+/*
+** test_float:
+** (
+**     ldr	%f4,%f0
+**     ldr	%f5,%f2
+** |
+**     ldr	%f5,%f2
+**     ldr	%f4,%f0
+** )
+**     aebr	%f5,%f4
+**     ldr	%f0,%f5
+**     br	%r14
+*/
+
+float
+test_float (float x, float y)
+{
+  asm ("aebr	%0,%1" : "+{f5}" (y) : "{f4}" (x));
+  return 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..5df37b5b717
--- /dev/null
+++ b/gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c
@@ -0,0 +1,42 @@
+/* { dg-do compile { target lp64 } } */
+/* { dg-options "-O2 -march=z13 -mzarch" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+/* { dg-final { scan-assembler {\.LC0:\n\t\.long\t1074339512\n\t\.long\t1374389535\n} } } */
+
+/*
+** test_double_into_gpr:
+**     lgrl	%r4,.LC0
+**     foo	%r4
+**     br	%r14
+*/
+
+void
+test_double_into_gpr (void)
+{
+  // This is the counterpart to
+  //   register double x asm ("r4") = 3.14;
+  //   asm ("foo	%0" :: "r" (x));
+  // where the bit-pattern of 3.14 is loaded into GPR.
+  asm ("foo	%0" :: "{r4}" (3.14));
+}
+
+/*
+** test_double:
+** (
+**     ldr	%f4,%f0
+**     ldr	%f5,%f2
+** |
+**     ldr	%f5,%f2
+**     ldr	%f4,%f0
+** )
+**     adbr	%f5,%f4
+**     ldr	%f0,%f5
+**     br	%r14
+*/
+
+double
+test_double (double x, double y)
+{
+  asm ("adbr	%0,%1" : "+{f5}" (y) : "{f4}" (x));
+  return y;
+}
diff --git a/gcc/testsuite/lib/scanasm.exp b/gcc/testsuite/lib/scanasm.exp
index 737eefc655e..1cc3cdcc904 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
