Implement CALL_EXPR_MUST_TAIL_CALL
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 20 May 2016 14:20:03 +0000 (14:20 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Fri, 20 May 2016 14:20:03 +0000 (14:20 +0000)
This patch implements support for marking CALL_EXPRs
as being mandatory for tail-call-optimization. expand_call
tries harder to perform the optimization on such CALL_EXPRs,
and issues an error if it fails.

Currently this flag isn't accessible from any frontend,
so the patch uses a plugin for testing the functionality.

gcc/ChangeLog:
* calls.c (maybe_complain_about_tail_call): New function.
(initialize_argument_information): Call
maybe_complain_about_tail_call when clearing *may_tailcall.
(can_implement_as_sibling_call_p): Call
maybe_complain_about_tail_call when returning false.
(expand_call): Read CALL_EXPR_MUST_TAIL_CALL and, if set,
ensure try_tail_call is set.  Call maybe_complain_about_tail_call
if tail-call optimization fails.
* cfgexpand.c (expand_call_stmt): Initialize
CALL_EXPR_MUST_TAIL_CALL from gimple_call_must_tail_p.
* gimple-pretty-print.c (dump_gimple_call): Dump
gimple_call_must_tail_p.
* gimple.c (gimple_build_call_from_tree): Call
gimple_call_set_must_tail with the value of
CALL_EXPR_MUST_TAIL_CALL.
* gimple.h (enum gf_mask): Add GF_CALL_MUST_TAIL_CALL.
(gimple_call_set_must_tail): New function.
(gimple_call_must_tail_p): New function.
* print-tree.c (print_node): Update printing of TREE_STATIC
to reflect its use for CALL_EXPR_MUST_TAIL_CALL.
* tree-core.h (struct tree_base): Add MUST_TAIL_CALL to the
trailing comment listing applicable flags.
* tree.h (CALL_EXPR_MUST_TAIL_CALL): New macro.

gcc/testsuite/ChangeLog:
* gcc.dg/plugin/must-tail-call-1.c: New test case.
* gcc.dg/plugin/must-tail-call-2.c: New test case.
* gcc.dg/plugin/must_tail_call_plugin.c: New file.
* gcc.dg/plugin/plugin.exp (plugin_test_list): Add the above.

From-SVN: r236514

14 files changed:
gcc/ChangeLog
gcc/calls.c
gcc/cfgexpand.c
gcc/gimple-pretty-print.c
gcc/gimple.c
gcc/gimple.h
gcc/print-tree.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/plugin/must-tail-call-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/must-tail-call-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/must_tail_call_plugin.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/plugin.exp
gcc/tree-core.h
gcc/tree.h

index 1b5959dce8bde20292bf5c255f9f1d9a5700c596..67d7bf86439373a29cdbc6a2065e2bc6bb281266 100644 (file)
@@ -1,3 +1,29 @@
+2016-05-20  David Malcolm  <dmalcolm@redhat.com>
+
+       * calls.c (maybe_complain_about_tail_call): New function.
+       (initialize_argument_information): Call
+       maybe_complain_about_tail_call when clearing *may_tailcall.
+       (can_implement_as_sibling_call_p): Call
+       maybe_complain_about_tail_call when returning false.
+       (expand_call): Read CALL_EXPR_MUST_TAIL_CALL and, if set,
+       ensure try_tail_call is set.  Call maybe_complain_about_tail_call
+       if tail-call optimization fails.
+       * cfgexpand.c (expand_call_stmt): Initialize
+       CALL_EXPR_MUST_TAIL_CALL from gimple_call_must_tail_p.
+       * gimple-pretty-print.c (dump_gimple_call): Dump
+       gimple_call_must_tail_p.
+       * gimple.c (gimple_build_call_from_tree): Call
+       gimple_call_set_must_tail with the value of
+       CALL_EXPR_MUST_TAIL_CALL.
+       * gimple.h (enum gf_mask): Add GF_CALL_MUST_TAIL_CALL.
+       (gimple_call_set_must_tail): New function.
+       (gimple_call_must_tail_p): New function.
+       * print-tree.c (print_node): Update printing of TREE_STATIC
+       to reflect its use for CALL_EXPR_MUST_TAIL_CALL.
+       * tree-core.h (struct tree_base): Add MUST_TAIL_CALL to the
+       trailing comment listing applicable flags.
+       * tree.h (CALL_EXPR_MUST_TAIL_CALL): New macro.
+
 2016-05-20  David Malcolm  <dmalcolm@redhat.com>
 
        * calls.c (expand_call): Move "Rest of purposes for tail call
index ac8092c696ed296f78a1de4c63df8dc479ddab07..1b12ecaa7b9b895d089c9c3c8848e124d73ef8a4 100644 (file)
@@ -1102,6 +1102,19 @@ store_unaligned_arguments_into_pseudos (struct arg_data *args, int num_actuals)
       }
 }
 
