diff mbox series

diagnose unsupported uses of hardware register variables (PR 88000)

Message ID edacc336-963e-37e1-804d-44870c12bd55@gmail.com
State New
Headers show
Series diagnose unsupported uses of hardware register variables (PR 88000) | expand

Commit Message

Martin Sebor Nov. 14, 2018, 4:11 a.m. UTC
In PR 88000 the reporter expects to be able to use an explicit
register local variable in a context where it isn't supported
i.e., for something other than an input or output of an asm
statement: namely to pass it as argument to a user-defined
function.  GCC emits unexpected object code in this case which
the reporter thought was a GCC bug.

Since explicit register global variables are supported in these
contexts, using the same kind of local variable seems like an easy
mistake to make.  To help users avoid it the attached patch adds
a warning that points it out.

Tested on x86_64-linux.  A number of i386 tests use explicit
register variables in calls to GCC library functions like
__builtin_fabsq.  I prune the warnings from those but if using
explicit register vars in those functions is meant to be supported
despite what the manual says the patch will need tweaking (as will
the manual).

Martin

Comments

Alexander Monakov Nov. 14, 2018, 9:39 a.m. UTC | #1
On Tue, 13 Nov 2018, Martin Sebor wrote:

> In PR 88000 the reporter expects to be able to use an explicit
> register local variable in a context where it isn't supported
> i.e., for something other than an input or output of an asm
> statement: namely to pass it as argument to a user-defined
> function.  GCC emits unexpected object code in this case which
> the reporter thought was a GCC bug.

I appreciate warnings for misuse of extensions in general, but in
this particular case I think GCC's behavior is misdesigned, so
instead of enshrining a bad engineering choice in a warning and
in the manual, I'd rather see GCC implement the extension sanely.

Merely changing a normal auto variable to a register asm variable
should not invalidate the program. As the manual says, it should
amount to providing a hint to the register allocator, at most.

Have a look at PR 87984, where code is miscompiled despite following
the documentation to the letter. This is because we lower accesses to
register variables when transitioning from GIMPLE to RTL incorrectly.
Fixing that should make the warning unnecessary. I hope I can work on
that before stage 4.

I think LLVM is doing the right thing there, and so should we.

Thanks.
Alexander
Jakub Jelinek Nov. 14, 2018, 9:47 a.m. UTC | #2
On Tue, Nov 13, 2018 at 09:11:41PM -0700, Martin Sebor wrote:
> --- gcc/c-family/c-warn.c	(revision 266086)
> +++ gcc/c-family/c-warn.c	(working copy)
> @@ -2609,3 +2609,39 @@ warn_for_multistatement_macros (location_t body_lo
>      inform (guard_loc, "some parts of macro expansion are not guarded by "
>  	    "this %qs clause", guard_tinfo_to_string (keyword));
>  }
> +
> +
> +/* Diagnose unsuported use of explicit hardware register variable ARG
> +   as an argument ARGNO to function FNDECL.  */
> +
> +void
> +warn_hw_reg_arg (tree fndecl, int argno, tree arg)
> +{
> +  if (!fndecl)
> +    return;
> +
> +  /* Avoid diagnosing GCC intrinsics with no library fallbacks.  */
> +  if (fndecl_built_in_p (fndecl)
> +      && DECL_IS_BUILTIN (fndecl)
> +      && !c_decl_implicit (fndecl)
> +      && !DECL_ASSEMBLER_NAME_SET_P (fndecl))
> +    return;
> +
> +  /* Also avoid diagnosing always_inline functions since those are
> +     often used to implement vectorization intrinsics that make use
> +     of hardware register variables.  */
> +  if (lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl)))
> +    return;
> +
> +  /* Diagnose uses of local variables declared asm register.  */
> +  STRIP_ANY_LOCATION_WRAPPER (arg);
> +  if (VAR_P (arg)
> +      && !TREE_STATIC (arg)
> +      && DECL_HARD_REGISTER (arg)
> +      && warning_at (input_location, OPT_Wasm_register_var,
> +		     "unsupported use of hardware register %qD as "
> +		     "argument %d of %qD",
> +		     arg, argno, fndecl))
> +    inform (DECL_SOURCE_LOCATION (arg),
> +	    "%qD declared here", arg);
> +}

