]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
Hard register constraints
authorStefan Schulze Frielinghaus <stefansf@gcc.gnu.org>
Mon, 21 Jul 2025 11:05:26 +0000 (13:05 +0200)
committerStefan Schulze Frielinghaus <stefansf@gcc.gnu.org>
Mon, 21 Jul 2025 11:05:26 +0000 (13:05 +0200)
Implement hard register constraints of the form {regname} where regname
must be a valid register name for the target.  Such constraints may be
used in asm statements as a replacement for register asm and in machine
descriptions.  A more verbose description is given in extend.texi.

It is expected and desired that optimizations coalesce multiple pseudos
into one whenever possible.  However, in case of hard register
constraints we may have to undo this and introduce copies since
otherwise we would constraint a single pseudo to multiple hard
registers.  This is done prior RA during asmcons in
match_asm_constraints_2().  While IRA tries to reduce live ranges, it
also replaces some register-register moves.  That in turn might undo
those copies of a pseudo which we just introduced during asmcons.  Thus,
check in decrease_live_ranges_number() via
valid_replacement_for_asm_input_p() whether it is valid to perform a
replacement.

The reminder of the patch mostly deals with parsing and decoding hard
register constraints.  The actual work is done by LRA in
process_alt_operands() where a register filter, according to the
constraint, is installed.

For the sake of "reviewability" and in order to show the beauty of LRA,
error handling (which gets pretty involved) is spread out into a
subsequent patch.

Limitation
----------

Currently, a fixed register cannot be used as hard register constraint.
For example, loading the stack pointer on x86_64 via

void *
foo (void)
{
  void *y;
  __asm__ ("" : "={rsp}" (y));
  return y;
}

leads to an error.

Asm Adjust Hook
---------------

The following targets implement TARGET_MD_ASM_ADJUST:

- aarch64
- arm
- avr
- cris
- i386
- mn10300
- nds32
- pdp11
- rs6000
- s390
- vax

Most of them only add the CC register to the list of clobbered register.
However, cris, i386, and s390 need some minor adjustment.

gcc/ChangeLog:

* config/cris/cris.cc (cris_md_asm_adjust): Deal with hard
register constraint.
* config/i386/i386.cc (map_egpr_constraints): Ditto.
* config/s390/s390.cc (f_constraint_p): Ditto.
* doc/extend.texi: Document hard register constraints.
* doc/md.texi: Ditto.
* function.cc (match_asm_constraints_2): Have a unique pseudo
for each operand with a hard register constraint.
(pass_match_asm_constraints::execute): Calling into new helper
match_asm_constraints_2().
* genoutput.cc (mdep_constraint_len): Return the length of a
hard register constraint.
* genpreds.cc (write_insn_constraint_len): Support hard register
constraints for insn_constraint_len().
* ira.cc (valid_replacement_for_asm_input_p_1): New helper.
(valid_replacement_for_asm_input_p): New helper.
(decrease_live_ranges_number): Similar to
match_asm_constraints_2() ensure that each operand has a unique
pseudo if constrained by a hard register.
* lra-constraints.cc (process_alt_operands): Install hard
register filter according to constraint.
* recog.cc (asm_operand_ok): Accept register type for hard
register constrained asm operands.
(constrain_operands): Validate hard register constraints.
* stmt.cc (decode_hard_reg_constraint): Parse a hard register
constraint into the corresponding register number or bail out.
(parse_output_constraint): Parse hard register constraint and
set *ALLOWS_REG.
(parse_input_constraint): Ditto.
* stmt.h (decode_hard_reg_constraint): Declaration of new
function.

gcc/testsuite/ChangeLog:

* gcc.dg/asm-hard-reg-1.c: New test.
* gcc.dg/asm-hard-reg-2.c: New test.
* gcc.dg/asm-hard-reg-3.c: New test.
* gcc.dg/asm-hard-reg-4.c: New test.
* gcc.dg/asm-hard-reg-5.c: New test.
* gcc.dg/asm-hard-reg-6.c: New test.
* gcc.dg/asm-hard-reg-7.c: New test.
* gcc.dg/asm-hard-reg-8.c: New test.
* gcc.target/aarch64/asm-hard-reg-1.c: New test.
* gcc.target/i386/asm-hard-reg-1.c: New test.
* gcc.target/i386/asm-hard-reg-2.c: New test.
* gcc.target/s390/asm-hard-reg-1.c: New test.
* gcc.target/s390/asm-hard-reg-2.c: New test.
* gcc.target/s390/asm-hard-reg-3.c: New test.
* gcc.target/s390/asm-hard-reg-4.c: New test.
* gcc.target/s390/asm-hard-reg-5.c: New test.
* gcc.target/s390/asm-hard-reg-6.c: New test.
* gcc.target/s390/asm-hard-reg-longdouble.h: New test.

31 files changed:
gcc/config/cris/cris.cc
gcc/config/i386/i386.cc
gcc/config/s390/s390.cc
gcc/doc/extend.texi
gcc/doc/md.texi
gcc/function.cc
gcc/genoutput.cc
gcc/genpreds.cc
gcc/ira.cc
gcc/lra-constraints.cc
gcc/recog.cc
gcc/stmt.cc
gcc/stmt.h
gcc/testsuite/gcc.dg/asm-hard-reg-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-4.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-5.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-6.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-7.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/asm-hard-reg-8.c [new file with mode: 0644]
gcc/testsuite/gcc.target/aarch64/asm-hard-reg-1.c [new file with mode: 0644]
gcc/testsuite/gcc.target/i386/asm-hard-reg-1.c [new file with mode: 0644]
gcc/testsuite/gcc.target/i386/asm-hard-reg-2.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-1.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-2.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-3.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-4.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-5.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-6.c [new file with mode: 0644]
gcc/testsuite/gcc.target/s390/asm-hard-reg-longdouble.h [new file with mode: 0644]

