diff mbox series

[RFC,2/4] Error handling for hard register constraints

Message ID 20240910142121.3285492-3-stefansf@gcc.gnu.org
State New
Headers show
Series Hard Register Constraints | expand

Commit Message

Stefan Schulze Frielinghaus Sept. 10, 2024, 2:20 p.m. UTC
This implements some basic error handling for hard register constraints
including potential conflics with register asm operands.

In contrast to register asm operands, hard register constraints allow
more than just one register per operand.  Even more than just one
register per alternative.  For example, a valid constraint for an
operand is "{r0}{r1}m,{r2}".  However, this also means that we have to
make sure that each register is used at most once in each alternative
over all outputs and likewise over all inputs.  For asm statements this
is done by this patch during gimplification.  For hard register
constraints used in machine description, error handling is still a todo
and I haven't investigated this so far and consider this rather a low
priority.

There are 9/10 call sides for parse_{input,output}_constraint() which I
didn't dare to touch in the first run.  If this patch is about to be
accepted I could change those call sides and explicitly pass a null
pointer instead of overloading those functions as it is done right now.
I consider this an implementation nit and didn't want to clutter the
patch for reviewing.
---
 gcc/cfgexpand.cc                              |  42 ----
 gcc/gimplify.cc                               |  73 +++++-
 gcc/gimplify_reg_info.h                       | 130 ++++++++++
 gcc/stmt.cc                                   | 229 +++++++++++++++++-
 gcc/stmt.h                                    |   8 +-
 gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c   |  83 +++++++
 gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c   |  20 ++
 gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c   |  21 ++
 gcc/testsuite/gcc.dg/pr87600-2.c              |  30 +--
 gcc/testsuite/gcc.dg/pr87600-3.c              |  35 +++
 gcc/testsuite/gcc.dg/pr87600-3.s              |   0
 .../gcc.target/s390/asm-hard-reg-1.c          | 103 ++++++++
 .../gcc.target/s390/asm-hard-reg-2.c          |  43 ++++
 .../gcc.target/s390/asm-hard-reg-3.c          |  42 ++++
 gcc/testsuite/lib/scanasm.exp                 |   4 +
 15 files changed, 779 insertions(+), 84 deletions(-)
 create mode 100644 gcc/gimplify_reg_info.h
 create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-1.c
 create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-2.c
 create mode 100644 gcc/testsuite/gcc.dg/asm-hard-reg-error-3.c
 create mode 100644 gcc/testsuite/gcc.dg/pr87600-3.c
 create mode 100644 gcc/testsuite/gcc.dg/pr87600-3.s
 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 mbox series

Patch

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