This makes no sense to me.  There is nothing unsupported in passing
a local hard register variable to a function, that is well defined,
and as your testcase changes show, you broke quite some completely valid
testcases with that.

What doesn't work as the reporter expect is assumption that local hard
register variables that live across function calls must have their values
preserved; they can be modified by the callees.

	Jakub
Segher Boessenkool Nov. 14, 2018, 11:35 a.m. UTC | #3
On Wed, Nov 14, 2018 at 10:47:57AM +0100, Jakub Jelinek wrote:
> This makes no sense to me.  There is nothing unsupported in passing
> a local hard register variable to a function, that is well defined,
> and as your testcase changes show, you broke quite some completely valid
> testcases with that.

What could perhaps be useful is a warning for a local register var that
is not used in any asm?

> What doesn't work as the reporter expect is assumption that local hard
> register variables that live across function calls must have their values
> preserved; they can be modified by the callees.

It would be really nice if we could fix that :-)


Segher
Jakub Jelinek Nov. 14, 2018, 11:40 a.m. UTC | #4
On Wed, Nov 14, 2018 at 05:35:06AM -0600, Segher Boessenkool wrote:
> On Wed, Nov 14, 2018 at 10:47:57AM +0100, Jakub Jelinek wrote:
> > This makes no sense to me.  There is nothing unsupported in passing
> > a local hard register variable to a function, that is well defined,
> > and as your testcase changes show, you broke quite some completely valid
> > testcases with that.
> 
> What could perhaps be useful is a warning for a local register var that
> is not used in any asm?
> 
> > What doesn't work as the reporter expect is assumption that local hard
> > register variables that live across function calls must have their values
> > preserved; they can be modified by the callees.
> 
> It would be really nice if we could fix that :-)

You mean for all local hard register variables living across function calls
save them into temporary and restore them around the calls?
One issue is that those variables aren't in SSA form, so liveness analysis
is harder.  And, would we want to have an exception for the stack pointer?
I mean there is no need for register void *sp __asm ("rsp"); to be
saved/restored that way, it shouldn't change value across function calls.
Plus, as has been mentioned, function calls aren't the only issue here,
e.g. division/modulo etc. that require specific hard registers might clobber
them too.

	Jakub
Segher Boessenkool Nov. 14, 2018, 11:50 a.m. UTC | #5
On Wed, Nov 14, 2018 at 12:40:01PM +0100, Jakub Jelinek wrote:
> On Wed, Nov 14, 2018 at 05:35:06AM -0600, Segher Boessenkool wrote:
> > On Wed, Nov 14, 2018 at 10:47:57AM +0100, Jakub Jelinek wrote:
> > > This makes no sense to me.  There is nothing unsupported in passing
> > > a local hard register variable to a function, that is well defined,
> > > and as your testcase changes show, you broke quite some completely valid
> > > testcases with that.
> > 
> > What could perhaps be useful is a warning for a local register var that
> > is not used in any asm?
> > 
> > > What doesn't work as the reporter expect is assumption that local hard
> > > register variables that live across function calls must have their values
> > > preserved; they can be modified by the callees.
> > 
> > It would be really nice if we could fix that :-)
> 
> You mean for all local hard register variables living across function calls
> save them into temporary and restore them around the calls?
> One issue is that those variables aren't in SSA form, so liveness analysis
> is harder.  And, would we want to have an exception for the stack pointer?
> I mean there is no need for register void *sp __asm ("rsp"); to be
> saved/restored that way, it shouldn't change value across function calls.
> Plus, as has been mentioned, function calls aren't the only issue here,
> e.g. division/modulo etc. that require specific hard registers might clobber
> them too.

I was thinking put them in pseudos always, just copy them to the hard reg
right before the asm statements that use them (and the other way around,
for outputs).


Segher
Jakub Jelinek Nov. 14, 2018, noon UTC | #6
On Wed, Nov 14, 2018 at 05:50:39AM -0600, Segher Boessenkool wrote:
> > You mean for all local hard register variables living across function calls
> > save them into temporary and restore them around the calls?
> > One issue is that those variables aren't in SSA form, so liveness analysis
> > is harder.  And, would we want to have an exception for the stack pointer?
> > I mean there is no need for register void *sp __asm ("rsp"); to be
> > saved/restored that way, it shouldn't change value across function calls.
> > Plus, as has been mentioned, function calls aren't the only issue here,
> > e.g. division/modulo etc. that require specific hard registers might clobber
> > them too.
> 
> I was thinking put them in pseudos always, just copy them to the hard reg
> right before the asm statements that use them (and the other way around,
> for outputs).