+/* Issue an error if CALL_EXPR was flagged as requiring
+   tall-call optimization.  */
+
+static void
+maybe_complain_about_tail_call (tree call_expr, const char *reason)
+{
+  gcc_assert (TREE_CODE (call_expr) == CALL_EXPR);
+  if (!CALL_EXPR_MUST_TAIL_CALL (call_expr))
+    return;
+
+  error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason);
+}
+
 /* Fill in ARGS_SIZE and ARGS array based on the parameters found in
    CALL_EXPR EXP.
 
@@ -1343,7 +1356,13 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
              /* We can't use sibcalls if a callee-copied argument is
                 stored in the current function's frame.  */
              if (!call_from_thunk_p && DECL_P (base) && !TREE_STATIC (base))
-               *may_tailcall = false;
+               {
+                 *may_tailcall = false;
+                 maybe_complain_about_tail_call (exp,
+                                                 "a callee-copied argument is"
+                                                 " stored in the current "
+                                                 " function's frame");
+               }
 
              args[i].tree_value = build_fold_addr_expr_loc (loc,
                                                         args[i].tree_value);
@@ -1406,6 +1425,9 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
                = build_fold_addr_expr_loc (loc, make_tree (type, copy));
              type = TREE_TYPE (args[i].tree_value);
              *may_tailcall = false;
+             maybe_complain_about_tail_call (exp,
+                                             "argument must be passed"
+                                             " by copying");
            }
        }
 
