jit: implement gcc_jit_rvalue_set_bool_require_tail_call
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 20 May 2016 19:12:49 +0000 (19:12 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Fri, 20 May 2016 19:12:49 +0000 (19:12 +0000)
This implements the libgccjit support for must-tail-call via
a new:
  gcc_jit_rvalue_set_bool_require_tail_call
API entrypoint.

(I didn't implement a wrapper for this within the C++ bindings)

gcc/jit/ChangeLog:
* docs/topics/compatibility.rst: Add LIBGCCJIT_ABI_6.
* docs/topics/expressions.rst (Function calls): Add documentation
of gcc_jit_rvalue_set_bool_require_tail_call.
* docs/_build/texinfo/libgccjit.texi: Regenerate.
* jit-common.h (gcc::jit::recording::base_call): Add forward decl.
* jit-playback.c: Within namespace gcc::jit::playback...
(context::build_call) Add "require_tail_call" param and use it
to set CALL_EXPR_MUST_TAIL_CALL.
(context::new_call): Add "require_tail_call" param.
(context::new_call_through_ptr): Likewise.
* jit-playback.h: Within namespace gcc::jit::playback...
(context::new_call: Add "require_tail_call" param.
(context::new_call_through_ptr): Likewise.
(context::build_call): Likewise.
* jit-recording.c: Within namespace gcc::jit::recording...
(base_call::base_call): New constructor.
(base_call::write_reproducer_tail_call): New method.
(call::call): Update for inheritance from base_call.
(call::replay_into): Provide m_require_tail_call to call
to new_call.
(call::write_reproducer): Call write_reproducer_tail_call.
(call_through_ptr::call_through_ptr): Update for inheritance from
base_call.
(call_through_ptr::replay_into): Provide m_require_tail_call to call
to new_call_through_ptr.
(recording::call_through_ptr::write_reproducer): Call
write_reproducer_tail_call.
* jit-recording.h: Within namespace gcc::jit::recording...
(rvalue::dyn_cast_base_call): New virtual function.
(class base_call): New subclass of class rvalue.
(class call): Inherit from base_call rather than directly from
rvalue, moving get_precedence and m_args to base_call.
(class call_through_ptr): Likewise.
* libgccjit.c (gcc_jit_rvalue_set_bool_require_tail_call): New
function.
* libgccjit.h
(LIBGCCJIT_HAVE_gcc_jit_rvalue_set_bool_require_tail_call): New
macro.
(gcc_jit_rvalue_set_bool_require_tail_call): New function.
* libgccjit.map (LIBGCCJIT_ABI_6): New.
(gcc_jit_rvalue_set_bool_require_tail_call): Add.

gcc/testsuite/ChangeLog:
* jit.dg/all-non-failing-tests.h: Add
test-factorial-must-tail-call.c.
* jit.dg/test-error-impossible-must-tail-call.c: New test case.
* jit.dg/test-factorial-must-tail-call.c: New test case.

From-SVN: r236531

15 files changed:
gcc/jit/ChangeLog
gcc/jit/docs/topics/compatibility.rst
gcc/jit/docs/topics/expressions.rst
gcc/jit/jit-common.h
gcc/jit/jit-playback.c
gcc/jit/jit-playback.h
gcc/jit/jit-recording.c
gcc/jit/jit-recording.h
gcc/jit/libgccjit.c
gcc/jit/libgccjit.h
gcc/jit/libgccjit.map
gcc/testsuite/ChangeLog
gcc/testsuite/jit.dg/all-non-failing-tests.h
gcc/testsuite/jit.dg/test-error-impossible-must-tail-call.c [new file with mode: 0644]
gcc/testsuite/jit.dg/test-factorial-must-tail-call.c [new file with mode: 0644]

index f9320ec..1c3e1fc 100644 (file)
@@ -1,3 +1,47 @@
+2016-05-20  David Malcolm  <dmalcolm@redhat.com>
+
+       * docs/topics/compatibility.rst: Add LIBGCCJIT_ABI_6.
+       * docs/topics/expressions.rst (Function calls): Add documentation
+       of gcc_jit_rvalue_set_bool_require_tail_call.
+       * docs/_build/texinfo/libgccjit.texi: Regenerate.
+       * jit-common.h (gcc::jit::recording::base_call): Add forward decl.
+       * jit-playback.c: Within namespace gcc::jit::playback...
+       (context::build_call) Add "require_tail_call" param and use it
+       to set CALL_EXPR_MUST_TAIL_CALL.
+       (context::new_call): Add "require_tail_call" param.
+       (context::new_call_through_ptr): Likewise.
+       * jit-playback.h: Within namespace gcc::jit::playback...
+       (context::new_call: Add "require_tail_call" param.
+       (context::new_call_through_ptr): Likewise.
+       (context::build_call): Likewise.
+       * jit-recording.c: Within namespace gcc::jit::recording...
+       (base_call::base_call): New constructor.
+       (base_call::write_reproducer_tail_call): New method.
+       (call::call): Update for inheritance from base_call.
+       (call::replay_into): Provide m_require_tail_call to call
+       to new_call.
+       (call::write_reproducer): Call write_reproducer_tail_call.
+       (call_through_ptr::call_through_ptr): Update for inheritance from
+       base_call.
+       (call_through_ptr::replay_into): Provide m_require_tail_call to call
+       to new_call_through_ptr.
+       (recording::call_through_ptr::write_reproducer): Call
+       write_reproducer_tail_call.
+       * jit-recording.h: Within namespace gcc::jit::recording...
+       (rvalue::dyn_cast_base_call): New virtual function.
+       (class base_call): New subclass of class rvalue.
+       (class call): Inherit from base_call rather than directly from
+       rvalue, moving get_precedence and m_args to base_call.
+       (class call_through_ptr): Likewise.
+       * libgccjit.c (gcc_jit_rvalue_set_bool_require_tail_call): New
+       function.
+       * libgccjit.h
+       (LIBGCCJIT_HAVE_gcc_jit_rvalue_set_bool_require_tail_call): New
+       macro.
+       (gcc_jit_rvalue_set_bool_require_tail_call): New function.
+       * libgccjit.map (LIBGCCJIT_ABI_6): New.
+       (gcc_jit_rvalue_set_bool_require_tail_call): Add.
+
 2016-05-17  David Malcolm  <dmalcolm@redhat.com>
 
        * dummy-frontend.c: Include diagnostic.h.
index d9eacf2..7abd050 100644 (file)
@@ -135,3 +135,10 @@ entrypoints:
 -------------------
 ``LIBGCCJIT_ABI_5`` covers the addition of
 :func:`gcc_jit_context_set_bool_use_external_driver`
+
+.. _LIBGCCJIT_ABI_6:
+
+``LIBGCCJIT_ABI_6``
+-------------------
+``LIBGCCJIT_ABI_6`` covers the addition of
+:func:`gcc_jit_rvalue_set_bool_require_tail_call`
index 0445332..261483c 100644 (file)
@@ -424,6 +424,30 @@ Function calls
 
       The same caveat as for :c:func:`gcc_jit_context_new_call` applies.
 
+.. function:: void\
+              gcc_jit_rvalue_set_bool_require_tail_call (gcc_jit_rvalue *call,\
+                                                         int require_tail_call)
+
+   Given an :c:type:`gcc_jit_rvalue *` for a call created through
+   :c:func:`gcc_jit_context_new_call` or
+   :c:func:`gcc_jit_context_new_call_through_ptr`, mark/clear the
+   call as needing tail-call optimization.  The optimizer will
+   attempt to optimize the call into a jump instruction; if it is
+   unable to do do, an error will be emitted.
+
+   This may be useful when implementing functions that use the
+   continuation-passing style (e.g. for functional programming
+   languages), in which every function "returns" by calling a
+   "continuation" function pointer.  This call must be
+   guaranteed to be implemented as a jump, otherwise the program
+   could consume an arbitrary amount of stack space as it executed.
+
+   This entrypoint was added in :ref:`LIBGCCJIT_ABI_6`; you can test for
+   its presence using
+
+   .. code-block:: c
+
+      #ifdef LIBGCCJIT_HAVE_gcc_jit_rvalue_set_bool_require_tail_call
 
 Type-coercion
 *************
index 8a6cd74..b48ea0d 100644 (file)
@@ -126,6 +126,7 @@ namespace recording {
         class local;
        class global;
         class param;
+      class base_call;
     class statement;
     class case_;
 
index 156448d..c9f4084 100644 (file)
@@ -854,7 +854,8 @@ playback::rvalue *
 playback::context::
 build_call (location *loc,
            tree fn_ptr,
-           const auto_vec<rvalue *> *args)
+           const auto_vec<rvalue *> *args,
+           bool require_tail_call)
 {
   vec<tree, va_gc> *tree_args;
   vec_alloc (tree_args, args->length ());
@@ -868,9 +869,13 @@ build_call (location *loc,
   tree fn_type = TREE_TYPE (fn);
   tree return_type = TREE_TYPE (fn_type);
 
-  return new rvalue (this,
-                    build_call_vec (return_type,
-                                    fn_ptr, tree_args));
+  tree call = build_call_vec (return_type,
+                             fn_ptr, tree_args);
+
+  if (require_tail_call)
+    CALL_EXPR_MUST_TAIL_CALL (call) = 1;
+
+  return new rvalue (this, call);
 
   /* see c-typeck.c: build_function_call
      which calls build_function_call_vec
@@ -890,7 +895,8 @@ playback::rvalue *
 playback::context::
 new_call (location *loc,
          function *func,
-         const auto_vec<rvalue *> *args)
+         const auto_vec<rvalue *> *args,
+         bool require_tail_call)
 {
   tree fndecl;
 
@@ -902,7 +908,7 @@ new_call (location *loc,
 
   tree fn = build1 (ADDR_EXPR, build_pointer_type (fntype), fndecl);
 
-  return build_call (loc, fn, args);
+  return build_call (loc, fn, args, require_tail_call);
 }
 
 /* Construct a playback::rvalue instance (wrapping a tree) for a
@@ -912,12 +918,13 @@ playback::rvalue *
 playback::context::
 new_call_through_ptr (location *loc,
                      rvalue *fn_ptr,
-                     const auto_vec<rvalue *> *args)
+                     const auto_vec<rvalue *> *args,
+                     bool require_tail_call)
 {
   gcc_assert (fn_ptr);
   tree t_fn_ptr = fn_ptr->as_tree ();
 
-  return build_call (loc, t_fn_ptr, args);
+  return build_call (loc, t_fn_ptr, args, require_tail_call);
 }
 
 /* Construct a tree for a cast.  */
index 8f7a43d..c00c258 100644 (file)
@@ -133,12 +133,14 @@ public:
   rvalue *
   new_call (location *loc,
            function *func,
-           const auto_vec<rvalue *> *args);
+           const auto_vec<rvalue *> *args,
+           bool require_tail_call);
 
   rvalue *
   new_call_through_ptr (location *loc,
                        rvalue *fn_ptr,
-                       const auto_vec<rvalue *> *args);
+                       const auto_vec<rvalue *> *args,
+                       bool require_tail_call);
 
   rvalue *
   new_cast (location *loc,
@@ -236,7 +238,8 @@ private:
   rvalue *
   build_call (location *loc,
              tree fn_ptr,
-             const auto_vec<rvalue *> *args);
+             const auto_vec<rvalue *> *args,
+             bool require_tail_call);
 
   tree
   build_cast (location *loc,
index 8f5f914..9376342 100644 (file)
@@ -4681,6 +4681,39 @@ recording::cast::write_reproducer (reproducer &r)
           r.get_identifier_as_type (get_type ()));
 }
 
+/* The implementation of class gcc::jit::recording::base_call.  */
+
+/* The constructor for gcc::jit::recording::base_call.  */
+
+recording::base_call::base_call (context *ctxt,
+                                location *loc,
+                                type *type_,
+                                int numargs,
+                                rvalue **args)
+: rvalue (ctxt, loc, type_),
+  m_args (),
+  m_require_tail_call (0)
+{
+  for (int i = 0; i< numargs; i++)
+    m_args.safe_push (args[i]);
+}
+
+/* Subroutine for use by call and call_though_ptr's write_reproducer
+   methods.  */
+
+void
+recording::base_call::write_reproducer_tail_call (reproducer &r,
+                                                 const char *id)
+{
+  if (m_require_tail_call)
+    {
+      r.write ("  gcc_jit_rvalue_set_bool_require_tail_call (%s,  /* gcc_jit_rvalue *call*/\n"
+              "                                             %i); /* int require_tail_call*/\n",
+              id,
+              1);
+    }
+}
+
 /* The implementation of class gcc::jit::recording::call.  */
 
 /* The constructor for gcc::jit::recording::call.  */
@@ -4690,12 +4723,9 @@ recording::call::call (recording::context *ctxt,
                       recording::function *func,
                       int numargs,
                       rvalue **args)
-: rvalue (ctxt, loc, func->get_return_type ()),
-  m_func (func),
-  m_args ()
+: base_call (ctxt, loc, func->get_return_type (), numargs, args),
+  m_func (func)
 {
-  for (int i = 0; i< numargs; i++)
-    m_args.safe_push (args[i]);
 }
 
 /* Implementation of pure virtual hook recording::memento::replay_into
@@ -4711,7 +4741,8 @@ recording::call::replay_into (replayer *r)
 
   set_playback_obj (r->new_call (playback_location (r, m_loc),
                                 m_func->playback_function (),
-                                &playback_args));
+                                &playback_args,
+                                m_require_tail_call));
 }
 
 /* Implementation of pure virtual hook recording::rvalue::visit_children
@@ -4790,6 +4821,7 @@ recording::call::write_reproducer (reproducer &r)
           r.get_identifier (m_func),
           m_args.length (),
           args_id);
+  write_reproducer_tail_call (r, id);
 }
 
 /* The implementation of class gcc::jit::recording::call_through_ptr.  */
@@ -4801,14 +4833,12 @@ recording::call_through_ptr::call_through_ptr (recording::context *ctxt,
                                               recording::rvalue *fn_ptr,
                                               int numargs,
                                               rvalue **args)
-: rvalue (ctxt, loc,
-         fn_ptr->get_type ()->dereference ()
-           ->as_a_function_type ()->get_return_type ()),
-  m_fn_ptr (fn_ptr),
-  m_args ()
+: base_call (ctxt, loc,
+            fn_ptr->get_type ()->dereference ()
+              ->as_a_function_type ()->get_return_type (),
+            numargs, args),
+  m_fn_ptr (fn_ptr)
 {
-  for (int i = 0; i< numargs; i++)
-    m_args.safe_push (args[i]);
 }
 
 /* Implementation of pure virtual hook recording::memento::replay_into
@@ -4824,7 +4854,8 @@ recording::call_through_ptr::replay_into (replayer *r)
 
   set_playback_obj (r->new_call_through_ptr (playback_location (r, m_loc),
                                             m_fn_ptr->playback_rvalue (),
-                                            &playback_args));
+                                            &playback_args,
+                                            m_require_tail_call));
 }
 
 /* Implementation of pure virtual hook recording::rvalue::visit_children
@@ -4907,6 +4938,7 @@ recording::call_through_ptr::write_reproducer (reproducer &r)
           r.get_identifier_as_rvalue (m_fn_ptr),
           m_args.length (),
           args_id);
+  write_reproducer_tail_call (r, id);
 }
 
 /* The implementation of class gcc::jit::recording::array_access.  */
index 1c3e763..0e3511c 100644 (file)
@@ -960,8 +960,9 @@ public:
   void set_scope (function *scope);
   function *get_scope () const { return m_scope; }
 
-  /* Dynamic cast.  */
+  /* Dynamic casts.  */
   virtual param *dyn_cast_param () { return NULL; }
+  virtual base_call *dyn_cast_base_call () { return NULL; }
 
   virtual const char *access_as_rvalue (reproducer &r);
 
@@ -1418,7 +1419,36 @@ private:
   rvalue *m_rvalue;
 };
 
-class call : public rvalue
+class base_call : public rvalue
+{
+ public:
+  base_call (context *ctxt,
+            location *loc,
+            type *type_,
+            int numargs,
+            rvalue **args);
+
+  enum precedence get_precedence () const FINAL OVERRIDE
+  {
+    return PRECEDENCE_POSTFIX;
+  }
+
+  base_call *dyn_cast_base_call () FINAL OVERRIDE { return this; }
+
+  void set_require_tail_call (bool require_tail_call)
+  {
+    m_require_tail_call = require_tail_call;
+  }
+
+ protected:
+  void write_reproducer_tail_call (reproducer &r, const char *id);
+
+ protected:
+  auto_vec<rvalue *> m_args;
+  bool m_require_tail_call;
+};
+
+class call : public base_call
 {
 public:
   call (context *ctxt,
@@ -1434,17 +1464,12 @@ public:
 private:
   string * make_debug_string () FINAL OVERRIDE;
   void write_reproducer (reproducer &r) FINAL OVERRIDE;
-  enum precedence get_precedence () const FINAL OVERRIDE
-  {
-    return PRECEDENCE_POSTFIX;
-  }
 
 private:
   function *m_func;
-  auto_vec<rvalue *> m_args;
 };
 
-class call_through_ptr : public rvalue
+class call_through_ptr : public base_call
 {
 public:
   call_through_ptr (context *ctxt,
@@ -1460,14 +1485,9 @@ public:
 private:
   string * make_debug_string () FINAL OVERRIDE;
   void write_reproducer (reproducer &r) FINAL OVERRIDE;
-  enum precedence get_precedence () const FINAL OVERRIDE
-  {
-    return PRECEDENCE_POSTFIX;
-  }
 
 private:
   rvalue *m_fn_ptr;
-  auto_vec<rvalue *> m_args;
 };
 
 class array_access : public lvalue
index 02ff50c..c2c6aeb 100644 (file)
@@ -2950,3 +2950,23 @@ gcc_jit_timer_print (gcc_jit_timer *timer,
   timer->start (TV_TOTAL);
   timer->push (TV_JIT_CLIENT_CODE);
 }
+
+/* Public entrypoint.  See description in libgccjit.h.
+
+   After error-checking, the real work is effectively done by the
+   gcc::jit::base_call::set_require_tail_call setter in jit-recording.h.  */
+
+void
+gcc_jit_rvalue_set_bool_require_tail_call (gcc_jit_rvalue *rvalue,
+                                          int require_tail_call)
+{
+  RETURN_IF_FAIL (rvalue, NULL, NULL, "NULL call");
+  JIT_LOG_FUNC (rvalue->get_context ()->get_logger ());
+
+  /* Verify that it's a call.  */
+  gcc::jit::recording::base_call *call = rvalue->dyn_cast_base_call ();
+  RETURN_IF_FAIL_PRINTF1 (call, NULL, NULL, "not a call: %s",
+                         rvalue->get_debug_string ());
+
+  call->set_require_tail_call (require_tail_call);
+}
index a8b9f55..175cc16 100644 (file)
@@ -1374,6 +1374,19 @@ extern void
 gcc_jit_timer_print (gcc_jit_timer *timer,
                     FILE *f_out);
 
+
+#define LIBGCCJIT_HAVE_gcc_jit_rvalue_set_bool_require_tail_call
+
+/* Mark/clear a call as needing tail-call optimization.
+
+   This API entrypoint was added in LIBGCCJIT_ABI_6; you can test for its
+   presence using
+     #ifdef LIBGCCJIT_HAVE_gcc_jit_rvalue_set_bool_require_tail_call
+*/
+extern void
+gcc_jit_rvalue_set_bool_require_tail_call (gcc_jit_rvalue *call,
+                                          int require_tail_call);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
index 65f3166..545b192 100644 (file)
@@ -145,3 +145,8 @@ LIBGCCJIT_ABI_5 {
   global:
     gcc_jit_context_set_bool_use_external_driver;
 } LIBGCCJIT_ABI_4;
+
+LIBGCCJIT_ABI_6 {
+  global:
+    gcc_jit_rvalue_set_bool_require_tail_call;
+} LIBGCCJIT_ABI_5;
index d5122e8..0391ce5 100644 (file)
@@ -1,3 +1,10 @@
+2016-05-20  David Malcolm  <dmalcolm@redhat.com>
+
+       * jit.dg/all-non-failing-tests.h: Add
+       test-factorial-must-tail-call.c.
+       * jit.dg/test-error-impossible-must-tail-call.c: New test case.
+       * jit.dg/test-factorial-must-tail-call.c: New test case.
+
 2016-05-20  Jakub Jelinek  <jakub@redhat.com>
 
        PR fortran/71204
index 463eefb..3e2b3b9 100644 (file)
 #undef create_code
 #undef verify_code
 
+/* test-factorial-must-tail-call.c */
+#define create_code create_code_factorial_must_tail_call
+#define verify_code verify_code_factorial_must_tail_call
+#include "test-factorial-must-tail-call.c"
+#undef create_code
+#undef verify_code
+
 /* test-fibonacci.c */
 #define create_code create_code_fibonacci
 #define verify_code verify_code_fibonacci
@@ -272,6 +279,9 @@ const struct testcase testcases[] = {
   {"factorial",
    create_code_factorial,
    verify_code_factorial},
+  {"factorial_must_tail_call",
+   create_code_factorial_must_tail_call,
+   verify_code_factorial_must_tail_call},
   {"fibonacci",
    create_code_fibonacci,
    verify_code_fibonacci},
diff --git a/gcc/testsuite/jit.dg/test-error-impossible-must-tail-call.c b/gcc/testsuite/jit.dg/test-error-impossible-must-tail-call.c
new file mode 100644 (file)
index 0000000..8848d30
--- /dev/null
@@ -0,0 +1,93 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "libgccjit.h"
+
+#include "harness.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  struct box { char dummy[64]; int i; };
+
+  extern struct box
+  returns_struct (int i);
+
+#ifdef __cplusplus
+}
+#endif
+
+void
+create_code (gcc_jit_context *ctxt, void *user_data)
+{
+  /* Let's try to inject the equivalent of:
+
+       int test (int i)
+       {
+         return [MUST TAIL CALL] returns_struct (i).i;
+       }
+
+     and verify that we get a sane error when the tail call
+     optimization can't be done.  */
+
+  gcc_jit_type *char_type =
+    gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_CHAR);
+  gcc_jit_type *int_type =
+    gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
+
+  /* Declare "struct box.  */
+  gcc_jit_type *array_type =
+    gcc_jit_context_new_array_type (ctxt, NULL, char_type, 64);
+  gcc_jit_field *field_dummy =
+    gcc_jit_context_new_field (ctxt, NULL, array_type, "dummy");
+  gcc_jit_field *field_i =
+    gcc_jit_context_new_field (ctxt, NULL, int_type, "i");
+  gcc_jit_field *fields[2] = {field_dummy, field_i};
+  gcc_jit_struct *struct_box =
+    gcc_jit_context_new_struct_type (ctxt, NULL, "box", 2, fields);
+
+  /* Declare the imported function.  */
+  gcc_jit_param *called_fn_param_i =
+    gcc_jit_context_new_param (ctxt, NULL, int_type, "i");
+  gcc_jit_function *called_fn =
+    gcc_jit_context_new_function (ctxt, NULL,
+                                  GCC_JIT_FUNCTION_IMPORTED,
+                                  gcc_jit_struct_as_type (struct_box),
+                                  "called_function",
+                                  1, &called_fn_param_i,
+                                  0);
+
+  /* Build the test_fn.  */
+  gcc_jit_param *caller_param_i =
+    gcc_jit_context_new_param (ctxt, NULL, int_type, "i");
+  gcc_jit_function *test_fn =
+    gcc_jit_context_new_function (ctxt, NULL,
+                                  GCC_JIT_FUNCTION_EXPORTED,
+                                  int_type,
+                                  "test_caller",
+                                  1, &caller_param_i,
+                                  0);
+  gcc_jit_rvalue *arg = gcc_jit_param_as_rvalue (caller_param_i);
+
+  gcc_jit_rvalue *call =
+    gcc_jit_context_new_call (ctxt, NULL,
+                              called_fn,
+                              1, &arg);
+
+  /* Mark the call as requiring tail-call optimization.  */
+  gcc_jit_rvalue_set_bool_require_tail_call (call, 1);
+
+  gcc_jit_block *block = gcc_jit_function_new_block (test_fn, NULL);
+  gcc_jit_block_end_with_return (block, NULL,
+    gcc_jit_rvalue_access_field (call, NULL, field_i));
+}
+
+void
+verify_code (gcc_jit_context *ctxt, gcc_jit_result *result)
+{
+  CHECK_VALUE (result, NULL);
+
+  CHECK_STRING_VALUE (gcc_jit_context_get_first_error (ctxt),
+                     "cannot tail-call: callee returns a structure");
+}
diff --git a/gcc/testsuite/jit.dg/test-factorial-must-tail-call.c b/gcc/testsuite/jit.dg/test-factorial-must-tail-call.c
new file mode 100644 (file)
index 0000000..c862611
--- /dev/null
@@ -0,0 +1,109 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libgccjit.h"
+
+#include "harness.h"
+
+void
+create_code (gcc_jit_context *ctxt, void *user_data)
+{
+  /* Let's try to inject the equivalent of:
+
+      int
+      my_factorial_must_tail_call (int x)
+      {
+        if (x < 2)
+          return x;
+        else
+          return x * my_factorial_must_tail_call (x - 1);
+      }
+
+     and mark the call as requiring tail-call-optimization.
+   */
+  gcc_jit_type *the_type =
+    gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
+  gcc_jit_type *return_type = the_type;
+
+  gcc_jit_param *x =
+    gcc_jit_context_new_param (ctxt, NULL, the_type, "x");
+  gcc_jit_param *params[1] = {x};
+  gcc_jit_function *func =
+    gcc_jit_context_new_function (ctxt, NULL,
+                                 GCC_JIT_FUNCTION_EXPORTED,
+                                 return_type,
+                                 "my_factorial_must_tail_call",
+                                 1, params, 0);
+
+  gcc_jit_block *initial =
+    gcc_jit_function_new_block (func, "initial");
+  gcc_jit_block *on_true =
+    gcc_jit_function_new_block (func, "on_true");
+  gcc_jit_block *on_false =
+    gcc_jit_function_new_block (func, "on_false");
+
+ /* if (x < 2) */
+  gcc_jit_block_end_with_conditional (
+    initial, NULL,
+    gcc_jit_context_new_comparison (
+      ctxt, NULL,
+      GCC_JIT_COMPARISON_LT,
+      gcc_jit_param_as_rvalue (x),
+      gcc_jit_context_new_rvalue_from_int (
+       ctxt,
+       the_type,
+       2)),
+    on_true,
+    on_false);
+
+  /* true branch: */
+  /* return x */
+  gcc_jit_block_end_with_return (
+    on_true,
+    NULL,
+    gcc_jit_param_as_rvalue (x));
+
+  /* false branch: */
+  gcc_jit_rvalue *x_minus_1 =
+    gcc_jit_context_new_binary_op (
+      ctxt, NULL,
+      GCC_JIT_BINARY_OP_MINUS, the_type,
+      gcc_jit_param_as_rvalue (x),
+      gcc_jit_context_new_rvalue_from_int (
+       ctxt,
+       the_type,
+       1));
+  /* my_factorial_must_tail_call (x - 1) */
+  gcc_jit_rvalue *call =
+      gcc_jit_context_new_call (
+        ctxt, NULL,
+        func,
+        1, &x_minus_1);
+
+  /* Mark the call as requiring tail-call optimization.  */
+  gcc_jit_rvalue_set_bool_require_tail_call (call, 1);
+
+  gcc_jit_block_end_with_return (
+    on_false,
+    NULL,
+    gcc_jit_context_new_binary_op (
+      ctxt, NULL,
+      GCC_JIT_BINARY_OP_MULT, the_type,
+      gcc_jit_param_as_rvalue (x),
+      call));
+}
+
+void
+verify_code (gcc_jit_context *ctxt, gcc_jit_result *result)
+{
+  typedef int (*my_factorial_fn_type) (int);
+  CHECK_NON_NULL (result);
+  my_factorial_fn_type my_factorial_must_tail_call =
+    (my_factorial_fn_type)gcc_jit_result_get_code (result, "my_factorial_must_tail_call");
+  CHECK_NON_NULL (my_factorial_must_tail_call);
+  int val = my_factorial_must_tail_call (10);
+  note ("my_factorial_must_tail_call returned: %d", val);
+  CHECK_VALUE (val, 3628800);
+}
+