diff --git a/gcc/jit/ChangeLog b/gcc/jit/ChangeLog index f9320eccee0..1c3e1fc8f21 100644 --- a/gcc/jit/ChangeLog +++ b/gcc/jit/ChangeLog @@ -1,3 +1,47 @@ +2016-05-20 David Malcolm + + * 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 * dummy-frontend.c: Include diagnostic.h. diff --git a/gcc/jit/docs/topics/compatibility.rst b/gcc/jit/docs/topics/compatibility.rst index d9eacf27c6d..7abd0508e20 100644 --- a/gcc/jit/docs/topics/compatibility.rst +++ b/gcc/jit/docs/topics/compatibility.rst @@ -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` diff --git a/gcc/jit/docs/topics/expressions.rst b/gcc/jit/docs/topics/expressions.rst index 04453321b9c..261483c78b0 100644 --- a/gcc/jit/docs/topics/expressions.rst +++ b/gcc/jit/docs/topics/expressions.rst @@ -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 ************* diff --git a/gcc/jit/jit-common.h b/gcc/jit/jit-common.h index 8a6cd7453a3..b48ea0db69d 100644 --- a/gcc/jit/jit-common.h +++ b/gcc/jit/jit-common.h @@ -126,6 +126,7 @@ namespace recording { class local; class global; class param; + class base_call; class statement; class case_; diff --git a/gcc/jit/jit-playback.c b/gcc/jit/jit-playback.c index 156448d13d5..c9f40848be0 100644 --- a/gcc/jit/jit-playback.c +++ b/gcc/jit/jit-playback.c @@ -854,7 +854,8 @@ playback::rvalue * playback::context:: build_call (location *loc, tree fn_ptr, - const auto_vec *args) + const auto_vec *args, + bool require_tail_call) { vec *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 *args) + const auto_vec *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 *args) + const auto_vec *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. */ diff --git a/gcc/jit/jit-playback.h b/gcc/jit/jit-playback.h index 8f7a43db1b0..c00c25820af 100644 --- a/gcc/jit/jit-playback.h +++ b/gcc/jit/jit-playback.h @@ -133,12 +133,14 @@ public: rvalue * new_call (location *loc, function *func, - const auto_vec *args); + const auto_vec *args, + bool require_tail_call); rvalue * new_call_through_ptr (location *loc, rvalue *fn_ptr, - const auto_vec *args); + const auto_vec *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 *args); + const auto_vec *args, + bool require_tail_call); tree build_cast (location *loc, diff --git a/gcc/jit/jit-recording.c b/gcc/jit/jit-recording.c index 8f5f914277c..937634207d6 100644 --- a/gcc/jit/jit-recording.c +++ b/gcc/jit/jit-recording.c @@ -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. */ diff --git a/gcc/jit/jit-recording.h b/gcc/jit/jit-recording.h index 1c3e7634209..0e3511c6a1c 100644 --- a/gcc/jit/jit-recording.h +++ b/gcc/jit/jit-recording.h @@ -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 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 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 m_args; }; class array_access : public lvalue diff --git a/gcc/jit/libgccjit.c b/gcc/jit/libgccjit.c index 02ff50c75a8..c2c6aeb5bfb 100644 --- a/gcc/jit/libgccjit.c +++ b/gcc/jit/libgccjit.c @@ -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); +} diff --git a/gcc/jit/libgccjit.h b/gcc/jit/libgccjit.h index a8b9f55dc1b..175cc16c01d 100644 --- a/gcc/jit/libgccjit.h +++ b/gcc/jit/libgccjit.h @@ -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 */ diff --git a/gcc/jit/libgccjit.map b/gcc/jit/libgccjit.map index 65f3166f694..545b192b64c 100644 --- a/gcc/jit/libgccjit.map +++ b/gcc/jit/libgccjit.map @@ -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; diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index d5122e82cc1..0391ce5e181 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,10 @@ +2016-05-20 David Malcolm + + * 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 PR fortran/71204 diff --git a/gcc/testsuite/jit.dg/all-non-failing-tests.h b/gcc/testsuite/jit.dg/all-non-failing-tests.h index 463eefb2ad0..3e2b3b910fc 100644 --- a/gcc/testsuite/jit.dg/all-non-failing-tests.h +++ b/gcc/testsuite/jit.dg/all-non-failing-tests.h @@ -105,6 +105,13 @@ #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 index 00000000000..8848d3015a9 --- /dev/null +++ b/gcc/testsuite/jit.dg/test-error-impossible-must-tail-call.c @@ -0,0 +1,93 @@ +#include +#include + +#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 index 00000000000..c8626115708 --- /dev/null +++ b/gcc/testsuite/jit.dg/test-factorial-must-tail-call.c @@ -0,0 +1,109 @@ +#include +#include +#include + +#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); +} +