Wouldn't that break all the code that does:
void *
retsp (void)
{
  register void *sp __asm ("sp");
  return sp;
}
(or any other (usually fixed) reg, where the user code expects to just
read the value of that register)?
Storing uninitialized value into it would break the program miserably.

	Jakub
Segher Boessenkool Nov. 14, 2018, 12:11 p.m. UTC | #7
On Wed, Nov 14, 2018 at 01:00:43PM +0100, Jakub Jelinek wrote:
> On Wed, Nov 14, 2018 at 05:50:39AM -0600, Segher Boessenkool wrote:
> > > You mean for all local hard register variables living across function calls
> > > save them into temporary and restore them around the calls?
> > > One issue is that those variables aren't in SSA form, so liveness analysis
> > > is harder.  And, would we want to have an exception for the stack pointer?
> > > I mean there is no need for register void *sp __asm ("rsp"); to be
> > > saved/restored that way, it shouldn't change value across function calls.
> > > Plus, as has been mentioned, function calls aren't the only issue here,
> > > e.g. division/modulo etc. that require specific hard registers might clobber
> > > them too.
> > 
> > I was thinking put them in pseudos always, just copy them to the hard reg
> > right before the asm statements that use them (and the other way around,
> > for outputs).
> 
> Wouldn't that break all the code that does:
> void *
> retsp (void)
> {
>   register void *sp __asm ("sp");
>   return sp;
> }
> (or any other (usually fixed) reg, where the user code expects to just
> read the value of that register)?

It doesn't "break" anything because it currently isn't guaranteed to work
either.  There is __builtin_frame_address(0) for this of course (well,
almost the same semantics, and it is actually well-defined and actually
works).  Is there user code that tries to do this with a register var?

> Storing uninitialized value into it would break the program miserably.

There is no asm statement here.


Segher
Jakub Jelinek Nov. 14, 2018, 12:14 p.m. UTC | #8
On Wed, Nov 14, 2018 at 06:11:37AM -0600, Segher Boessenkool wrote:
> It doesn't "break" anything because it currently isn't guaranteed to work
> either.  There is __builtin_frame_address(0) for this of course (well,
> almost the same semantics, and it is actually well-defined and actually
> works).  Is there user code that tries to do this with a register var?

I've seen such code many times over the years, don't have time to perform
code searches for such kind of constructs now though.

	Jakub
Martin Sebor Nov. 15, 2018, 6:31 p.m. UTC | #9
On 11/14/2018 02:47 AM, Jakub Jelinek wrote:
> On Tue, Nov 13, 2018 at 09:11:41PM -0700, Martin Sebor wrote:
>> --- gcc/c-family/c-warn.c	(revision 266086)
>> +++ gcc/c-family/c-warn.c	(working copy)
>> @@ -2609,3 +2609,39 @@ warn_for_multistatement_macros (location_t body_lo
>>      inform (guard_loc, "some parts of macro expansion are not guarded by "
>>  	    "this %qs clause", guard_tinfo_to_string (keyword));
>>  }
>> +
>> +
>> +/* Diagnose unsuported use of explicit hardware register variable ARG
>> +   as an argument ARGNO to function FNDECL.  */
>> +
>> +void
>> +warn_hw_reg_arg (tree fndecl, int argno, tree arg)
>> +{
>> +  if (!fndecl)
>> +    return;
>> +
>> +  /* Avoid diagnosing GCC intrinsics with no library fallbacks.  */
>> +  if (fndecl_built_in_p (fndecl)
>> +      && DECL_IS_BUILTIN (fndecl)
>> +      && !c_decl_implicit (fndecl)
>> +      && !DECL_ASSEMBLER_NAME_SET_P (fndecl))
>> +    return;
>> +
>> +  /* Also avoid diagnosing always_inline functions since those are
>> +     often used to implement vectorization intrinsics that make use
>> +     of hardware register variables.  */
>> +  if (lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl)))
>> +    return;
>> +
>> +  /* Diagnose uses of local variables declared asm register.  */
>> +  STRIP_ANY_LOCATION_WRAPPER (arg);
>> +  if (VAR_P (arg)
>> +      && !TREE_STATIC (arg)
>> +      && DECL_HARD_REGISTER (arg)
>> +      && warning_at (input_location, OPT_Wasm_register_var,
>> +		     "unsupported use of hardware register %qD as "
>> +		     "argument %d of %qD",
>> +		     arg, argno, fndecl))
>> +    inform (DECL_SOURCE_LOCATION (arg),
>> +	    "%qD declared here", arg);
>> +}
>
> This makes no sense to me.  There is nothing unsupported in passing
> a local hard register variable to a function, that is well defined,