index a34c9e9743ce0ae0b6927ada15aef02b1ed95845..4acdd1dee7d5b78b5129df5bbde51598f23b41dd 100644 (file)
@@ -3711,9 +3711,11 @@ cris_md_asm_adjust (vec<rtx> &outputs, vec<rtx> &inputs,
   /* Determine if the source using MOF.  If it is, automatically
      clobbering MOF would cause it to have impossible constraints.  */
 
-  /* Look for a use of the MOF constraint letter: h.  */
+  /* Look for a use of the MOF constraint letter h or a hard register
+     constraint.  */
   for (unsigned i = 0, n = constraints.length(); i < n; ++i)
-    if (strchr (constraints[i], 'h') != NULL)
+    if (strchr (constraints[i], 'h') != NULL
+       || strstr (constraints[i], "{mof}") != NULL)
       return NULL;
 
   /* Look for an output or an input that touches MOF.  */
index 49bd3939eb4e703d5e1dec36ea1fcb5024a6ec62..4682db85ce4c1fc487497129d9987a8882d234f7 100644 (file)
@@ -24788,6 +24788,12 @@ static void map_egpr_constraints (vec<const char *> &constraints)
              buf.safe_push (cur[j + 1]);
              j++;
              break;
+           case '{':
+             do
+               {
+                 buf.safe_push (cur[j]);
+               } while (cur[j++] != '}');
+             break;
            default:
              buf.safe_push (cur[j]);
              break;
index b5e636c5376757b36da4ad4ad1dbc6f1ba92fe1e..c15c883e38fc7c5734fc18e0e3bf8cc780c3f632 100644 (file)
@@ -17843,9 +17843,11 @@ f_constraint_p (const char *constraint)
   for (size_t i = 0, c_len = strlen (constraint); i < c_len;
        i += CONSTRAINT_LEN (constraint[i], constraint + i))
     {
-      if (constraint[i] == 'f')
+      if (constraint[i] == 'f'
+         || (constraint[i] == '{' && constraint[i + 1] == 'f'))
        seen_f_p = true;
-      if (constraint[i] == 'v')
+      if (constraint[i] == 'v'
+         || (constraint[i] == '{' && constraint[i + 1] == 'v'))
        seen_v_p = true;
     }
 
index 8c29e24c26700a536b3fab258d2c160587808a49..76fed32918f6369bfba36156980c29885b39bebf 100644 (file)
@@ -12775,6 +12775,7 @@ the two, as explained in the sections below.
 @menu
 * Global Register Variables::   Variables declared at global scope.
 * Local Register Variables::    Variables declared within a function.
+* Hard Register Constraints::   Operands forced into specific machine registers.
 @end menu
 
 @node Global Register Variables
@@ -12980,6 +12981,167 @@ with slightly different characteristics (@pxref{MIPS Coprocessors,,
 Defining coprocessor specifics for MIPS targets, gccint, 
 GNU Compiler Collection (GCC) Internals}).
 
+@node Hard Register Constraints
+@subsubsection Hard Register Constraints
+
+Similar to register @code{asm} but still distinct, hard register constraints
+are another way to force operands of inline @code{asm} into specific machine
+registers.  In contrast to register @code{asm} where a variable is bound to a
+machine register, a hard register constraint binds an @code{asm} operand to a
+machine register.  Assume in the following that @code{r4} is a general-purpose
+register, @code{f5} a floating-point register, and @code{v6} a vector register
+for some target.
+
+@smallexample
+int x;
+int y __attribute__ ((vector_size (16)));
+@dots{}
+asm ("some instructions"
+     : "=@{r4@}" (x)
+     : "@{f5@}" (42.0), "@{v6@}" (y));
+@end smallexample
+
+For the inline @code{asm}, variable @code{x} is bound to register @code{r4},
+and @code{y} is loaded to @code{v6}.  Furthermore, constant @code{42.0} is
+loaded into floating-point register @code{f5}.
+
+A key difference between register @code{asm} and hard register constraints is
+that the latter are specified at the point where they are supposed to
+materialize, namely at inline @code{asm}, which may lead to more readable code.
+
+@subsubheading Usage
+
+Each input operand is loaded into the register specified by its corresponding
+hard register constraint.  Furthermore, each hard register must be used at most
+once among an alternative for inputs.  This renders hard register constraints
+more strict compared to register @code{asm} where multiple inputs may share a
+register as for example in
+
+@smallexample
+int x;
+register int y asm ("0") = @dots{};
+asm ("" : "=r" (x) : "r" (y), "r" (y));
+@end smallexample
+
+or even
+
+@smallexample
+register int x asm ("0") = 42;
+register int y asm ("0") = 24;
+asm ("" : "=r" (x) : "r" (x), "r" (y));
+@end smallexample
+
+The analogue for hard register constraints is invalid in order to prevent
+subtle bugs.
+
+Likewise, two outputs must not share a register among an alternative.  That
+means, the following example is invalid
+
+@smallexample
+int x, y;
+asm ("" : "=@{r4@}" (x), "=@{r4@}" (y));  // invalid
+@end smallexample
+
+which also aligns with register @code{asm}.  Despite that, each output must
+refer to a distinct object if a hard register constraint is involved.  For
+example, in the following, object @code{x} is assigned two registers.
+
+@smallexample
+int x;
+asm ("" : "=r" (x), "=r" (x));
+@end smallexample
+
+This is not allowed for hard register constraints in order to prevent subtle
+bugs.  Even if only one output operand has a hard register constraint, the code
+is rejected since the allocation for the object is still ambiguous.
+
+@smallexample
+int x;
+asm ("" : "=r" (x), "=@{1@}" (x));  // invalid
+@end smallexample
+
+The type of an operand must be supported by the corresponding machine register.
+
+A hard register constraint may refer to any general, floating-point, or vector
+register except a fixed register as e.g.@: the stack-pointer register.  The set
+of allowed registers is target dependent analogue to register @code{asm}.
+Furthermore, the referenced register must be a valid register name of the
+target.  Note, on some targets, a single register may be referred to by
+different names where each name specifies the length of the register.  For
+example, on x86_64 the register names @code{rcx}, @code{ecx}, and @code{cx} all
+refer to the same register but in different sizes.  If any of those names is
+used for a hard register constraint, the actual size of a register is
+determined by its corresponding operand.  For example
+
+@smallexample
+long x;
+asm ("mov\t$42, %0" : "=@{ecx@}" (x));
+@end smallexample
+
+Although the hard register constraint refers to register @code{ecx}, the actual
+register will be @code{rcx} since on x86_64 a @code{long} is 8 byte in total.
+This aligns with register @code{asm} where you could have
+
+@smallexample
+register long x asm ("ecx");
+@end smallexample
+
+@subsubheading Interaction with Register @code{asm}
+
+A mixture of both constructs as for example
+
+@smallexample
+register int x asm ("r4") = 42;
+int y;
+asm ("" : "=@{r5@}" (y) : "r" (x));
+@end smallexample
+
+is valid.
+
+If an operand is a register @code{asm} and the corresponding constraint a hard
+register, then both must refer to the same register.  That means
+
+@smallexample
+register int x asm ("r4");
+asm ("" : "=@{r4@}" (x));
+@end smallexample
+
+is valid and
+
+@smallexample
+register int x asm ("r4");
+asm ("" : "=@{r5@}" (x));  // invalid
+@end smallexample
+
+is invalid.
+
+Note, register @code{asm} may not only be clobbered by function calls but also
+by inline @code{asm} in conjunction with hard register constraints.  For
+example, in the following
+
+@smallexample
+register int x asm ("r5") = 42;
+int y;
+asm ("" : "=@{r5@}" (y));
+asm ("" : "+r" (x));
+@end smallexample
+
+variable @code{x} materializes before the very first inline @code{asm} which
+writes to register @code{r5} and therefore clobbers @code{x} which in turn is
+read by the subsequent inline @code{asm}.
+
+@subsubheading Limitations
+
+At the moment fixed registers are not supported for hard register constraints.
+Thus, idioms like
+
+@smallexample
+register void *x asm ("rsp");
+asm ("" : "=r" (x));
+@end smallexample
+
+are not supported for hard register constraints.  This might be lifted.
+
 @node Size of an asm
 @subsection Size of an @code{asm}
 
index 28159b2e820a4e12a9a194cf70a814da2e455bf2..aba93f606eca59d31c103a05b2567fd4f3be55f3 100644 (file)
@@ -1366,6 +1366,12 @@ as for @samp{<} apply.
 A register operand is allowed provided that it is in a general
 register.
 
+@cindex hard registers in constraint
+@item @samp{@{r@}}
+An operand is bound to hard register @samp{r} which may be any general,
+floating-point, or vector register except a fixed register like a stack pointer
+register.  The set of fixed registers is target dependent.
+
 @cindex constants in constraints
 @cindex @samp{i} in constraint
 @item @samp{i}
index 48167b0c20721660a71124a0cea26fb33dfff00f..2b77bbd8bb320f60a58c27c48b1c4b52a62cbcaa 100644 (file)
@@ -7009,6 +7009,115 @@ match_asm_constraints_1 (rtx_insn *insn, rtx *p_sets, int noutputs)
     df_insn_rescan (insn);
 }
 
