diff mbox

Handle sibcalls with aggregate returns

Message ID 8760nhqfxs.fsf@e105548-lin.cambridge.arm.com
State New
Headers show

Commit Message

Richard Sandiford Nov. 21, 2016, 10:34 a.m. UTC
We treated this g as a sibling call to f:

  int f (int);
  int g (void) { return f (1); }

but not this one:

  struct s { int i; };
  struct s f (int);
  struct s g (void) { return f (1); }

We treated them both as sibcalls on x86 before the first patch for PR36326,
so I suppose this is a regression of sorts from 4.3.

The patch allows function returns to be local aggregate variables as well
as gimple registers.

Tested on aarch64-linux-gnu and x86_64-linux-gnu.  OK to install?

Thanks,
Richard


gcc/
	* tree-tailcall.c (process_assignment): Simplify the check for
	a valid copy, allowing the source to be a local variable as
	well as an SSA name.
	(find_tail_calls): Allow copies between local variables to follow
	the call.  Allow the result to be stored in any local variable,
	even if it's an aggregate.
	(eliminate_tail_call): Check whether the result is an SSA name
	before updating its SSA_NAME_DEF_STMT.

gcc/testsuite/
	* gcc.dg/tree-ssa/tailcall-7.c: New test.

Comments

Richard Biener Nov. 21, 2016, 12:35 p.m. UTC | #1
On Mon, Nov 21, 2016 at 11:34 AM, Richard Sandiford
<richard.sandiford@arm.com> wrote:
> We treated this g as a sibling call to f:
>
>   int f (int);
>   int g (void) { return f (1); }
>
> but not this one:
>
>   struct s { int i; };
>   struct s f (int);
>   struct s g (void) { return f (1); }
>
> We treated them both as sibcalls on x86 before the first patch for PR36326,
> so I suppose this is a regression of sorts from 4.3.
>
> The patch allows function returns to be local aggregate variables as well
> as gimple registers.
>
> Tested on aarch64-linux-gnu and x86_64-linux-gnu.  OK to install?

Ok.

Richard.