PR 88000 was resolved as invalid quoting the following sentence
from the manual as the rationale:

   The only supported use for this feature is to specify registers
   for input and output operands when calling Extended asm.

If these variables are meant to be supported in other contexts
(such as in calls to built-ins or even user-defined functions)
the manual needs to be clarified.

> and as your testcase changes show, you broke quite some completely valid
> testcases with that.

Let's not be melodramatic.  Nothing was "broken" by a proposed
warning.  But I did say that "if using explicit register vars in
those functions is meant to be supported despite what the manual
says the patch will need tweaking (as will the manual)."

> What doesn't work as the reporter expect is assumption that local hard
> register variables that live across function calls must have their values
> preserved; they can be modified by the callees.

I'm not sure I understand how what you described as supported
is different from what the test case in 88000 does: i.e., pass
the value of a register variable to a function and expect its
value to be unchanged.  If the value happens to be preserved
in some cases/for some registers but not for others, unless
the two sets can be reliably distinguished it seems like a good
candidate for a warning, to help users fall into the same trap.
Users who know what they're doing can easily suppress the warning
and others will fix their code.  Is there a way to do distinguish
the two sets of cases?

Martin
Martin Sebor Nov. 15, 2018, 10:34 p.m. UTC | #10
On 11/14/2018 02:39 AM, Alexander Monakov wrote:
> On Tue, 13 Nov 2018, Martin Sebor wrote:
>
>> In PR 88000 the reporter expects to be able to use an explicit
>> register local variable in a context where it isn't supported
>> i.e., for something other than an input or output of an asm
>> statement: namely to pass it as argument to a user-defined
>> function.  GCC emits unexpected object code in this case which
>> the reporter thought was a GCC bug.
>
> I appreciate warnings for misuse of extensions in general, but in
> this particular case I think GCC's behavior is misdesigned, so
> instead of enshrining a bad engineering choice in a warning and
> in the manual, I'd rather see GCC implement the extension sanely.
>
> Merely changing a normal auto variable to a register asm variable
> should not invalidate the program. As the manual says, it should
> amount to providing a hint to the register allocator, at most.
>
> Have a look at PR 87984, where code is miscompiled despite following
> the documentation to the letter. This is because we lower accesses to
> register variables when transitioning from GIMPLE to RTL incorrectly.
> Fixing that should make the warning unnecessary. I hope I can work on
> that before stage 4.
>
> I think LLVM is doing the right thing there, and so should we.

That would indeed be preferable but it's not something I expect
to have the cycles to work on.  I put the patch together only
because it seemed like an easy way to help keep users from
shooting themselves in the foot (to borrow the phrase from
the comment in PR 88000).

If there's consensus that the general use case of passing hard
registers to functions should be supported then I suggest to
acknowledge PR 88000 as a bug.  We can discuss whether it's
possible and worthwhile to warn on the unsupported subset of
the use cases.  In any case, I think the least we can for now
is to clarify in the manual what that supported subset is,
since as I (and others) read it, passing hard registers to
functions is not.

Martin
diff mbox series

Patch

PR c/88000 - Different local vars regs order may produce different code

gcc/c/ChangeLog:

	PR c/88000
	* c-typeck.c (convert_arguments):  Call warn_hw_reg_arg to diagnose
	unsupported uses of hardware register variables.

gcc/c-family/ChangeLog:

	PR c/88000
	* c.opt (-Wasm-register-var): New option.
	* c-common.h (warn_hw_reg_arg): Declare.
	* c-warn.c (warn_hw_reg_arg): Define.