+/* It is expected and desired that optimizations coalesce multiple pseudos into
+   one whenever possible.  However, in case of hard register constraints we may
+   have to undo this and introduce copies since otherwise we could constraint a
+   single pseudo to different hard registers.  For example, during register
+   allocation the following insn would be unsatisfiable since pseudo 60 is
+   constrained to hard register r5 and r6 at the same time.
+
+   (insn 7 5 0 2 (asm_operands/v ("foo") ("") 0 [
+              (reg:DI 60) repeated x2
+          ]
+           [
+              (asm_input:DI ("{r5}") t.c:4)
+              (asm_input:DI ("{r6}") t.c:4)
+          ]
+           [] t.c:4) "t.c":4:3 -1
+       (expr_list:REG_DEAD (reg:DI 60)
+          (nil)))
+
+   Therefore, introduce a copy of pseudo 60 and transform it into
+
+   (insn 10 5 7 2 (set (reg:DI 62)
+          (reg:DI 60)) "t.c":4:3 1503 {*movdi_64}
+       (nil))
+   (insn 7 10 11 2 (asm_operands/v ("foo") ("") 0 [
+              (reg:DI 60)
+              (reg:DI 62)
+          ]
+           [
+              (asm_input:DI ("{r5}") t.c:4)
+              (asm_input:DI ("{r6}") t.c:4)
+          ]
+           [] t.c:4) "t.c":4:3 -1
+       (expr_list:REG_DEAD (reg:DI 62)
+          (expr_list:REG_DEAD (reg:DI 60)
+              (nil))))
+
+   Now, LRA can assign pseudo 60 to r5, and pseudo 62 to r6.
+
+   TODO: The current implementation is conservative and we could do a bit
+   better in case of alternatives.  For example
+
+   (insn 7 5 0 2 (asm_operands/v ("foo") ("") 0 [
+              (reg:DI 60) repeated x2
+          ]
+           [
+              (asm_input:DI ("r,{r5}") t.c:4)
+              (asm_input:DI ("{r6},r") t.c:4)
+          ]
+           [] t.c:4) "t.c":4:3 -1
+       (expr_list:REG_DEAD (reg:DI 60)
+          (nil)))
+
+   For this insn we wouldn't need to come up with a copy of pseudo 60 since in
+   each alternative pseudo 60 is constrained exactly one time.  */
+
+static void
+match_asm_constraints_2 (rtx_insn *insn, rtx pat)
+{
+  rtx op;
+  if (GET_CODE (pat) == SET && GET_CODE (SET_SRC (pat)) == ASM_OPERANDS)
+    op = SET_SRC (pat);
+  else if (GET_CODE (pat) == ASM_OPERANDS)
+    op = pat;
+  else
+    return;
+  int ninputs = ASM_OPERANDS_INPUT_LENGTH (op);
+  rtvec inputs = ASM_OPERANDS_INPUT_VEC (op);
+  bool changed = false;
+  auto_bitmap constrained_regs;
+
+  for (int i = 0; i < ninputs; ++i)
+    {
+      rtx input = RTVEC_ELT (inputs, i);
+      const char *constraint = ASM_OPERANDS_INPUT_CONSTRAINT (op, i);
+      if ((!REG_P (input) && !SUBREG_P (input))
+         || (REG_P (input) && HARD_REGISTER_P (input))
+         || strchr (constraint, '{') == nullptr)
+       continue;
+      int regno;
+      if (SUBREG_P (input))
+       {
+         if (REG_P (SUBREG_REG (input)))
+           regno = REGNO (SUBREG_REG (input));
+         else
+           continue;
+       }
+      else
+       regno = REGNO (input);
+      /* Keep the first usage of a constrained pseudo as is and only
+        introduce copies for subsequent usages.  */
+      if (! bitmap_bit_p (constrained_regs, regno))
+       {
+         bitmap_set_bit (constrained_regs, regno);
+         continue;
+       }
+      rtx tmp = gen_reg_rtx (GET_MODE (input));
+      start_sequence ();
+      emit_move_insn (tmp, input);
+      rtx_insn *insns = get_insns ();
+      end_sequence ();
+      emit_insn_before (insns, insn);
+      RTVEC_ELT (inputs, i) = tmp;
+      changed = true;
+    }
+
+  if (changed)
+    df_insn_rescan (insn);
+}
+
 /* Add the decl D to the local_decls list of FUN.  */
 
 void
@@ -7065,6 +7174,13 @@ pass_match_asm_constraints::execute (function *fun)
            continue;
 
          pat = PATTERN (insn);
+
+         if (GET_CODE (pat) == PARALLEL)
+           for (int i = XVECLEN (pat, 0) - 1; i >= 0; --i)
+             match_asm_constraints_2 (insn, XVECEXP (pat, 0, i));
+         else
+           match_asm_constraints_2 (insn, pat);
+
          if (GET_CODE (pat) == PARALLEL)
            p_sets = &XVECEXP (pat, 0, 0), noutputs = XVECLEN (pat, 0);
          else if (GET_CODE (pat) == SET)
index 25d0b8b8646763c81c24b5924ed91468b8d820f3..1157f820cf4b955b784d36b5fc245927f02eeb44 100644 (file)
@@ -1284,6 +1284,20 @@ mdep_constraint_len (const char *s, file_location loc, int opno)
       if (!strncmp (s, p->name, p->namelen))
        return p->namelen;
 
+  if (*s == '{')
+    {
+      const char *end = s + 1;
+      while (*end != '}' && *end != '"' && *end != '\0')
+       ++end;
+      /* Similarly as in decode_hreg_constraint(), consider any hard register
+        name longer than a few characters as an error.  */
+      ptrdiff_t len = end - s;
+      if (*end == '}' && len > 1 && len < 31)
+       {
+         return len + 1;
+       }
+    }
+
   error_at (loc, "error: undefined machine-specific constraint "
            "at this point: \"%s\"", s);
   message_at (loc, "note:  in operand %d", opno);
index d09d769c00dfb346e4b26e251b34968486c90d28..c6a2983419af8d93b61111efefd1975f9146c3bb 100644 (file)
@@ -1150,7 +1150,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"
        "    {");
@@ -1183,6 +1183,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");
 }
