Hi all,

In this bug we have TER during out-of-ssa misbehaving. A minimal example is:
void foo(unsigned a, float b)
{
  unsigned c = (unsigned) b;      // 1
  register unsigned r0 __asm__("r0") = a; // 2
  register unsigned r1 __asm__("r1") = c; // 3
    __asm__ volatile( "str %[r1], [%[r0]]\n"
                      :
                      : [r0] "r" (r0),
                        [r1] "r" (r1));
}

Statement 1 can produce a libcall to convert float b into an int and TER moves 
it
into statement 3. But the libcall clobbers r0, which we want set to 'a' in 
statement 2.
So r0 gets clobbered by the argument to the conversion libcall.

TER already has code to avoid substituting across function calls and ideally 
we'd teach it
to not substitute expressions that can perform a libcall across register 
variable definitions
where the register can be clobbered in a libcall, but that information is not 
easy to get hold
off in a general way at this level.

So this patch disallows replacement across any local register variable 
definition. It does this
by keeping track of local register definitions encountered in a similar way to 
which calls are
counted for TER purposes.

I hope this is not too big a hammer as local register variables are not very 
widely used and we
only disable replacement across them and it allows us to fix the wrong-code bug 
on some
inline-asm usage scenarios where gcc currently miscompiles code following its 
documented
advice [1]

Bootstrapped and tested on arm-none-linux-gnueabihf, aarch64-none-linux-gnu, 
x86_64.

Is this approach acceptable?

Thanks,
Kyrill

[1] 
https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html#Local-Register-Variables

2016-11-24  Kyrylo Tkachov  <kyrylo.tkac...@arm.com>

    PR target/48863
    PR inline-asm/70184
    * tree-ssa-ter.c (temp_expr_table): Add reg_vars_cnt field.
    (new_temp_expr_table): Initialise reg_vars_cnt.
    (free_temp_expr_table): Release reg_vars_cnt.
    (process_replaceable): Add reg_vars_cnt argument, set reg_vars_cnt
    field of TAB.
    (find_replaceable_in_bb): Use the above to record register variable
    write occurrences and cancel replacement across them.

2016-11-24  Kyrylo Tkachov  <kyrylo.tkac...@arm.com>

    PR target/48863
    PR inline-asm/70184
    * gcc.target/arm/pr48863.c: New test.
commit c53373216f7954e89893c1c7067e053fe6b819e8
Author: Kyrylo Tkachov <kyrylo.tkac...@arm.com>
Date:   Wed Nov 23 16:44:47 2016 +0000

    [TER] PR 48863: Don't replace expressions across local register variable definitions

diff --git a/gcc/testsuite/gcc.target/arm/pr48863.c b/gcc/testsuite/gcc.target/arm/pr48863.c
new file mode 100644
index 0000000..33bc7a4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr48863.c
@@ -0,0 +1,35 @@
+/* PR target/48863.  */
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+
+/* Check that Temporary Expression Replacement does not move a
+  libcall-producing expression across a statement initialising a local
+  register variable.  */
+
+static inline int
+dosvc (int fd, unsigned long high, unsigned low)
+{
+  register int r0 asm("r0") = fd;
+  register int r2 asm("r2") = high;
+  register int r3 asm("r3") = low;
+
+  asm volatile("" : "=r"(r0) : "0"(r0), "r"(r2), "r"(r3));
+  return r0;
+}
+
+struct s
+{
+  int fd;
+  long long length;
+} s = { 2, 0 }, *p = &s;
+
+int
+main (void)
+{
+  unsigned low = p->length & 0xffffffff;
+  unsigned high = p->length / 23;
+
+  if (dosvc (p->fd, high, low) != 2)
+    __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/tree-ssa-ter.c b/gcc/tree-ssa-ter.c
index c7d8b7e..af5d91c 100644
--- a/gcc/tree-ssa-ter.c
+++ b/gcc/tree-ssa-ter.c
@@ -169,6 +169,8 @@ struct temp_expr_table
   bitmap new_replaceable_dependencies;	/* Holding place for pending dep's.  */
   int *num_in_part;			/* # of ssa_names in a partition.  */
   int *call_cnt;			/* Call count at definition.  */
+  int *reg_vars_cnt;			/* Number of register variable
+					   definitions encountered.  */
 };
 
 /* Used to indicate a dependency on VDEFs.  */