> Thanks,
> Richard
>
>
> gcc/
>         * tree-tailcall.c (process_assignment): Simplify the check for
>         a valid copy, allowing the source to be a local variable as
>         well as an SSA name.
>         (find_tail_calls): Allow copies between local variables to follow
>         the call.  Allow the result to be stored in any local variable,
>         even if it's an aggregate.
>         (eliminate_tail_call): Check whether the result is an SSA name
>         before updating its SSA_NAME_DEF_STMT.
>
> gcc/testsuite/
>         * gcc.dg/tree-ssa/tailcall-7.c: New test.
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/tailcall-7.c b/gcc/testsuite/gcc.dg/tree-ssa/tailcall-7.c
> new file mode 100644
> index 0000000..eabf1a8
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/tailcall-7.c
> @@ -0,0 +1,89 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-tailc-details" } */
> +
> +struct s { int x; };
> +struct s f (int);
> +struct s global;
> +void callit (void (*) (void));
> +
> +/* Tail call.  */
> +void
> +g1 (void)
> +{
> +  f (1);
> +}
> +
> +/* Not a tail call.  */
> +void
> +g2 (void)
> +{
> +  global = f (2);
> +}
> +
> +/* Not a tail call.  */
> +void
> +g3 (struct s *ptr)
> +{
> +  *ptr = f (3);
> +}
> +
> +/* Tail call.  */
> +struct s
> +g4 (struct s param)
> +{
> +  param = f (4);
> +  return param;
> +}
> +
> +/* Tail call.  */
> +struct s
> +g5 (void)
> +{
> +  struct s local = f (5);
> +  return local;
> +}
> +
> +/* Tail call.  */
> +struct s
> +g6 (void)
> +{
> +  return f (6);
> +}
> +
> +/* Not a tail call.  */
> +struct s
> +g7 (void)
> +{
> +  struct s local = f (7);
> +  global = local;
> +  return local;
> +}
> +
> +/* Not a tail call.  */
> +struct s
> +g8 (struct s *ptr)
> +{
> +  struct s local = f (8);
> +  *ptr = local;
> +  return local;
> +}
> +
> +/* Not a tail call.  */
> +int
> +g9 (struct s param)
> +{
> +  void inner (void) { param = f (9); }
> +  callit (inner);
> +  return 9;
> +}
> +
> +/* Tail call.  */
> +int
> +g10 (int param)
> +{
> +  void inner (void) { f (param); }
> +  callit (inner);
> +  return 10;
> +}
> +
> +/* { dg-final { scan-tree-dump-times "Found tail call" 5 "tailc" } } */
> diff --git a/gcc/tree-tailcall.c b/gcc/tree-tailcall.c
> index 0436f0f..f97541d 100644
> --- a/gcc/tree-tailcall.c
> +++ b/gcc/tree-tailcall.c
> @@ -269,7 +269,7 @@ process_assignment (gassign *stmt, gimple_stmt_iterator call, tree *m,
>       conversions that can never produce extra code between the function
>       call and the function return.  */
>    if ((rhs_class == GIMPLE_SINGLE_RHS || gimple_assign_cast_p (stmt))
> -      && (TREE_CODE (src_var) == SSA_NAME))
> +      && src_var == *ass_var)
>      {
>        /* Reject a tailcall if the type conversion might need
>          additional code.  */
> @@ -287,9 +287,6 @@ process_assignment (gassign *stmt, gimple_stmt_iterator call, tree *m,
>             return false;
>         }
>
> -      if (src_var != *ass_var)
> -       return false;
> -
>        *ass_var = dest;
>        return true;
>      }
> @@ -428,6 +425,13 @@ find_tail_calls (basic_block bb, struct tailcall **ret)
>           break;
>         }
>
> +      /* Allow simple copies between local variables, even if they're
> +        aggregates.  */
> +      if (is_gimple_assign (stmt)
> +         && auto_var_in_fn_p (gimple_assign_lhs (stmt), cfun->decl)
> +         && auto_var_in_fn_p (gimple_assign_rhs1 (stmt), cfun->decl))
> +       continue;
> +
>        /* If the statement references memory or volatile operands, fail.  */
>        if (gimple_references_memory_p (stmt)
>           || gimple_has_volatile_ops (stmt))
> @@ -444,18 +448,20 @@ find_tail_calls (basic_block bb, struct tailcall **ret)
>        return;
>      }
>
> -  /* If the LHS of our call is not just a simple register, we can't
> -     transform this into a tail or sibling call.  This situation happens,
> -     in (e.g.) "*p = foo()" where foo returns a struct.  In this case
> -     we won't have a temporary here, but we need to carry out the side
> -     effect anyway, so tailcall is impossible.
> +  /* If the LHS of our call is not just a simple register or local
> +     variable, we can't transform this into a tail or sibling call.
> +     This situation happens, in (e.g.) "*p = foo()" where foo returns a
> +     struct.  In this case we won't have a temporary here, but we need
> +     to carry out the side effect anyway, so tailcall is impossible.
>
>       ??? In some situations (when the struct is returned in memory via
>       invisible argument) we could deal with this, e.g. by passing 'p'
>       itself as that argument to foo, but it's too early to do this here,
>       and expand_call() will not handle it anyway.  If it ever can, then
>       we need to revisit this here, to allow that situation.  */
> -  if (ass_var && !is_gimple_reg (ass_var))
> +  if (ass_var
> +      && !is_gimple_reg (ass_var)
> +      && !auto_var_in_fn_p (ass_var, cfun->decl))
>      return;
>
>    /* We found the call, check whether it is suitable.  */
> @@ -888,7 +894,7 @@ eliminate_tail_call (struct tailcall *t)
>
>    call = gsi_stmt (t->call_gsi);
>    rslt = gimple_call_lhs (call);
> -  if (rslt != NULL_TREE)
> +  if (rslt != NULL_TREE && TREE_CODE (rslt) == SSA_NAME)
>      {
>        /* Result of the call will no longer be defined.  So adjust the
>          SSA_NAME_DEF_STMT accordingly.  */
>
diff mbox

Patch

diff --git a/gcc/testsuite/gcc.dg/tree-ssa/tailcall-7.c b/gcc/testsuite/gcc.dg/tree-ssa/tailcall-7.c
new file mode 100644
index 0000000..eabf1a8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/tailcall-7.c
@@ -0,0 +1,89 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-tailc-details" } */
+
+struct s { int x; };
+struct s f (int);
+struct s global;
+void callit (void (*) (void));
+
+/* Tail call.  */
+void
+g1 (void)
+{
+  f (1);
+}
+
+/* Not a tail call.  */
+void
+g2 (void)
+{
+  global = f (2);
+}
+
+/* Not a tail call.  */
+void
+g3 (struct s *ptr)
+{
+  *ptr = f (3);
+}
+
+/* Tail call.  */
+struct s
+g4 (struct s param)
+{
+  param = f (4);
+  return param;
+}
+
+/* Tail call.  */
+struct s
+g5 (void)
+{
+  struct s local = f (5);
+  return local;
+}
+
+/* Tail call.  */
+struct s
+g6 (void)
+{
+  return f (6);
+}
+
+/* Not a tail call.  */
+struct s
+g7 (void)
+{
+  struct s local = f (7);
+  global = local;
+  return local;
+}
+
+/* Not a tail call.  */
+struct s
+g8 (struct s *ptr)
+{
+  struct s local = f (8);
+  *ptr = local;
+  return local;
+}
+
+/* Not a tail call.  */
+int
+g9 (struct s param)
+{
+  void inner (void) { param = f (9); }
+  callit (inner);
+  return 9;
+}
+
+/* Tail call.  */
+int
+g10 (int param)
+{
+  void inner (void) { f (param); }
+  callit (inner);
+  return 10;
+}
+
+/* { dg-final { scan-tree-dump-times "Found tail call" 5 "tailc" } } */
diff --git a/gcc/tree-tailcall.c b/gcc/tree-tailcall.c
index 0436f0f..f97541d 100644
--- a/gcc/tree-tailcall.c
+++ b/gcc/tree-tailcall.c
@@ -269,7 +269,7 @@  process_assignment (gassign *stmt, gimple_stmt_iterator call, tree *m,
      conversions that can never produce extra code between the function
      call and the function return.  */
   if ((rhs_class == GIMPLE_SINGLE_RHS || gimple_assign_cast_p (stmt))
-      && (TREE_CODE (src_var) == SSA_NAME))
+      && src_var == *ass_var)
     {
       /* Reject a tailcall if the type conversion might need
 	 additional code.  */
@@ -287,9 +287,6 @@  process_assignment (gassign *stmt, gimple_stmt_iterator call, tree *m,
 	    return false;
 	}
 
-      if (src_var != *ass_var)
-	return false;
-
       *ass_var = dest;
       return true;
     }
@@ -428,6 +425,13 @@  find_tail_calls (basic_block bb, struct tailcall **ret)
 	  break;
 	}
 