index 979a034a87f436d75640b67d1ea57257ed133749..4eebc9c4c50fb0338d714890fed3c120c485e178 100644 (file)
@@ -2113,6 +2113,87 @@ ira_get_dup_out_num (int op_num, alternative_mask alts,
 
 \f
 
+/* Return true if a replacement of SRC by DEST does not lead to unsatisfiable
+   asm.  A replacement is valid if SRC or DEST are not constrained in asm
+   inputs of a single asm statement.  See match_asm_constraints_2() for more
+   details.  TODO: As in match_asm_constraints_2() consider alternatives more
+   precisely.  */
+
+static bool
+valid_replacement_for_asm_input_p_1 (const_rtx asmops, const_rtx src, const_rtx dest)
+{
+  int ninputs = ASM_OPERANDS_INPUT_LENGTH (asmops);
+  rtvec inputs = ASM_OPERANDS_INPUT_VEC (asmops);
+  for (int i = 0; i < ninputs; ++i)
+    {
+      rtx input_src = RTVEC_ELT (inputs, i);
+      const char *constraint_src
+       = ASM_OPERANDS_INPUT_CONSTRAINT (asmops, i);
+      if (rtx_equal_p (input_src, src)
+         && strchr (constraint_src, '{') != nullptr)
+       for (int j = 0; j < ninputs; ++j)
+         {
+           rtx input_dest = RTVEC_ELT (inputs, j);
+           const char *constraint_dest
+             = ASM_OPERANDS_INPUT_CONSTRAINT (asmops, j);
+           if (rtx_equal_p (input_dest, dest)
+               && strchr (constraint_dest, '{') != nullptr)
+             return false;
+         }
+    }
+  return true;
+}
+
+/* Return true if a replacement of SRC by DEST does not lead to unsatisfiable
+   asm.  A replacement is valid if SRC or DEST are not constrained in asm
+   inputs of a single asm statement.  The final check is done in function
+   valid_replacement_for_asm_input_p_1.  */
+
+static bool
+valid_replacement_for_asm_input_p (const_rtx src, const_rtx dest)
+{
+  /* Bail out early if there is no asm statement.  */
+  if (!crtl->has_asm_statement)
+    return true;
+  for (df_ref use = DF_REG_USE_CHAIN (REGNO (src));
+       use;
+       use = DF_REF_NEXT_REG (use))
+    {
+      struct df_insn_info *use_info = DF_REF_INSN_INFO (use);
+      /* Only check real uses, not artificial ones.  */
+      if (use_info)
+       {
+         rtx_insn *insn = DF_REF_INSN (use);
+         rtx pat = PATTERN (insn);
+         if (asm_noperands (pat) <= 0)
+           continue;
+         if (GET_CODE (pat) == SET)
+           {
+             if (!valid_replacement_for_asm_input_p_1 (SET_SRC (pat), src, dest))
+               return false;
+           }
+         else if (GET_CODE (pat) == PARALLEL)
+           for (int i = 0, len = XVECLEN (pat, 0); i < len; ++i)
+             {
+               rtx asmops = XVECEXP (pat, 0, i);
+               if (GET_CODE (asmops) == SET)
+                 asmops = SET_SRC (asmops);
+               if (GET_CODE (asmops) == ASM_OPERANDS
+                   && !valid_replacement_for_asm_input_p_1 (asmops, src, dest))
+                 return false;
+             }
+         else if (GET_CODE (pat) == ASM_OPERANDS)
+           {
+             if (!valid_replacement_for_asm_input_p_1 (pat, src, dest))
+               return false;
+           }
+         else
+           gcc_unreachable ();
+       }
+    }
+  return true;
+}
+
 /* Search forward to see if the source register of a copy insn dies
    before either it or the destination register is modified, but don't
    scan past the end of the basic block.  If so, we can replace the
@@ -2162,7 +2243,8 @@ decrease_live_ranges_number (void)
               auto-inc memory reference, so we must disallow this
               optimization on them.  */
            || sregno == STACK_POINTER_REGNUM
-           || dregno == STACK_POINTER_REGNUM)
+           || dregno == STACK_POINTER_REGNUM
+           || !valid_replacement_for_asm_input_p (src, dest))
          continue;
 
        dest_death = NULL_RTX;
index c941d2f33e24ae870fbc468cc432795afe89c076..83f8fda3b525cf2edcf75b5bb3a4e0677366f7ea 100644 (file)
 #include "target.h"
 #include "rtl.h"
 #include "tree.h"
+#include "stmt.h"
 #include "predict.h"
 #include "df.h"
 #include "memmodel.h"
@@ -2168,6 +2169,7 @@ process_alt_operands (int only_alternative)
   bool costly_p;
   enum reg_class cl;
   const HARD_REG_SET *cl_filter;
+  HARD_REG_SET hard_reg_constraint;
 
   /* Calculate some data common for all alternatives to speed up the
      function. */
@@ -2545,6 +2547,17 @@ process_alt_operands (int only_alternative)
                  cl_filter = nullptr;
                  goto reg;
 
+               case '{':
+                   {
+                     int regno = decode_hard_reg_constraint (p);
+                     gcc_assert (regno >= 0);
+                     cl = REGNO_REG_CLASS (regno);
+                     CLEAR_HARD_REG_SET (hard_reg_constraint);
+                     SET_HARD_REG_BIT (hard_reg_constraint, regno);
+                     cl_filter = &hard_reg_constraint;
+                     goto reg;
+                   }
+
                default:
                  cn = lookup_constraint (p);
                  switch (get_constraint_type (cn))