gcc/cp/ChangeLog:

	PR c/88000
	* call.c (build_over_call): Call warn_hw_reg_arg.

gcc/testsuite/ChangeLog:

	PR c/88000
	* c-c++-common/Wasm-register.c: New test.
	* g++.dg/ext/Wasm-register.C: Same.
	* gcc.dg/torture/pr71762-3.c: Prune excess diagnostics.
	* gcc.target/i386/attr-returns_twice-1.c: Same.
	* gcc.target/i386/avx512dq-abs-copysign-1.c: Same.
	* gcc.target/i386/avx512dq-concatv2si-1.c: Same.
	* gcc.target/i386/avx512vl-abs-copysign-1.c: Same.
	* gcc.target/i386/avx512vl-abs-copysign-2.c: Same.
	* gcc.target/i386/avx512vl-concatv2si-1.c: Same.

gcc/ChangeLog:

	PR c/88000
	* doc/invoke.texi (-Wasm-register-var): Document.

Index: gcc/c/c-typeck.c
===================================================================
--- gcc/c/c-typeck.c	(revision 266086)
+++ gcc/c/c-typeck.c	(working copy)
@@ -3543,6 +3543,9 @@  convert_arguments (location_t loc, vec<location_t>
 	/* Convert `short' and `char' to full-size `int'.  */
 	parmval = default_conversion (val);
 
+      /* Diagnose uses of local variables declared asm register.  */
+      warn_hw_reg_arg (fundecl, parmnum + 1, val);
+
       (*values)[parmnum] = parmval;
       if (parmval == error_mark_node)
 	error_args = true;
Index: gcc/c-family/c.opt
===================================================================
--- gcc/c-family/c.opt	(revision 266086)
+++ gcc/c-family/c.opt	(working copy)
@@ -338,6 +338,10 @@  Warray-bounds=
 LangEnabledBy(C ObjC C++ LTO ObjC++,Wall,1,0)
 ; in common.opt
 
+Wasm-register-var
+C ObjC C++ ObjC++ Var(warn_asm_register_var) Warning Init(1)
+Warn for unsupported uses of variables declared asm register.
+
 Wassign-intercept
 ObjC ObjC++ Var(warn_assign_intercept) Warning
 Warn whenever an Objective-C assignment is being intercepted by the garbage collector.
Index: gcc/c-family/c-common.h
===================================================================
--- gcc/c-family/c-common.h	(revision 266086)
+++ gcc/c-family/c-common.h	(working copy)
@@ -1321,6 +1321,7 @@  extern bool diagnose_mismatched_attributes (tree,
 extern tree do_warn_duplicated_branches_r (tree *, int *, void *);
 extern void warn_for_multistatement_macros (location_t, location_t,
 					    location_t, enum rid);
+extern void warn_hw_reg_arg (tree, int, tree);
 
 /* In c-attribs.c.  */
 extern bool attribute_takes_identifier_p (const_tree);
Index: gcc/c-family/c-warn.c
===================================================================
--- gcc/c-family/c-warn.c	(revision 266086)
+++ gcc/c-family/c-warn.c	(working copy)
@@ -2609,3 +2609,39 @@  warn_for_multistatement_macros (location_t body_lo
     inform (guard_loc, "some parts of macro expansion are not guarded by "
 	    "this %qs clause", guard_tinfo_to_string (keyword));
 }
+
+
+/* Diagnose unsuported use of explicit hardware register variable ARG
+   as an argument ARGNO to function FNDECL.  */
+
+void
+warn_hw_reg_arg (tree fndecl, int argno, tree arg)
+{
+  if (!fndecl)
+    return;
+
+  /* Avoid diagnosing GCC intrinsics with no library fallbacks.  */
+  if (fndecl_built_in_p (fndecl)
+      && DECL_IS_BUILTIN (fndecl)
+      && !c_decl_implicit (fndecl)
+      && !DECL_ASSEMBLER_NAME_SET_P (fndecl))
+    return;
+
+  /* Also avoid diagnosing always_inline functions since those are
+     often used to implement vectorization intrinsics that make use
+     of hardware register variables.  */
+  if (lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl)))
+    return;
+
+  /* Diagnose uses of local variables declared asm register.  */
+  STRIP_ANY_LOCATION_WRAPPER (arg);
+  if (VAR_P (arg)
+      && !TREE_STATIC (arg)
+      && DECL_HARD_REGISTER (arg)
+      && warning_at (input_location, OPT_Wasm_register_var,
+		     "unsupported use of hardware register %qD as "
+		     "argument %d of %qD",
+		     arg, argno, fndecl))
+    inform (DECL_SOURCE_LOCATION (arg),
+	    "%qD declared here", arg);
+}
Index: gcc/cp/call.c
===================================================================
--- gcc/cp/call.c	(revision 266086)
+++ gcc/cp/call.c	(working copy)
@@ -8111,6 +8111,9 @@  build_over_call (struct z_candidate *cand, int fla
 
       conv = convs[i];
 
+      /* Diagnose uses of local variables declared asm register.  */
+      warn_hw_reg_arg (cand->fn, i + 1, arg);
+
       /* If the argument is NULL and used to (implicitly) instantiate a
          template function (and bind one of the template arguments to
          the type of 'long int'), we don't want to warn about passing NULL
@@ -8207,6 +8210,9 @@  build_over_call (struct z_candidate *cand, int fla
   for (; arg_index < vec_safe_length (args); ++arg_index)
     {
       tree a = (*args)[arg_index];
+      /* Diagnose uses of local variables declared asm register.  */
+      warn_hw_reg_arg (cand->fn, arg_index + 1, a);
+
       if ((magic == 3 && arg_index == 2) || magic == 2)
 	{
 	  /* Do no conversions for certain magic varargs.  */
Index: gcc/doc/invoke.texi
===================================================================
--- gcc/doc/invoke.texi	(revision 266086)
+++ gcc/doc/invoke.texi	(working copy)
@@ -283,6 +283,7 @@  Objective-C and Objective-C++ Dialects}.
 -Walloc-zero  -Walloc-size-larger-than=@var{byte-size}
 -Walloca  -Walloca-larger-than=@var{byte-size} @gol
 -Wno-aggressive-loop-optimizations  -Warray-bounds  -Warray-bounds=@var{n} @gol
+-Wasm-register-var @gol
 -Wno-attributes -Wno-attribute-alias @gol
 -Wbool-compare  -Wbool-operation @gol
 -Wno-builtin-declaration-mismatch @gol
@@ -5868,6 +5869,13 @@  pointers. This warning level may give a larger num
 false positives and is deactivated by default.
 @end table
 
+@item -Wno-asm-register-var
+@opindex Wasm-register-var
+@opindex Wno-asm-register-var
+Warn about unsupported uses of hardware register variables, or those declared
+using the @code{asm register} extension (@pxref{Explicit Register Variables}).
+This warning is enabled by default.
+
 @item -Wattribute-alias=@var{n}
 @itemx -Wno-attribute-alias
 @opindex -Wattribute-alias
Index: gcc/testsuite/c-c++-common/Wasm-register.c
===================================================================
--- gcc/testsuite/c-c++-common/Wasm-register.c	(nonexistent)
+++ gcc/testsuite/c-c++-common/Wasm-register.c	(working copy)
@@ -0,0 +1,120 @@ 
+/* PR 88000 - Different local vars regs order may produce different code
+   Verify that passing asm register variables as arguments to functions
+   triggers a warning.
+   { dg-do compile }
+   { dg-options "-Wno-pedantic" } */
+
+#if __cplusplus
+/* Fake C++ function without a prototype.  */
+void fany (...);
+#else
+void fany ();
+#endif
+
+void fproto (char, int);
+void fellip (int, ...);
+
+register char gcr __asm__ ("r12");
+register long glr __asm__ ("r13");
+
+
+void test_global_no_proto (void)
+{
+  fany (gcr);
+  fany (0, gcr);
+  fany (glr);
+  fany (0, glr);
+}
+
+
+/* Verify that calls to a GCC intrinsic with no library fallback do
+   not trigger a warning.  */
+
+void test_gcc_builtin (void)
+{
+  register char cr __asm__ ("r11") = 1;
+  register int ir __asm__ ("r12") = 22;
+
+  __builtin_clz (cr);
+  __builtin_clz (ir);
+}
+
+
+/* Verify that calls to a GCC intrinsic with a library fallback do
+   trigger a warning.  */
+
+void test_lib_builtin (void)
+{
+  register char cr __asm__ ("r11") = 1;
+  register int ir __asm__ ("r12") = 22;
+
+  __builtin_abs (cr);     /* { dg-warning "unsupported use of hardware register .cr. as argument 1 of .\(int \)?__builtin_abs" } */
+
+  __builtin_abs (ir);     /* { dg-warning "unsupported use of hardware register .ir. as argument 1 " } */
+}
+
+
+void test_local_no_proto (void)
+{
+  register char cr __asm__ ("r11") = 1;
+  register int ir __asm__ ("r12") = 22;
+
+  fany (cr);          /* { dg-warning "unsupported use of hardware register .cr. as argument 1 of .\(void \)?fany" } */
+
+  fany (0, cr);       /* { dg-warning "unsupported use of hardware register .cr. as argument 2 " } */
+
+  fany (ir);          /* { dg-warning "unsupported use of hardware register .ir. as argument 1 " } */
+
+  fany (0, ir);       /* { dg-warning "unsupported use of hardware register .ir. as argument 2 " } */
+}
+
+
+void test_global_proto (void)
+{
+  fproto (gcr, 0);
+  fproto (0, gcr);
+  fproto (glr, 0);
+  fproto (0, glr);
+}
+
+
+void test_local_proto (void)
+{
+  register char cr __asm__ ("r11") = 1;
+  register int ir __asm__ ("r12") = 22;
+
+  fproto (cr, 0);     /* { dg-warning "unsupported use of hardware register .cr. as argument 1 of .\(void \)?fproto" } */
+
+  fproto (0, cr);     /* { dg-warning "unsupported use of hardware register .cr. as argument 2 " } */
+
+  fproto (ir, 0);     /* { dg-warning "unsupported use of hardware register .ir. as argument 1 " } */
+
+  fproto (0, ir);     /* { dg-warning "unsupported use of hardware register .ir. as argument 2 " } */
+}
+
+
+void test_global_ellip (void)
+{
+  fellip (gcr);
+  fellip (glr);
+  fellip (0, gcr);
+  fellip (0, glr);
+}
+
+
+void test_local_ellip (void)
+{
+  register char cr __asm__ ("r11") = 1;
+  register int ir __asm__ ("r12") = 22;
+
+  fellip (cr);        /* { dg-warning "unsupported use of hardware register .cr. as argument 1 of .\(void \)?fellip." } */
+
+  fellip (ir);        /* { dg-warning "unsupported use of hardware register .ir. as argument 1 " } */
+
+  fellip (0, cr);     /* { dg-warning "unsupported use of hardware register .cr. as argument 2 " } */
+
+  fellip (0, ir);     /* { dg-warning "unsupported use of hardware register .ir. as argument 2 " } */
+}
+
+
+/* { dg-prune-output "register used" } */
Index: gcc/testsuite/g++.dg/ext/Wasm-register.C
===================================================================
--- gcc/testsuite/g++.dg/ext/Wasm-register.C	(nonexistent)
+++ gcc/testsuite/g++.dg/ext/Wasm-register.C	(working copy)
@@ -0,0 +1,31 @@ 
+/* PR c/88000 - Different local vars regs order may produce different code
+   Verify that passing asm register variables as arguments to a function
+   template triggers a warning.
+   { dg-do compile }
+   { dg-options "-Wno-pedantic" } */
+
+template <class T>
+void f (T);
+
+register char gcr __asm__ ("r12");
+register long glr __asm__ ("r13");
+
+
+void test_global (void)
+{
+  f (gcr);
+  f (glr);
+}
+
+
+void test_local (void)
+{
+  register char cr __asm__ ("r11") = 1;
+  register int ir __asm__ ("r12") = 22;
+
+  f (cr);          /* { dg-warning "unsupported use of hardware register .cr. as argument 1 of .void f\\\(T\\\) \\\[with T = char]" } */
+
+  f (ir);          /* { dg-warning "unsupported use of hardware register .ir. as argument 1 of .void f\\\(T\\\) \\\[with T = int]" } */
+}
+
+/* { dg-prune-output "register used" } */
Index: gcc/testsuite/gcc.dg/torture/pr71762-3.c
===================================================================
--- gcc/testsuite/gcc.dg/torture/pr71762-3.c	(revision 266086)
+++ gcc/testsuite/gcc.dg/torture/pr71762-3.c	(working copy)
@@ -20,3 +20,5 @@  int main()
     __builtin_abort ();
   return 0;
 }