@@ -2358,48 +2380,87 @@ can_implement_as_sibling_call_p (tree exp,
                                 const args_size &args_size)
 {
   if (!targetm.have_sibcall_epilogue ())
-    return false;
+    {
+      maybe_complain_about_tail_call
+       (exp,
+        "machine description does not have"
+        " a sibcall_epilogue instruction pattern");
+      return false;
+    }
 
   /* Doing sibling call optimization needs some work, since
      structure_value_addr can be allocated on the stack.
      It does not seem worth the effort since few optimizable
      sibling calls will return a structure.  */
   if (structure_value_addr != NULL_RTX)
-    return false;
+    {
+      maybe_complain_about_tail_call (exp, "callee returns a structure");
+      return false;
+    }
 
 #ifdef REG_PARM_STACK_SPACE
   /* If outgoing reg parm stack space changes, we can not do sibcall.  */
   if (OUTGOING_REG_PARM_STACK_SPACE (funtype)
       != OUTGOING_REG_PARM_STACK_SPACE (TREE_TYPE (current_function_decl))
       || (reg_parm_stack_space != REG_PARM_STACK_SPACE (current_function_decl)))
-    return false;
+    {
+      maybe_complain_about_tail_call (exp,
+                                     "inconsistent size of stack space"
+                                     " allocated for arguments which are"
+                                     " passed in registers");
+      return false;
+    }
 #endif
 
   /* Check whether the target is able to optimize the call
      into a sibcall.  */
   if (!targetm.function_ok_for_sibcall (fndecl, exp))
-    return false;
+    {
+      maybe_complain_about_tail_call (exp,
+                                     "target is not able to optimize the"
+                                     " call into a sibling call");
+      return false;
+    }
 
   /* Functions that do not return exactly once may not be sibcall
      optimized.  */
-  if (flags & (ECF_RETURNS_TWICE | ECF_NORETURN))
-    return false;
+  if (flags & ECF_RETURNS_TWICE)
+    {
+      maybe_complain_about_tail_call (exp, "callee returns twice");
+      return false;
+    }
+  if (flags & ECF_NORETURN)
+    {
+      maybe_complain_about_tail_call (exp, "callee does not return");
+      return false;
+    }
 
   if (TYPE_VOLATILE (TREE_TYPE (TREE_TYPE (addr))))
-    return false;
+    {
+      maybe_complain_about_tail_call (exp, "volatile function type");
+      return false;
+    }
 
   /* If the called function is nested in the current one, it might access
      some of the caller's arguments, but could clobber them beforehand if
      the argument areas are shared.  */
   if (fndecl && decl_function_context (fndecl) == current_function_decl)
-    return false;
+    {
+      maybe_complain_about_tail_call (exp, "nested function");
+      return false;
+    }
 
   /* If this function requires more stack slots than the current
      function, we cannot change it into a sibling call.
      crtl->args.pretend_args_size is not part of the
      stack allocated by our caller.  */
   if (args_size.constant > (crtl->args.size - crtl->args.pretend_args_size))
-    return false;
+    {
+      maybe_complain_about_tail_call (exp,
+                                     "callee required more stack slots"
+                                     " than the caller");
+      return false;
+    }
 
   /* If the callee pops its own arguments, then it must pop exactly
      the same number of arguments as the current function.  */
@@ -2407,10 +2468,19 @@ can_implement_as_sibling_call_p (tree exp,
       != targetm.calls.return_pops_args (current_function_decl,
                                         TREE_TYPE (current_function_decl),
                                         crtl->args.size))
-    return false;
+    {
+      maybe_complain_about_tail_call (exp,
+                                     "inconsistent number of"
+                                     " popped arguments");
+      return false;
+    }
 
   if (!lang_hooks.decls.ok_for_sibcall (fndecl))
-    return false;
+    {
+      maybe_complain_about_tail_call (exp, "frontend does not support"
+                                           " sibling call");
+      return false;
+    }
 
   /* All checks passed.  */
   return true;
@@ -2444,6 +2514,7 @@ expand_call (tree exp, rtx target, int ignore)
   /* The type of the function being called.  */
   tree fntype;
   bool try_tail_call = CALL_EXPR_TAILCALL (exp);
+  bool must_tail_call = CALL_EXPR_MUST_TAIL_CALL (exp);
   int pass;
 
   /* Register in which non-BLKmode value will be returned,
@@ -2811,10 +2882,18 @@ expand_call (tree exp, rtx target, int ignore)
       || dbg_cnt (tail_call) == false)
     try_tail_call = 0;
 
+  /* If the user has marked the function as requiring tail-call
+     optimization, attempt it.  */
+  if (must_tail_call)
+    try_tail_call = 1;
+
   /*  Rest of purposes for tail call optimizations to fail.  */
   if (try_tail_call)
-    try_tail_call = can_implement_as_sibling_call_p (exp, structure_value_addr, funtype,
-                                                    reg_parm_stack_space, fndecl,
+    try_tail_call = can_implement_as_sibling_call_p (exp,
+                                                    structure_value_addr,
+                                                    funtype,
+                                                    reg_parm_stack_space,
+                                                    fndecl,
                                                     flags, addr, args_size);
 
   /* Check if caller and callee disagree in promotion of function
@@ -2845,7 +2924,13 @@ expand_call (tree exp, rtx target, int ignore)
                  && (caller_unsignedp != callee_unsignedp
                      || GET_MODE_BITSIZE (caller_mode)
                         < GET_MODE_BITSIZE (callee_mode)))))
-       try_tail_call = 0;
+       {
+         try_tail_call = 0;
+         maybe_complain_about_tail_call (exp,
+                                         "caller and callee disagree in"
+                                         " promotion of function"
+                                         " return value");
+       }
     }
 
   /* Ensure current function's preferred stack boundary is at least
@@ -3743,7 +3828,13 @@ expand_call (tree exp, rtx target, int ignore)
       crtl->tail_call_emit = true;
     }
   else
-    emit_insn (normal_call_insns);
+    {
+      emit_insn (normal_call_insns);
+      if (try_tail_call)
+       /* Ideally we'd emit a message for all of the ways that it could
+          have failed.  */
+       maybe_complain_about_tail_call (exp, "tail call production failed");
+    }
 
   currently_expanding_call--;
 
index 1461ad8b90b7f961c23d99f153728c0167220b50..56ef71dfbabb73108b35340a99099e4168a0547e 100644 (file)
@@ -2626,6 +2626,7 @@ expand_call_stmt (gcall *stmt)
     TREE_NOTHROW (exp) = 1;
 
   CALL_EXPR_TAILCALL (exp) = gimple_call_tail_p (stmt);
+  CALL_EXPR_MUST_TAIL_CALL (exp) = gimple_call_must_tail_p (stmt);
   CALL_EXPR_RETURN_SLOT_OPT (exp) = gimple_call_return_slot_opt_p (stmt);
   if (decl
       && DECL_BUILT_IN_CLASS (decl) == BUILT_IN_NORMAL
index f8642684ad91147911e6c609609107270722af86..48edacce1483cbdf962e8134a020ec839786086d 100644 (file)
@@ -760,6 +760,8 @@ dump_gimple_call (pretty_printer *buffer, gcall *gs, int spc, int flags)
     pp_string (buffer, " [return slot optimization]");
   if (gimple_call_tail_p (gs))
     pp_string (buffer, " [tail call]");
+  if (gimple_call_must_tail_p (gs))
+    pp_string (buffer, " [must tail call]");
 
   if (fn == NULL)
     return;
index 742c907d8bbff4b141bf0b0e7a097f1eedf0b00b..226b080107289def522ddfea247c5f6aa3bf9114 100644 (file)
@@ -359,6 +359,7 @@ gimple_build_call_from_tree (tree t)
   /* Carry all the CALL_EXPR flags to the new GIMPLE_CALL.  */
   gimple_call_set_chain (call, CALL_EXPR_STATIC_CHAIN (t));
   gimple_call_set_tail (call, CALL_EXPR_TAILCALL (t));
+  gimple_call_set_must_tail (call, CALL_EXPR_MUST_TAIL_CALL (t));
   gimple_call_set_return_slot_opt (call, CALL_EXPR_RETURN_SLOT_OPT (t));
   if (fndecl
       && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL
index 6d15dab5120e0b24294dff506d6ea3396a0fa04f..063e29d78978f4dad74db05705be1d10856d8607 100644 (file)
@@ -145,6 +145,7 @@ enum gf_mask {
     GF_CALL_INTERNAL           = 1 << 6,
     GF_CALL_CTRL_ALTERING       = 1 << 7,
     GF_CALL_WITH_BOUNDS        = 1 << 8,
+    GF_CALL_MUST_TAIL_CALL     = 1 << 9,
     GF_OMP_PARALLEL_COMBINED   = 1 << 0,
     GF_OMP_PARALLEL_GRID_PHONY = 1 << 1,
     GF_OMP_TASK_TASKLOOP       = 1 << 0,
@@ -3209,6 +3210,25 @@ gimple_call_tail_p (gcall *s)
   return (s->subcode & GF_CALL_TAILCALL) != 0;
 }
 
+/* Mark (or clear) call statement S as requiring tail call optimization.  */
+
+static inline void
+gimple_call_set_must_tail (gcall *s, bool must_tail_p)
+{
+  if (must_tail_p)
+    s->subcode |= GF_CALL_MUST_TAIL_CALL;
+  else
+    s->subcode &= ~GF_CALL_MUST_TAIL_CALL;
+}
+
+/* Return true if call statement has been marked as requiring
+   tail call optimization.  */
+
+static inline bool
+gimple_call_must_tail_p (const gcall *s)
+{
+  return (s->subcode & GF_CALL_MUST_TAIL_CALL) != 0;
+}
 
 /* If RETURN_SLOT_OPT_P is true mark GIMPLE_CALL S as valid for return
    slot optimization.  This transformation uses the target of the call
index aa6593f28f43eae4185b6f22b0092e4cefe7d51b..58b611891e39ecf24dd46bbe713cda256b5557c9 100644 (file)
@@ -324,7 +324,7 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
   if (TREE_PROTECTED (node))
     fputs (" protected", file);
   if (TREE_STATIC (node))
-    fputs (" static", file);
+    fputs (code == CALL_EXPR ? " must-tail-call" : " static", file);
   if (TREE_DEPRECATED (node))
     fputs (" deprecated", file);
   if (TREE_VISITED (node))
index 9ddd4f9e682259197031a8c4b7da9576b9136931..d5739cbd22f98cce23e89d3898b6b5552b3ad80c 100644 (file)
@@ -1,3 +1,10 @@
+2016-05-20  David Malcolm  <dmalcolm@redhat.com>
+
+       * gcc.dg/plugin/must-tail-call-1.c: New test case.
+       * gcc.dg/plugin/must-tail-call-2.c: New test case.
+       * gcc.dg/plugin/must_tail_call_plugin.c: New file.
+       * gcc.dg/plugin/plugin.exp (plugin_test_list): Add the above.
+
 2016-05-20  Jan Hubicka  <hubicka@ucw.cz>
 
        * gcc.dg/tree-ssa/prefetch-5.c: xfail.
diff --git a/gcc/testsuite/gcc.dg/plugin/must-tail-call-1.c b/gcc/testsuite/gcc.dg/plugin/must-tail-call-1.c
new file mode 100644 (file)
index 0000000..6d74955
--- /dev/null
@@ -0,0 +1,22 @@
+extern void abort (void);
+
+int __attribute__((noinline,noclone))
+callee (int i)
+{
+  return i * i;
+}
+
+int __attribute__((noinline,noclone))
+caller (int i)
+{
+  return callee (i + 1);
+}
+
+int
+main (int argc, const char **argv)
+{
+  int result = caller (5);
+  if (result != 36)
+    abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/must-tail-call-2.c b/gcc/testsuite/gcc.dg/plugin/must-tail-call-2.c
new file mode 100644 (file)
index 0000000..c5504f8
--- /dev/null
@@ -0,0 +1,58 @@
+/* Allow nested functions.  */
+/* { dg-options "-Wno-pedantic" } */
+
+struct box { char field[64]; int i; };
+
+struct box __attribute__((noinline,noclone))
+returns_struct (int i)
+{
+  struct box b;
+  b.i = i * i;
+  return b;
+}
+
+int __attribute__((noinline,noclone))
+test_1 (int i)
+{
+  return returns_struct (i * 5).i; /* { dg-error "cannot tail-call: callee returns a structure" } */
+}
+
+int __attribute__((noinline,noclone))
+test_2_callee (int i, struct box b)
+{
+  if (b.field[0])
+    return 5;
+  return i * i;
+}
+
+int __attribute__((noinline,noclone))
+test_2_caller (int i)
+{
+  struct box b;
+  return test_2_callee (i + 1, b); /* { dg-error "cannot tail-call: callee required more stack slots than the caller" } */
+}
+
+extern void setjmp (void);
+void
+test_3 (void)
+{
+  setjmp (); /* { dg-error "cannot tail-call: callee returns twice" } */
+}
+
+void
+test_4 (void)
+{
+  void nested (void)
+  {
+  }
+  nested (); /* { dg-error "cannot tail-call: nested function" } */
+}
+
+typedef void (fn_ptr_t) (void);
+volatile fn_ptr_t fn_ptr;
+
+void
+test_5 (void)
+{
+  fn_ptr (); /* { dg-error "cannot tail-call: callee does not return" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/must_tail_call_plugin.c b/gcc/testsuite/gcc.dg/plugin/must_tail_call_plugin.c
new file mode 100644 (file)
index 0000000..5294f28
--- /dev/null
@@ -0,0 +1,76 @@
+/* { dg-options "-O" } */
+
+/* Mark all CALL_EXPRs not within "main" as requiring tail-call. */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+
+int plugin_is_GPL_compatible;
+
+tree
+cb_walk_tree_fn (tree * tp, int * walk_subtrees,
+                void * data ATTRIBUTE_UNUSED)
+{
+  if (TREE_CODE (*tp) != CALL_EXPR)
+    return NULL_TREE;
+
+  tree call_expr = *tp;
+
+  /* Forcibly mark the CALL_EXPR as requiring tail-call optimization.  */
+  CALL_EXPR_MUST_TAIL_CALL (call_expr) = 1;
+  
+  return NULL_TREE;
+}
+
+static void
+callback (void *gcc_data, void *user_data)
+{
+  tree fndecl = (tree)gcc_data;
+  gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+
+  /* Don't mark calls inside "main".  */
+  tree decl_name = DECL_NAME (fndecl);
+  if (decl_name)
+    if (0 == strcmp (IDENTIFIER_POINTER (decl_name), "main"))
+      return;
+
+  walk_tree (&DECL_SAVED_TREE (fndecl), cb_walk_tree_fn, NULL, NULL);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+            struct plugin_gcc_version *version)
+{
+  const char *plugin_name = plugin_info->base_name;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  register_callback (plugin_name,
+                    PLUGIN_PRE_GENERICIZE,
+                    callback,
+                    NULL);
+
+  return 0;
+}
index fd1e98e53c4eafb939995de0e1c293e92e4f4c1a..62f6797c8139975c102dd8cee9336e6ea2fd8236 100644 (file)
@@ -74,6 +74,9 @@ set plugin_test_list [list \
     { location_overflow_plugin.c \
          location-overflow-test-1.c \
          location-overflow-test-2.c } \
+    { must_tail_call_plugin.c \
+         must-tail-call-1.c \
+         must-tail-call-2.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
index e4c5068241b1781943f8a2ca87375acb649cfcd8..b0699284e0d2e9ad3e7a9a61328e57cc9aab3847 100644 (file)
@@ -1001,6 +1001,9 @@ struct GTY(()) tree_base {
        SSA_NAME_ANTI_RANGE_P in
           SSA_NAME
 
+       MUST_TAIL_CALL in
+          CALL_EXPR
+
    public_flag:
 
        TREE_OVERFLOW in
index 37324bf86b8ccca177690554d45fc2115c9e1094..2510d166ea6c456f9863e74c1bcfdb9d43fc676b 100644 (file)
@@ -661,6 +661,11 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int,
 #define CALL_EXPR_TAILCALL(NODE) \
   (CALL_EXPR_CHECK (NODE)->base.addressable_flag)
 
+/* Set on a CALL_EXPR if the call has been marked as requiring tail call
+   optimization for correctness.  */
+#define CALL_EXPR_MUST_TAIL_CALL(NODE) \
+  (CALL_EXPR_CHECK (NODE)->base.static_flag)
+
 /* Used as a temporary field on a CASE_LABEL_EXPR to indicate that the
    CASE_LOW operand has been processed.  */
 #define CASE_LOW_SEEN(NODE) \