index edf22cba807c794c68f77779e7ddb3596af1e8fe..67d7fa6306921a63f77c78c917349a9013179929 100644 (file)
@@ -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"
@@ -2363,7 +2364,8 @@ asm_operand_ok (rtx op, const char *constraint, const char **constraints)
            {
            case CT_REGISTER:
              if (!result
-                 && reg_class_for_constraint (cn) != NO_REGS
+                 && (reg_class_for_constraint (cn) != NO_REGS
+                     || constraint[0] == '{')
                  && GET_MODE (op) != BLKmode
                  && register_operand (op, VOIDmode))
                result = 1;
@@ -3304,6 +3306,13 @@ constrain_operands (int strict, alternative_mask alternatives)
                  win = true;
                break;
 
+             case '{':
+               if ((REG_P (op) && HARD_REGISTER_P (op)
+                    && (int) REGNO (op) == decode_hard_reg_constraint (p))
+                   || !reload_completed)
+                 win = true;
+               break;
+
              default:
                {
                  enum constraint_num cn = lookup_constraint (p);
index a510f8fe9f32545d010d00569305a5c7473d914c..58aa8edd5f38b322a947846d1e69313c97d87ab4 100644 (file)
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "dumpfile.h"
 #include "builtins.h"
 #include "cfgexpand.h"
+#include "output.h"
 
 \f
 /* Functions and data structures for expanding case statements.  */
@@ -175,6 +176,32 @@ expand_label (tree label)
     maybe_set_first_label_num (label_r);
 }
 \f
+/* 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
+decode_hard_reg_constraint (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,
@@ -294,6 +321,12 @@ parse_output_constraint (const char **constraint_p, int operand_num,
          *allows_mem = true;
          break;
 
+       case '{':
+         {
+           *allows_reg = true;
+           break;
+         }
+
        default:
          if (!ISALPHA (*p))
            break;
@@ -429,6 +462,12 @@ parse_input_constraint (const char **constraint_p, int input_num,
        *allows_mem = true;
        break;
 
+      case '{':
+       {
+         *allows_reg = true;
+         break;
+       }
+
       default:
        if (! ISALPHA (constraint[j]))
          {
index 2beb4d5efe772bbf5ea9eb2c5401aac74c22ed78..1195a0bca62c9e19eed1d947e93e432e3a495153 100644 (file)
@@ -25,6 +25,7 @@ extern bool parse_output_constraint (const char **, int, int, int,
                                     bool *, bool *, bool *);
 extern bool parse_input_constraint (const char **, int, int, int, int,
                                    const char * const *, bool *, bool *);
+extern int decode_hard_reg_constraint (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.dg/asm-hard-reg-1.c b/gcc/testsuite/gcc.dg/asm-hard-reg-1.c
new file mode 100644 (file)
index 0000000..6a5a9ad
--- /dev/null
@@ -0,0 +1,85 @@
+/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */
+
+#if defined (__aarch64__)
+# define GPR "{x4}"
+/* { dg-final { scan-assembler-times "foo\tx4" 8 { target { aarch64*-*-* } } } } */
+#elif defined (__arm__)
+# define GPR "{r4}"
+/* { dg-final { scan-assembler-times "foo\tr4" 8 { target { arm*-*-* } } } } */
+#elif defined (__i386__)
+# define GPR "{ecx}"
+/* { dg-final { scan-assembler-times "foo\t%cl" 2 { target { i?86-*-* } } } } */
+/* { dg-final { scan-assembler-times "foo\t%cx" 2 { target { i?86-*-* } } } } */
+/* { dg-final { scan-assembler-times "foo\t%ecx" 4 { target { i?86-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define GPR "{r5}"
+/* { dg-final { scan-assembler-times "foo\t5" 8 { target { powerpc*-*-* } } } } */
+#elif defined (__riscv)
+# define GPR "{t5}"
+/* { dg-final { scan-assembler-times "foo\tt5" 8 { target { riscv*-*-* } } } } */
+#elif defined (__s390__)
+# define GPR "{r4}"
+/* { dg-final { scan-assembler-times "foo\t%r4" 8 { target { s390*-*-* } } } } */
+#elif defined (__x86_64__)
+# define GPR "{rcx}"
+/* { dg-final { scan-assembler-times "foo\t%cl" 2 { target { x86_64-*-* } } } } */
+/* { dg-final { scan-assembler-times "foo\t%cx" 2 { target { x86_64-*-* } } } } */
+/* { dg-final { scan-assembler-times "foo\t%ecx" 2 { target { x86_64-*-* } } } } */
+/* { dg-final { scan-assembler-times "foo\t%rcx" 2 { target { x86_64-*-* } } } } */
+#endif
+
+char
+test_char (char x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (x));
+  return x;
+}
+
+char
+test_char_from_mem (char *x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (*x));
+  return *x;
+}
+
+short
+test_short (short x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (x));
+  return x;
+}
+
+short
+test_short_from_mem (short *x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (*x));
+  return *x;
+}
+
+int
+test_int (int x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (x));
+  return x;
+}
+
+int
+test_int_from_mem (int *x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (*x));
+  return *x;
+}
+
+long
+test_long (long x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (x));
+  return x;
+}
+
+long
+test_long_from_mem (long *x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (*x));
+  return *x;
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-2.c b/gcc/testsuite/gcc.dg/asm-hard-reg-2.c
new file mode 100644 (file)
index 0000000..7dabf96
--- /dev/null
@@ -0,0 +1,33 @@
+/* { dg-do compile { target aarch64*-*-* powerpc64*-*-* riscv64-*-* s390*-*-* x86_64-*-* } } */
+/* { dg-options "-std=c99" } we need long long */
+
+#if defined (__aarch64__)
+# define GPR "{x4}"
+/* { dg-final { scan-assembler-times "foo\tx4" 2 { target { aarch64*-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define GPR "{r5}"
+/* { dg-final { scan-assembler-times "foo\t5" 2 { target { powerpc64*-*-* } } } } */
+#elif defined (__riscv)
+# define GPR "{t5}"
+/* { dg-final { scan-assembler-times "foo\tt5" 2 { target { riscv64-*-* } } } } */
+#elif defined (__s390__)
+# define GPR "{r4}"
+/* { dg-final { scan-assembler-times "foo\t%r4" 2 { target { s390*-*-* } } } } */
+#elif defined (__x86_64__)
+# define GPR "{rcx}"
+/* { dg-final { scan-assembler-times "foo\t%rcx" 2 { target { x86_64-*-* } } } } */
+#endif
+
+long long
+test_longlong (long long x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (x));
+  return x;
+}
+
+long long
+test_longlong_from_mem (long long *x)
+{
+  __asm__ ("foo\t%0" : "+"GPR (*x));
+  return *x;
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-3.c b/gcc/testsuite/gcc.dg/asm-hard-reg-3.c
new file mode 100644 (file)
index 0000000..fa4472a
--- /dev/null
@@ -0,0 +1,25 @@
+/* { dg-do compile { target { { aarch64*-*-* powerpc64*-*-* riscv64-*-* s390*-*-* x86_64-*-* } && int128 } } } */
+/* { dg-options "-O2" } get rid of -ansi since we use __int128 */
+
+#if defined (__aarch64__)
+# define REG "{x4}"
+/* { dg-final { scan-assembler-times "foo\tx4" 1 { target { aarch64*-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define REG "{r5}"
+/* { dg-final { scan-assembler-times "foo\t5" 1 { target { powerpc*-*-* } } } } */
+#elif defined (__riscv)
+# define REG "{t5}"
+/* { dg-final { scan-assembler-times "foo\tt5" 1 { target { riscv*-*-* } } } } */
+#elif defined (__s390__)
+# define REG "{r4}"
+/* { dg-final { scan-assembler-times "foo\t%r4" 1 { target { s390*-*-* } } } } */
+#elif defined (__x86_64__)
+# define REG "{xmm0}"
+/* { dg-final { scan-assembler-times "foo\t%xmm0" 1 { target { x86_64-*-* } } } } */
+#endif
+
+void
+test (void)
+{
+  __asm__ ("foo\t%0" :: REG ((__int128) 42));
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-4.c b/gcc/testsuite/gcc.dg/asm-hard-reg-4.c
new file mode 100644 (file)
index 0000000..0134bf0
--- /dev/null
@@ -0,0 +1,50 @@
+/* { dg-do compile { target aarch64*-*-* arm*-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */
+
+#if defined (__aarch64__)
+# define FPR "{d5}"
+/* { dg-final { scan-assembler-times "foo\tv5" 4 { target { aarch64*-*-* } } } } */
+#elif defined (__arm__)
+# define FPR "{d5}"
+/* { dg-additional-options "-march=armv7-a+fp -mfloat-abi=hard" { target arm*-*-* } } */
+/* { dg-final { scan-assembler-times "foo\ts10" 4 { target { arm*-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define FPR "{5}"
+/* { dg-final { scan-assembler-times "foo\t5" 4 { target { powerpc*-*-* } } } } */
+#elif defined (__riscv)
+# define FPR "{fa5}"
+/* { dg-final { scan-assembler-times "foo\tfa5" 4 { target { rsicv*-*-* } } } } */
+#elif defined (__s390__)
+# define FPR "{f5}"
+/* { dg-final { scan-assembler-times "foo\t%f5" 4 { target { s390*-*-* } } } } */
+#elif defined (__x86_64__)
+# define FPR "{xmm5}"
+/* { dg-final { scan-assembler-times "foo\t%xmm5" 4 { target { x86_64-*-* } } } } */
+#endif
+
+float
+test_float (float x)
+{
+  __asm__ ("foo\t%0" : "+"FPR (x));
+  return x;
+}
+
+float
+test_float_from_mem (float *x)
+{
+  __asm__ ("foo\t%0" : "+"FPR (*x));
+  return *x;
+}
+
+double
+test_double (double x)
+{
+  __asm__ ("foo\t%0" : "+"FPR (x));
+  return x;
+}
+
+double
+test_double_from_mem (double *x)
+{
+  __asm__ ("foo\t%0" : "+"FPR (*x));
+  return *x;
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-5.c b/gcc/testsuite/gcc.dg/asm-hard-reg-5.c
new file mode 100644 (file)
index 0000000..a9e25ce
--- /dev/null
@@ -0,0 +1,36 @@
+/* { dg-do compile { target aarch64*-*-* powerpc64*-*-* riscv64-*-* s390*-*-* x86_64-*-* } } */
+
+typedef int V __attribute__ ((vector_size (4 * sizeof (int))));
+
+#if defined (__aarch64__)
+# define VR "{v20}"
+/* { dg-final { scan-assembler-times "foo\tv20" 2 { target { aarch64*-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define VR "{v5}"
+/* { dg-final { scan-assembler-times "foo\t5" 2 { target { powerpc64*-*-* } } } } */
+#elif defined (__riscv)
+# define VR "{v5}"
+/* { dg-additional-options "-march=rv64imv" { target riscv64-*-* } } */
+/* { dg-final { scan-assembler-times "foo\tv5" 2 { target { riscv*-*-* } } } } */
+#elif defined (__s390__)
+# define VR "{v5}"
+/* { dg-require-effective-target s390_mvx { target s390*-*-* } } */
+/* { dg-final { scan-assembler-times "foo\t%v5" 2 { target s390*-*-* } } } */
+#elif defined (__x86_64__)
+# define VR "{xmm9}"
+/* { dg-final { scan-assembler-times "foo\t%xmm9" 2 { target { x86_64-*-* } } } } */
+#endif
+
+V
+test (V x)
+{
+  __asm__ ("foo\t%0" : "+"VR (x));
+  return x;
+}
+
+V
+test_from_mem (V *x)
+{
+  __asm__ ("foo\t%0" : "+"VR (*x));
+  return *x;
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-6.c b/gcc/testsuite/gcc.dg/asm-hard-reg-6.c
new file mode 100644 (file)
index 0000000..d9b7fae
--- /dev/null
@@ -0,0 +1,60 @@
+/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */
+/* { dg-options "-O2" } */
+
+/* Test multiple alternatives.  */
+
+#if defined (__aarch64__)
+# define GPR1 "{x1}"
+# define GPR2 "{x2}"
+# define GPR3 "{x3}"
+/* { dg-final { scan-assembler-times "foo\tx1,x3" 1 { target { aarch64*-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\tx2,\\\[x1\\\]" 1 { target { aarch64*-*-* } } } } */
+#elif defined (__arm__)
+# define GPR1 "{r1}"
+# define GPR2 "{r2}"
+# define GPR3 "{r3}"
+/* { dg-final { scan-assembler-times "foo\tr1,r3" 1 { target { arm*-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\tr2,\\\[r1\\\]" 1 { target { arm*-*-* } } } } */
+#elif defined (__i386__)
+# define GPR1 "{eax}"
+# define GPR2 "{ebx}"
+# define GPR3 "{ecx}"
+/* { dg-final { scan-assembler-times "foo\t4\\(%esp\\),%ecx" 1 { target { i?86-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\t%ebx,\\(%eax\\)" 1 { target { i?86-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define GPR1 "{r4}"
+# define GPR2 "{r5}"
+# define GPR3 "{r6}"
+/* { dg-final { scan-assembler-times "foo\t4,6" 1 { target { powerpc*-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\t5,0\\(4\\)" 1 { target { powerpc*-*-* } } } } */
+#elif defined (__riscv)
+# define GPR1 "{t1}"
+# define GPR2 "{t2}"
+# define GPR3 "{t3}"
+/* { dg-final { scan-assembler-times "foo\tt1,t3" 1 { target { riscv*-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\tt2,0\\(a1\\)" 1 { target { riscv*-*-* } } } } */
+#elif defined (__s390__)
+# define GPR1 "{r0}"
+# define GPR2 "{r1}"
+# define GPR3 "{r2}"
+/* { dg-final { scan-assembler-times "foo\t%r0,%r2" 1 { target { s390*-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\t%r1,0\\(%r3\\)" 1 { target { s390*-*-* } } } } */
+#elif defined (__x86_64__)
+# define GPR1 "{eax}"
+# define GPR2 "{ebx}"
+# define GPR3 "{rcx}"
+/* { dg-final { scan-assembler-times "foo\t%eax,%rcx" 1 { target { x86_64-*-* } } } } */
+/* { dg-final { scan-assembler-times "bar\t%ebx,\\(%rsi\\)" 1 { target { x86_64-*-* } } } } */
+#endif
+
+void
+test_reg_reg (int x, long long *y)
+{
+  __asm__ ("foo\t%0,%1" :: GPR1"m,"GPR2 (x), GPR3",m" (y));
+}
+
+void
+test_reg_mem (int x, long long *y)
+{
+  __asm__ ("bar\t%0,%1" :: GPR1"m,"GPR2 (x), GPR3",m" (*y));
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-7.c b/gcc/testsuite/gcc.dg/asm-hard-reg-7.c
new file mode 100644 (file)
index 0000000..761a6b7
--- /dev/null
@@ -0,0 +1,41 @@
+/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */
+/* { dg-options "-O2" } */
+
+/* Test multiple alternatives.  */
+
+#if defined (__aarch64__)
+# define GPR "{x1}"
+/* { dg-final { scan-assembler-times "foo\tx1,x1" 2 { target { aarch64*-*-* } } } } */
+#elif defined (__arm__)
+# define GPR "{r1}"
+/* { dg-final { scan-assembler-times "foo\tr1,r1" 2 { target { arm*-*-* } } } } */
+#elif defined (__i386__)
+# define GPR "{eax}"
+/* { dg-final { scan-assembler-times "foo\t%eax,%eax" 2 { target { i?86-*-* } } } } */
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define GPR "{r4}"
+/* { dg-final { scan-assembler-times "foo\t4,4" 2 { target { powerpc*-*-* } } } } */
+#elif defined (__riscv)
+# define GPR "{t1}"
+/* { dg-final { scan-assembler-times "foo\tt1,t1" 2 { target { riscv*-*-* } } } } */
+#elif defined (__s390__)
+# define GPR "{r0}"
+/* { dg-final { scan-assembler-times "foo\t%r0,%r0" 2 { target { s390*-*-* } } } } */
+#elif defined (__x86_64__)
+# define GPR "{eax}"
+/* { dg-final { scan-assembler-times "foo\t%eax,%eax" 2 { target { x86_64-*-* } } } } */
+#endif
+
+int
+test_1 (int x)
+{
+  __asm__ ("foo\t%0,%0" : "+"GPR (x));
+  return x;
+}
+
+int
+test_2 (int x, int y)
+{
+  __asm__ ("foo\t%0,%1" : "="GPR (x) : GPR (y));
+  return x;
+}
diff --git a/gcc/testsuite/gcc.dg/asm-hard-reg-8.c b/gcc/testsuite/gcc.dg/asm-hard-reg-8.c
new file mode 100644 (file)
index 0000000..cda5e3e
--- /dev/null
@@ -0,0 +1,49 @@
+/* { dg-do compile { target aarch64*-*-* arm*-*-* i?86-*-* powerpc*-*-* riscv*-*-* s390*-*-* x86_64-*-* } } */
+
+/* Due to hard register constraints, X must be copied.  */
+
+#if defined (__aarch64__)
+# define GPR1 "{x1}"
+# define GPR2 "{x2}"
+#elif defined (__arm__)
+# define GPR1 "{r1}"
+# define GPR2 "{r2}"
+#elif defined (__i386__)
+# define GPR1 "{eax}"
+# define GPR2 "{ebx}"
+#elif defined (__powerpc__) || defined (__POWERPC__)
+# define GPR1 "{r4}"
+# define GPR2 "{r5}"
+#elif defined (__riscv)
+# define GPR1 "{t1}"
+# define GPR2 "{t2}"
+#elif defined (__s390__)
+# define GPR1 "{r0}"
+# define GPR2 "{r1}"
+#elif defined (__x86_64__)
+# define GPR1 "{eax}"
+# define GPR2 "{ebx}"
+#endif
+
+#define TEST(T) \
+int \
+test_##T (T x) \
+{ \
+  int out; \
+  __asm__ ("foo" : "=r" (out) : GPR1 (x), GPR2 (x)); \
+  return out; \
+}
+
+TEST(char)
+TEST(short)
+TEST(int)
+TEST(long)
+
+int
+test_subreg (long x)
+{
+  int out;
+  short subreg_x = x;
+  __asm__ ("foo" : "=r" (out) : GPR1 (x), GPR2 (subreg_x));
+  return out;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/asm-hard-reg-1.c b/gcc/testsuite/gcc.target/aarch64/asm-hard-reg-1.c
new file mode 100644 (file)
index 0000000..7441dd7
--- /dev/null
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+/* Ensure that we error out in case no hard regs are available for an operand
+   with constraint y.  The position/order of the y-constrained operand does not
+   matter.  */
+
+void
+test (void)
+{
+  int x, a, b, c, d, e, f, g, h;
+
+  __asm__ __volatile__ ("" :
+      "={v0}" (a),
+      "={v1}" (b),
+      "={v2}" (c),
+      "={v3}" (d),
+      "={v4}" (e),
+      "={v5}" (f),
+      "={v6}" (g),
+      "={v7}" (h));
+
+  __asm__ __volatile__ ("" : /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+      "=y" (x),
+      "={v0}" (a),
+      "={v1}" (b),
+      "={v2}" (c),
+      "={v3}" (d),
+      "={v4}" (e),
+      "={v5}" (f),
+      "={v6}" (g),
+      "={v7}" (h));
+
+  __asm__ __volatile__ ("" : /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+      "={v0}" (a),
+      "={v1}" (b),
+      "={v2}" (c),
+      "={v3}" (d),
+      "=y" (x),
+      "={v4}" (e),
+      "={v5}" (f),
+      "={v6}" (g),
+      "={v7}" (h));
+
+  __asm__ __volatile__ ("" : /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+      "={v0}" (a),
+      "={v1}" (b),
+      "={v2}" (c),
+      "={v3}" (d),
+      "={v4}" (e),
+      "={v5}" (f),
+      "={v6}" (g),
+      "={v7}" (h),
+      "=y" (x));
+}
diff --git a/gcc/testsuite/gcc.target/i386/asm-hard-reg-1.c b/gcc/testsuite/gcc.target/i386/asm-hard-reg-1.c
new file mode 100644 (file)
index 0000000..8080f56
--- /dev/null
@@ -0,0 +1,80 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+void
+test (void)
+{
+  int x, y;
+
+  __asm__ __volatile__ ("" : "=a" (x), "={rbx}" (y));
+  __asm__ __volatile__ ("" : "=a" (x), "={rax}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=a" (x) : "{rax}" (y));
+  __asm__ __volatile__ ("" : "=&a" (x) : "{rax}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "a" (x), "{rax}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rbx}" (x), "=a" (y));
+  __asm__ __volatile__ ("" : "={rax}" (x), "=a" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rax}" (x) : "a" (y));
+  __asm__ __volatile__ ("" : "=&{rax}" (x) : "a" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rax}" (x), "a" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "=b" (x), "={rax}" (y));
+  __asm__ __volatile__ ("" : "=b" (x), "={rbx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=b" (x) : "{rbx}" (y));
+  __asm__ __volatile__ ("" : "=&b" (x) : "{rbx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "b" (x), "{rbx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rax}" (x), "=b" (y));
+  __asm__ __volatile__ ("" : "={rbx}" (x), "=b" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rbx}" (x) : "b" (y));
+  __asm__ __volatile__ ("" : "=&{rbx}" (x) : "b" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rbx}" (x), "b" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "=c" (x), "={rax}" (y));
+  __asm__ __volatile__ ("" : "=c" (x), "={rcx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=c" (x) : "{rcx}" (y));
+  __asm__ __volatile__ ("" : "=&c" (x) : "{rcx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "c" (x), "{rcx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rax}" (x), "=c" (y));
+  __asm__ __volatile__ ("" : "={rcx}" (x), "=c" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rcx}" (x) : "c" (y));
+  __asm__ __volatile__ ("" : "=&{rcx}" (x) : "c" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rcx}" (x), "c" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "=d" (x), "={rax}" (y));
+  __asm__ __volatile__ ("" : "=d" (x), "={rdx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=d" (x) : "{rdx}" (y));
+  __asm__ __volatile__ ("" : "=&d" (x) : "{rdx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "d" (x), "{rdx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rax}" (x), "=d" (y));
+  __asm__ __volatile__ ("" : "={rdx}" (x), "=d" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rdx}" (x) : "d" (y));
+  __asm__ __volatile__ ("" : "=&{rdx}" (x) : "d" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rdx}" (x), "d" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "=S" (x), "={rax}" (y));
+  __asm__ __volatile__ ("" : "=S" (x), "={rsi}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=S" (x) : "{rsi}" (y));
+  __asm__ __volatile__ ("" : "=&S" (x) : "{rsi}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "S" (x), "{rsi}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rax}" (x), "=S" (y));
+  __asm__ __volatile__ ("" : "={rsi}" (x), "=S" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rsi}" (x) : "S" (y));
+  __asm__ __volatile__ ("" : "=&{rsi}" (x) : "S" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rsi}" (x), "S" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "=D" (x), "={rax}" (y));
+  __asm__ __volatile__ ("" : "=D" (x), "={rdi}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=D" (x) : "{rdi}" (y));
+  __asm__ __volatile__ ("" : "=&D" (x) : "{rdi}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "D" (x), "{rdi}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rax}" (x), "=D" (y));
+  __asm__ __volatile__ ("" : "={rdi}" (x), "=D" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rdi}" (x) : "D" (y));
+  __asm__ __volatile__ ("" : "=&{rdi}" (x) : "D" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rdi}" (x), "D" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+}
diff --git a/gcc/testsuite/gcc.target/i386/asm-hard-reg-2.c b/gcc/testsuite/gcc.target/i386/asm-hard-reg-2.c
new file mode 100644 (file)
index 0000000..b35cf53
--- /dev/null
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+void
+test (void)
+{
+  int x, y, yy;
+#ifdef __x86_64__
+  int z __attribute__ ((mode (TI)));
+#else
+  long z;
+#endif
+
+  __asm__ __volatile__ ("" : "=A" (z), "={rbx}" (y));
+  __asm__ __volatile__ ("" : "=A" (z), "={rax}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=A" (z), "={rdx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=A" (z) : "{rax}" (y));
+  __asm__ __volatile__ ("" : "=A" (z) : "{rdx}" (y));
+  __asm__ __volatile__ ("" : "=&A" (z) : "{rax}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=&A" (z) : "{rdx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "A" (z), "{rax}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "A" (z), "{rdx}" (y)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  __asm__ __volatile__ ("" : "={rbx}" (y), "=A" (z));
+  __asm__ __volatile__ ("" : "={rax}" (y), "=A" (z)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rdx}" (y), "=A" (z)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "={rax}" (y) : "A" (z));
+  __asm__ __volatile__ ("" : "={rdx}" (y) : "A" (z));
+  __asm__ __volatile__ ("" : "=&{rax}" (y) : "A" (z)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=&{rdx}" (y) : "A" (z)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rax}" (y), "A" (z)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" :: "{rdx}" (y), "A" (z)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+
+  /* Note, we do not error for */
+  __asm__ __volatile__ ("" : "=A" (x), "={rax}" (y));
+  __asm__ __volatile__ ("" : "=A" (x), "={rdx}" (y));
+  /* This is due to how constraint A is implemented.  RA has the freedom to
+     choose between rax or rdx for operand 0 since x fits into a single
+     register and does not require a register pair.  Of course, we error out if
+     rax and rdx are taken by other operands as in the following:  */
+  __asm__ __volatile__ ("" : "=A" (x), "={rax}" (y), "={rdx}" (yy)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+  __asm__ __volatile__ ("" : "=A" (x), "={rdx}" (y), "={rax}" (yy)); /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+}
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 (file)
index 0000000..671c0ed
--- /dev/null
@@ -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 (file)
index 0000000..a892fe8
--- /dev/null
@@ -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 (file)
index 0000000..5df37b5
--- /dev/null
@@ -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/gcc.target/s390/asm-hard-reg-4.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-4.c
new file mode 100644 (file)
index 0000000..29927ce
--- /dev/null
@@ -0,0 +1,6 @@
+/* { dg-do run { target lp64 } } */
+/* { dg-options "-O2 -march=z13 -mzarch" } */
+
+/* Test TARGET_MD_ASM_ADJUST for z13 and long double.  */
+
+#include "asm-hard-reg-longdouble.h"
diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-5.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-5.c
new file mode 100644 (file)
index 0000000..eaf34d9
--- /dev/null
@@ -0,0 +1,6 @@
+/* { dg-do run { target lp64 } } */
+/* { dg-options "-O2 -march=z14 -mzarch" } */
+
+/* Test TARGET_MD_ASM_ADJUST for z14 and long double.  */
+
+#include "asm-hard-reg-longdouble.h"
diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-6.c b/gcc/testsuite/gcc.target/s390/asm-hard-reg-6.c
new file mode 100644 (file)
index 0000000..d012966
--- /dev/null
@@ -0,0 +1,152 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+void
+test (void)
+{
+  // GPRs
+  {
+    int a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p;
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14"
+        : "=r" (a),
+          "=r" (b),
+          "=r" (c),
+          "=r" (d),
+          "=r" (e),
+          "=r" (f),
+          "=r" (g),
+          "=r" (h),
+          "=r" (i),
+          "=r" (j),
+          "=r" (k),
+          "=r" (l),
+          "=r" (m),
+          "=r" (n),
+          "=r" (o));
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14"
+        : "={r0}" (a),
+          "={r1}" (b),
+          "={r2}" (c),
+          "={r3}" (d),
+          "={r4}" (e),
+          "={r5}" (f),
+          "={r6}" (g),
+          "={r7}" (h),
+          "={r8}" (i),
+          "={r9}" (j),
+          "={r10}" (k),
+          "={r11}" (l),
+          "={r12}" (m),
+          "={r13}" (n),
+          "={r14}" (o));
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15" /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+        : "=r" (a),
+          "=r" (b),
+          "=r" (c),
+          "=r" (d),
+          "=r" (e),
+          "=r" (f),
+          "=r" (g),
+          "=r" (h),
+          "=r" (i),
+          "=r" (j),
+          "=r" (k),
+          "=r" (l),
+          "=r" (m),
+          "=r" (n),
+          "=r" (o),
+          "=r" (p));
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15" /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+        : "=r" (a),
+          "=r" (b),
+          "=r" (c),
+          "=r" (d),
+          "=r" (e),
+          "=r" (f),
+          "=r" (g),
+          "=r" (h),
+          "=r" (i),
+          "=r" (j),
+          "=r" (k),
+          "=r" (l),
+          "=r" (m),
+          "=r" (n),
+          "=r" (o),
+          "={r4}" (p));
+  }
+
+  // FPRs
+  {
+    float a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q;
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15"
+        : "=f" (a),
+          "=f" (b),
+          "=f" (c),
+          "=f" (d),
+          "=f" (e),
+          "=f" (f),
+          "=f" (g),
+          "=f" (h),
+          "=f" (i),
+          "=f" (j),
+          "=f" (k),
+          "=f" (l),
+          "=f" (m),
+          "=f" (n),
+          "=f" (o),
+          "=f" (p));
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15"
+        : "={f0}" (a),
+          "={f1}" (b),
+          "={f2}" (c),
+          "={f3}" (d),
+          "={f4}" (e),
+          "={f5}" (f),
+          "={f6}" (g),
+          "={f7}" (h),
+          "={f8}" (i),
+          "={f9}" (j),
+          "={f10}" (k),
+          "={f11}" (l),
+          "={f12}" (m),
+          "={f13}" (n),
+          "={f14}" (o),
+          "={f15}" (p));
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16" /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+        : "=f" (a),
+          "=f" (b),
+          "=f" (c),
+          "=f" (d),
+          "=f" (e),
+          "=f" (f),
+          "=f" (g),
+          "=f" (h),
+          "=f" (i),
+          "=f" (j),
+          "=f" (k),
+          "=f" (l),
+          "=f" (m),
+          "=f" (n),
+          "=f" (o),
+          "=f" (p),
+          "=f" (q));
+    __asm__ __volatile__ ("%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16" /* { dg-error "'asm' operand has impossible constraints or there are not enough registers" } */
+        : "=f" (a),
+          "=f" (b),
+          "=f" (c),
+          "=f" (d),
+          "=f" (e),
+          "=f" (f),
+          "=f" (g),
+          "=f" (h),
+          "=f" (i),
+          "=f" (j),
+          "=f" (k),
+          "=f" (l),
+          "=f" (m),
+          "=f" (n),
+          "=f" (o),
+          "=f" (p),
+          "={f4}" (q));
+  }
+}
diff --git a/gcc/testsuite/gcc.target/s390/asm-hard-reg-longdouble.h b/gcc/testsuite/gcc.target/s390/asm-hard-reg-longdouble.h
new file mode 100644 (file)
index 0000000..9f4adad
--- /dev/null
@@ -0,0 +1,18 @@
+__attribute__ ((noipa))
+long double
+test_longdouble (long double x)
+{
+  long double y;
+  asm ("sqxbr\t%0,%1" : "={f4}" (y) : "{f5}" (x));
+  return y;
+}
+
+int
+main (void)
+{
+  long double x = test_longdouble (42.L);
+  long double y = 6.48074069840786023096596743608799656681773277430814773408787249757445105002862106857719481922686100006103515625L;
+  if (x != y)
+    __builtin_abort ();
+  return 0;
+}