+
+/* { dg-prune-output "unsupported use of hardware register" } */
Index: gcc/testsuite/gcc.target/i386/attr-returns_twice-1.c
===================================================================
--- gcc/testsuite/gcc.target/i386/attr-returns_twice-1.c	(revision 266086)
+++ gcc/testsuite/gcc.target/i386/attr-returns_twice-1.c	(working copy)
@@ -21,3 +21,5 @@  main (void)
 
   return 0;
 }
+
+/* { dg-prune-output "unsupported use of hardware register" } */
Index: gcc/testsuite/gcc.target/i386/avx512dq-abs-copysign-1.c
===================================================================
--- gcc/testsuite/gcc.target/i386/avx512dq-abs-copysign-1.c	(revision 266086)
+++ gcc/testsuite/gcc.target/i386/avx512dq-abs-copysign-1.c	(working copy)
@@ -69,3 +69,4 @@  f6 (double x)
 /* { dg-final { scan-assembler "vandpd\[^\n\r\]*xmm18" } } */
 /* { dg-final { scan-assembler "vorpd\[^\n\r\]*xmm18" } } */
 /* { dg-final { scan-assembler "vxorpd\[^\n\r\]*xmm18" } } */
+/* { dg-prune-output "unsupported use of hardware register" } */
Index: gcc/testsuite/gcc.target/i386/avx512dq-concatv2si-1.c
===================================================================
--- gcc/testsuite/gcc.target/i386/avx512dq-concatv2si-1.c	(revision 266086)
+++ gcc/testsuite/gcc.target/i386/avx512dq-concatv2si-1.c	(working copy)
@@ -41,3 +41,4 @@  f3 (int x, int *y)
 }
 
 /* { dg-final { scan-assembler-times "vpinsrd\[^\n\r]*\\\$1\[^\n\r]*%xmm16\[^\n\r]*%xmm3" 2 } } */