@@ -211,6 +213,7 @@ new_temp_expr_table (var_map map)
         t->num_in_part[p]++;
     }
   t->call_cnt = XCNEWVEC (int, num_ssa_names + 1);
+  t->reg_vars_cnt = XCNEWVEC (int, num_ssa_names + 1);
 
   return t;
 }
@@ -243,6 +246,7 @@ free_temp_expr_table (temp_expr_table *t)
   free (t->partition_dependencies);
   free (t->num_in_part);
   free (t->call_cnt);
+  free (t->reg_vars_cnt);
 
   if (t->replaceable_expressions)
     ret = t->replaceable_expressions;
@@ -435,7 +439,8 @@ ter_is_replaceable_p (gimple *stmt)
 /* Create an expression entry for a replaceable expression.  */
 
 static void
-process_replaceable (temp_expr_table *tab, gimple *stmt, int call_cnt)
+process_replaceable (temp_expr_table *tab, gimple *stmt, int call_cnt,
+		     int reg_vars_cnt)
 {
   tree var, def, basevar;
   int version;
@@ -477,6 +482,7 @@ process_replaceable (temp_expr_table *tab, gimple *stmt, int call_cnt)
     }
 
   tab->call_cnt[version] = call_cnt;
+  tab->reg_vars_cnt[version] = reg_vars_cnt;
 }
 
 
@@ -573,6 +579,7 @@ find_replaceable_in_bb (temp_expr_table *tab, basic_block bb)
   ssa_op_iter iter;
   bool stmt_replaceable;
   int cur_call_cnt = 0;
+  int cur_reg_vars_cnt = 0;
 
   for (bsi = gsi_start_bb (bb); !gsi_end_p (bsi); gsi_next (&bsi))
     {
@@ -649,11 +656,14 @@ find_replaceable_in_bb (temp_expr_table *tab, basic_block bb)
 	      /* Mark expression as replaceable unless stmt is volatile, or the
 		 def variable has the same root variable as something in the
 		 substitution list, or the def and use span a call such that
-		 we'll expand lifetimes across a call.  */
+		 we'll expand lifetimes across a call.  We also don't want to
+		 replace across these expressions that may call libcalls that
+		 clobber the register involved.  See PR 70184.  */
 	      if (gimple_has_volatile_ops (stmt) || same_root_var
 		  || (tab->call_cnt[ver] != cur_call_cnt
 		      && SINGLE_SSA_USE_OPERAND (SSA_NAME_DEF_STMT (use), SSA_OP_USE)
-			 == NULL_USE_OPERAND_P))
+			 == NULL_USE_OPERAND_P)
+		  || tab->reg_vars_cnt[ver] != cur_reg_vars_cnt)
 		finished_with_expr (tab, ver, true);
 	      else
 		mark_replaceable (tab, use, stmt_replaceable);
@@ -676,9 +686,16 @@ find_replaceable_in_bb (temp_expr_table *tab, basic_block bb)
 	       && DECL_BUILT_IN (fndecl)))
 	cur_call_cnt++;
 
+      /* Increment counter if this statement sets a local
+	 register variable.  */
+      if (gimple_assign_single_p (stmt)
+	  && (TREE_CODE (gimple_assign_lhs (stmt)) == VAR_DECL
+	  && DECL_HARD_REGISTER (gimple_assign_lhs (stmt))))
+	cur_reg_vars_cnt++;
+
       /* Now see if we are creating a new expression or not.  */
       if (stmt_replaceable)
-	process_replaceable (tab, stmt, cur_call_cnt);
+	process_replaceable (tab, stmt, cur_call_cnt, cur_reg_vars_cnt);
 
       /* Free any unused dependency lists.  */
       bitmap_clear (tab->new_replaceable_dependencies);

Reply via email to