+      /* Allow simple copies between local variables, even if they're
+	 aggregates.  */
+      if (is_gimple_assign (stmt)
+	  && auto_var_in_fn_p (gimple_assign_lhs (stmt), cfun->decl)
+	  && auto_var_in_fn_p (gimple_assign_rhs1 (stmt), cfun->decl))
+	continue;
+
       /* If the statement references memory or volatile operands, fail.  */
       if (gimple_references_memory_p (stmt)
 	  || gimple_has_volatile_ops (stmt))
@@ -444,18 +448,20 @@  find_tail_calls (basic_block bb, struct tailcall **ret)
       return;
     }
 
-  /* If the LHS of our call is not just a simple register, we can't
-     transform this into a tail or sibling call.  This situation happens,
-     in (e.g.) "*p = foo()" where foo returns a struct.  In this case
-     we won't have a temporary here, but we need to carry out the side
-     effect anyway, so tailcall is impossible.
+  /* If the LHS of our call is not just a simple register or local
+     variable, we can't transform this into a tail or sibling call.
+     This situation happens, in (e.g.) "*p = foo()" where foo returns a
+     struct.  In this case we won't have a temporary here, but we need
+     to carry out the side effect anyway, so tailcall is impossible.
 
      ??? In some situations (when the struct is returned in memory via
      invisible argument) we could deal with this, e.g. by passing 'p'
      itself as that argument to foo, but it's too early to do this here,
      and expand_call() will not handle it anyway.  If it ever can, then
      we need to revisit this here, to allow that situation.  */
-  if (ass_var && !is_gimple_reg (ass_var))
+  if (ass_var
+      && !is_gimple_reg (ass_var)
+      && !auto_var_in_fn_p (ass_var, cfun->decl))
     return;
 
   /* We found the call, check whether it is suitable.  */
@@ -888,7 +894,7 @@  eliminate_tail_call (struct tailcall *t)
 
   call = gsi_stmt (t->call_gsi);
   rslt = gimple_call_lhs (call);
-  if (rslt != NULL_TREE)
+  if (rslt != NULL_TREE && TREE_CODE (rslt) == SSA_NAME)
     {
       /* Result of the call will no longer be defined.  So adjust the
 	 SSA_NAME_DEF_STMT accordingly.  */