+/* { dg-prune-output "unsupported use of hardware register" } */
Index: gcc/testsuite/gcc.target/i386/avx512vl-abs-copysign-1.c
===================================================================
--- gcc/testsuite/gcc.target/i386/avx512vl-abs-copysign-1.c	(revision 266086)
+++ gcc/testsuite/gcc.target/i386/avx512vl-abs-copysign-1.c	(working copy)
@@ -69,3 +69,4 @@  f6 (double x)
 /* { dg-final { scan-assembler "vpandq\[^\n\r\]*xmm18" } } */
 /* { dg-final { scan-assembler "vporq\[^\n\r\]*xmm18" } } */
 /* { dg-final { scan-assembler "vpxorq\[^\n\r\]*xmm18" } } */
+/* { dg-prune-output "unsupported use of hardware register" } */
Index: gcc/testsuite/gcc.target/i386/avx512vl-abs-copysign-2.c
===================================================================
--- gcc/testsuite/gcc.target/i386/avx512vl-abs-copysign-2.c	(revision 266086)
+++ gcc/testsuite/gcc.target/i386/avx512vl-abs-copysign-2.c	(working copy)
@@ -47,3 +47,4 @@  f4 (void)
 /* { dg-final { scan-assembler "vpandq\[^\n\r\]*xmm16" } } */
 /* { dg-final { scan-assembler "vporq\[^\n\r\]*xmm16" } } */
 /* { dg-final { scan-assembler "vpxorq\[^\n\r\]*xmm16" } } */
+/* { dg-prune-output "unsupported use of hardware register" } */
Index: gcc/testsuite/gcc.target/i386/avx512vl-concatv2si-1.c
===================================================================
--- gcc/testsuite/gcc.target/i386/avx512vl-concatv2si-1.c	(revision 266086)
+++ gcc/testsuite/gcc.target/i386/avx512vl-concatv2si-1.c	(working copy)
@@ -41,3 +41,4 @@  f3 (int x, int *y)
 }
 
 /* { dg-final { scan-assembler-not "vpinsrd\[^\n\r]*\\\$1\[^\n\r]*%xmm16\[^\n\r]*%xmm3" } } */
+/* { dg-prune-output "unsupported use of hardware register" } */