This is the JIT-specific part of the patch for PR jit/64166. Implement a way to get at dumpfiles from JIT testcases.
Use it from test-sum-of-squares.c to provide a simple selftest of the dumping functionality, and use it from test-functions.c to add verification of the fix for PR jit/64020. gcc/jit/ChangeLog: PR jit/64166 * docs/topics/contexts.rst (Debugging): Add description of gcc_jit_context_enable_dump. * jit-playback.c: Include context.h (class auto_argvec): New class (auto_argvec::~auto_argvec): New function. (gcc::jit::playback::context::compile): Convert fake_args to be an auto_argvec, so that it can contain dynamically-allocated strings. Construct a vec of all requested dumps, and pass it to make_fake_args. Extract requested dumps between the calls to toplev::main and toplev::finalize. (gcc::jit::playback::context::make_fake_args): Convert param "argvec" to be a vec <char *>, and gain a "requested_dumps" param. Convert to dynamically-allocated arg strings by converting ADD_ARG to take a copy of the arg, and add ADD_ARG_TAKE_OWNERSHIP for args that are already a copy. Add args for all requested dumps. (gcc::jit::playback::context::extract_any_requested_dumps): New function. (gcc::jit::playback::context::read_dump_file): New function. * jit-playback.h (gcc::jit::playback::context::make_fake_args): Convert param "argvec" to be a vec <char *>, and gain a "requested_dumps" param. (gcc::jit::playback::context::extract_any_requested_dumps): New function. (gcc::jit::playback::context::read_dump_file): New function. * jit-recording.c (gcc::jit::recording::context::enable_dump): New function. (gcc::jit::recording::context::get_all_requested_dumps): New function. * jit-recording.h (gcc::jit::recording::requested_dump): New struct. (gcc::jit::recording::context::enable_dump): New function. (gcc::jit::recording::context::get_all_requested_dumps): New function. (gcc::jit::recording::context::m_requested_dumps): New field. * libgccjit.c (gcc_jit_context_enable_dump): New API entrypoint. * libgccjit.h (gcc_jit_context_enable_dump): New API entrypoint. * libgccjit.map (gcc_jit_context_enable_dump): New API entrypoint. gcc/testsuite/ChangeLog: PR jit/64166 PR jit/64020 * jit.dg/harness.h (CHECK_STRING_CONTAINS): New macro. (check_string_contains): New function. * jit.dg/test-error-unrecognized-dump.c: New file. * jit.dg/test-functions.c (trig_sincos_dump): New variable. (trig_statistics_dump): New variable. (create_test_of_builtin_trig): Enable dumping of "sincos" and "statistics" into "trig_sincos_dump" and "trig_statistics_dump". (verify_test_of_builtin_trig): Verify the sincos and statistics dumps. * jit.dg/test-sum-of-squares.c (dump_vrp1): New variable. (create_code): Enable dumping of "tree-vrp1" into dump_vrp1. (verify_code): Verify the tree-vrp1 dump. --- gcc/jit/docs/topics/contexts.rst | 47 +++++++ gcc/jit/jit-playback.c | 141 ++++++++++++++++++++- gcc/jit/jit-playback.h | 12 +- gcc/jit/jit-recording.c | 34 +++++ gcc/jit/jit-recording.h | 17 +++ gcc/jit/libgccjit.c | 18 +++ gcc/jit/libgccjit.h | 34 +++++ gcc/jit/libgccjit.map | 1 + gcc/testsuite/jit.dg/harness.h | 33 +++++ .../jit.dg/test-error-unrecognized-dump.c | 27 ++++ gcc/testsuite/jit.dg/test-functions.c | 27 ++++ gcc/testsuite/jit.dg/test-sum-of-squares.c | 16 +++ 12 files changed, 400 insertions(+), 7 deletions(-) create mode 100644 gcc/testsuite/jit.dg/test-error-unrecognized-dump.c diff --git a/gcc/jit/docs/topics/contexts.rst b/gcc/jit/docs/topics/contexts.rst index c3f8c52..d03ccf4 100644 --- a/gcc/jit/docs/topics/contexts.rst +++ b/gcc/jit/docs/topics/contexts.rst @@ -152,6 +152,53 @@ Debugging :macro:`GCC_JIT_BOOL_OPTION_DEBUGINFO` to allow stepping through the code in a debugger. +.. function:: void\ + gcc_jit_context_enable_dump (gcc_jit_context *ctxt,\ + const char *dumpname, \ + char **out_ptr) + + Enable the dumping of a specific set of internal state from the + compilation, capturing the result in-memory as a buffer. + + Parameter "dumpname" corresponds to the equivalent gcc command-line + option, without the "-fdump-" prefix. + For example, to get the equivalent of :option:`-fdump-tree-vrp1`, + supply ``"tree-vrp1"``: + + .. code-block:: c + + static char *dump_vrp1; + + void + create_code (gcc_jit_context *ctxt) + { + gcc_jit_context_enable_dump (ctxt, "tree-vrp1", &dump_vrp1); + /* (other API calls omitted for brevity) */ + } + + The context directly stores the dumpname as a ``(const char *)``, so + the passed string must outlive the context. + + :func:`gcc_jit_context_compile` will capture the dump as a + dynamically-allocated buffer, writing it to ``*out_ptr``. + + The caller becomes responsible for calling: + + .. code-block:: c + + free (*out_ptr) + + each time that :func:`gcc_jit_context_compile` is called. + ``*out_ptr`` will be written to, either with the address of a buffer, + or with ``NULL`` if an error occurred. + + .. warning:: + + This API entrypoint is likely to be less stable than the others. + In particular, both the precise dumpnames, and the format and content + of the dumps are subject to change. + + It exists primarily for writing the library's own test suite. Options ------- diff --git a/gcc/jit/jit-playback.c b/gcc/jit/jit-playback.c index ecdae80..cf50fb3 100644 --- a/gcc/jit/jit-playback.c +++ b/gcc/jit/jit-playback.c @@ -47,6 +47,7 @@ along with GCC; see the file COPYING3. If not see #include "gimplify.h" #include "gcc-driver-name.h" #include "attribs.h" +#include "context.h" #include "jit-common.h" #include "jit-playback.h" @@ -1552,6 +1553,26 @@ make_tempdir_path_template () return result; } +/* A subclass of auto_vec <char *> that frees all of its elements on + deletion. */ + +class auto_argvec : public auto_vec <char *> +{ + public: + ~auto_argvec (); +}; + +/* auto_argvec's dtor, freeing all contained strings, automatically + chaining up to ~auto_vec <char *>, which frees the internal buffer. */ + +auto_argvec::~auto_argvec () +{ + int i; + char *str; + FOR_EACH_VEC_ELT (*this, i, str) + free (str); +} + /* Compile a playback::context: - Use the context's options to cconstruct command-line options, and @@ -1594,14 +1615,25 @@ compile () if (!ctxt_progname) ctxt_progname = "libgccjit.so"; - auto_vec <const char *> fake_args; - make_fake_args (&fake_args, ctxt_progname); + auto_vec <recording::requested_dump> requested_dumps; + m_recording_ctxt->get_all_requested_dumps (&requested_dumps); + + auto_argvec fake_args; + make_fake_args (&fake_args, ctxt_progname, &requested_dumps); if (errors_occurred ()) return NULL; + /* This runs the compiler. */ toplev toplev (false); toplev.main (fake_args.length (), const_cast <char **> (fake_args.address ())); + + /* Extracting dumps makes use of the gcc::dump_manager, hence we + need to do it between toplev::main (which creates the dump manager) + and toplev::finalize (which deletes it). */ + extract_any_requested_dumps (&requested_dumps); + + /* Clean up the compiler. */ toplev.finalize (); active_playback_ctxt = NULL; @@ -1645,10 +1677,12 @@ compile () void playback::context:: -make_fake_args (auto_vec <const char *> *argvec, - const char *ctxt_progname) +make_fake_args (vec <char *> *argvec, + const char *ctxt_progname, + vec <recording::requested_dump> *requested_dumps) { -#define ADD_ARG(arg) argvec->safe_push (arg) +#define ADD_ARG(arg) argvec->safe_push (xstrdup (arg)) +#define ADD_ARG_TAKE_OWNERSHIP(arg) argvec->safe_push (arg) ADD_ARG (ctxt_progname); ADD_ARG (m_path_c_file); @@ -1707,7 +1741,104 @@ make_fake_args (auto_vec <const char *> *argvec, ADD_ARG ("-fdump-rtl-all"); ADD_ARG ("-fdump-ipa-all"); } + + /* Add "-fdump-" options for any calls to + gcc_jit_context_enable_dump. */ + { + int i; + recording::requested_dump *d; + FOR_EACH_VEC_ELT (*requested_dumps, i, d) + { + char *arg = concat ("-fdump-", d->m_dumpname, NULL); + ADD_ARG_TAKE_OWNERSHIP (arg); + } + } + #undef ADD_ARG +#undef ADD_ARG_TAKE_OWNERSHIP +} + +/* The second half of the implementation of gcc_jit_context_enable_dump. + Iterate through the requested dumps, reading the underlying files + into heap-allocated buffers, writing pointers to the buffers into + the char ** pointers provided by client code. + Client code is responsible for calling free on the results. */ + +void +playback::context:: +extract_any_requested_dumps (vec <recording::requested_dump> *requested_dumps) +{ + int i; + recording::requested_dump *d; + FOR_EACH_VEC_ELT (*requested_dumps, i, d) + { + dump_file_info *dfi; + char *filename; + char *content; + + dfi = g->get_dumps ()->get_dump_file_info_by_switch (d->m_dumpname); + if (!dfi) + { + add_error (NULL, "unrecognized dump: %s", d->m_dumpname); + continue; + } + + filename = g->get_dumps ()->get_dump_file_name (dfi); + content = read_dump_file (filename); + *(d->m_out_ptr) = content; + free (filename); + } +} + +/* Helper function for playback::context::extract_any_requested_dumps + (itself for use in implementation of gcc_jit_context_enable_dump). + + Attempt to read the complete file at the given path, returning the + bytes found there as a buffer. + The caller is responsible for calling free on the result. + Errors will be reported on the context, and lead to NULL being + returned; an out-of-memory error will terminate the process. */ + +char * +playback::context::read_dump_file (const char *path) +{ + char *result = NULL; + size_t total_sz = 0; + char buf[4096]; + size_t sz; + FILE *f_in; + + f_in = fopen (path, "r"); + if (!f_in) + { + add_error (NULL, "unable to open %s for reading", path); + return NULL; + } + + while ( (sz = fread (buf, 1, sizeof (buf), f_in)) ) + { + size_t old_total_sz = total_sz; + total_sz += sz; + result = reinterpret_cast <char *> (xrealloc (result, total_sz + 1)); + memcpy (result + old_total_sz, buf, sz); + } + + if (!feof (f_in)) + { + add_error (NULL, "error reading from %s", path); + free (result); + return NULL; + } + + fclose (f_in); + + if (result) + { + result[total_sz] = '\0'; + return result; + } + else + return xstrdup (""); } /* Part of playback::context::compile (). diff --git a/gcc/jit/jit-playback.h b/gcc/jit/jit-playback.h index 02f08ba..b2b983a 100644 --- a/gcc/jit/jit-playback.h +++ b/gcc/jit/jit-playback.h @@ -236,8 +236,16 @@ private: /* Functions for implementing "compile". */ void - make_fake_args (auto_vec <const char *> *argvec, - const char *ctxt_progname); + make_fake_args (vec <char *> *argvec, + const char *ctxt_progname, + vec <recording::requested_dump> *requested_dumps); + + void + extract_any_requested_dumps + (vec <recording::requested_dump> *requested_dumps); + + char * + read_dump_file (const char *path); void convert_to_dso (const char *ctxt_progname); diff --git a/gcc/jit/jit-recording.c b/gcc/jit/jit-recording.c index 82ec399..74fd111 100644 --- a/gcc/jit/jit-recording.c +++ b/gcc/jit/jit-recording.c @@ -868,6 +868,27 @@ recording::context::set_bool_option (enum gcc_jit_bool_option opt, m_bool_options[opt] = value ? true : false; } +/* Add the given dumpname/out_ptr pair to this context's list of requested + dumps. + + Implements the post-error-checking part of + gcc_jit_context_enable_dump. */ + +void +recording::context::enable_dump (const char *dumpname, + char **out_ptr) +{ + requested_dump d; + gcc_assert (dumpname); + gcc_assert (out_ptr); + + d.m_dumpname = dumpname; + d.m_out_ptr = out_ptr; + *out_ptr = NULL; + m_requested_dumps.safe_push (d); +} + + /* This mutex guards gcc::jit::recording::context::compile, so that only one thread can be accessing the bulk of GCC's state at once. */ @@ -1026,6 +1047,19 @@ recording::context::dump_to_file (const char *path, bool update_locations) } } +/* Copy the requested dumps within this context and all ancestors into + OUT. */ + +void +recording::context::get_all_requested_dumps (vec <recording::requested_dump> *out) +{ + if (m_parent_ctxt) + m_parent_ctxt->get_all_requested_dumps (out); + + out->reserve (m_requested_dumps.length ()); + out->splice (m_requested_dumps); +} + /* This is a pre-compilation check for the context (and any parents). Detect errors within the context, adding errors if any are found. */ diff --git a/gcc/jit/jit-recording.h b/gcc/jit/jit-recording.h index 31fb304..d2f5ffb 100644 --- a/gcc/jit/jit-recording.h +++ b/gcc/jit/jit-recording.h @@ -45,6 +45,13 @@ playback_string (string *str); playback::block * playback_block (block *b); +/* A recording of a call to gcc_jit_context_enable_dump. */ +struct requested_dump +{ + const char *m_dumpname; + char **m_out_ptr; +}; + /* A JIT-compilation context. */ class context { @@ -191,6 +198,10 @@ public: set_bool_option (enum gcc_jit_bool_option opt, int value); + void + enable_dump (const char *dumpname, + char **out_ptr); + const char * get_str_option (enum gcc_jit_str_option opt) const { @@ -235,6 +246,9 @@ public: void dump_to_file (const char *path, bool update_locations); + void + get_all_requested_dumps (vec <recording::requested_dump> *out); + private: void validate (); @@ -250,6 +264,9 @@ private: int m_int_options[GCC_JIT_NUM_INT_OPTIONS]; bool m_bool_options[GCC_JIT_NUM_BOOL_OPTIONS]; + /* Dumpfiles that were requested via gcc_jit_context_enable_dump. */ + auto_vec<requested_dump> m_requested_dumps; + /* Recorded API usage. */ auto_vec<memento *> m_mementos; diff --git a/gcc/jit/libgccjit.c b/gcc/jit/libgccjit.c index 42769e8..0f50c43 100644 --- a/gcc/jit/libgccjit.c +++ b/gcc/jit/libgccjit.c @@ -2004,6 +2004,24 @@ gcc_jit_context_set_bool_option (gcc_jit_context *ctxt, /* Public entrypoint. See description in libgccjit.h. After error-checking, the real work is done by the + gcc::jit::recording::context::enable_dump method in + jit-recording.c. */ + +void +gcc_jit_context_enable_dump (gcc_jit_context *ctxt, + const char *dumpname, + char **out_ptr) +{ + RETURN_IF_FAIL (ctxt, NULL, NULL, "NULL context"); + RETURN_IF_FAIL (dumpname, ctxt, NULL, "NULL dumpname"); + RETURN_IF_FAIL (out_ptr, ctxt, NULL, "NULL out_ptr"); + + ctxt->enable_dump (dumpname, out_ptr); +} + +/* Public entrypoint. See description in libgccjit.h. + + After error-checking, the real work is done by the gcc::jit::recording::context::compile method in jit-recording.c. */ diff --git a/gcc/jit/libgccjit.h b/gcc/jit/libgccjit.h index ed6390e..71628e0 100644 --- a/gcc/jit/libgccjit.h +++ b/gcc/jit/libgccjit.h @@ -985,6 +985,40 @@ gcc_jit_block_end_with_void_return (gcc_jit_block *block, extern gcc_jit_context * gcc_jit_context_new_child_context (gcc_jit_context *parent_ctxt); +/********************************************************************** + Implementation support. + **********************************************************************/ + +/* Enable the dumping of a specific set of internal state from the + compilation, capturing the result in-memory as a buffer. + + Parameter "dumpname" corresponds to the equivalent gcc command-line + option, without the "-fdump-" prefix. + For example, to get the equivalent of "-fdump-tree-vrp1", supply + "tree-vrp1". + The context directly stores the dumpname as a (const char *), so the + passed string must outlive the context. + + gcc_jit_context_compile will capture the dump as a + dynamically-allocated buffer, writing it to ``*out_ptr``. + + The caller becomes responsible for calling + free (*out_ptr) + each time that gcc_jit_context_compile is called. *out_ptr will be + written to, either with the address of a buffer, or with NULL if an + error occurred. + + This API entrypoint is likely to be less stable than the others. + In particular, both the precise dumpnames, and the format and content + of the dumps are subject to change. + + It exists primarily for writing the library's own test suite. */ + +extern void +gcc_jit_context_enable_dump (gcc_jit_context *ctxt, + const char *dumpname, + char **out_ptr); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/gcc/jit/libgccjit.map b/gcc/jit/libgccjit.map index d4ba7b6..0375e77 100644 --- a/gcc/jit/libgccjit.map +++ b/gcc/jit/libgccjit.map @@ -33,6 +33,7 @@ gcc_jit_context_acquire; gcc_jit_context_compile; gcc_jit_context_dump_to_file; + gcc_jit_context_enable_dump; gcc_jit_context_get_builtin_function; gcc_jit_context_get_first_error; gcc_jit_context_get_type; diff --git a/gcc/testsuite/jit.dg/harness.h b/gcc/testsuite/jit.dg/harness.h index a30b66e..1493c6f 100644 --- a/gcc/testsuite/jit.dg/harness.h +++ b/gcc/testsuite/jit.dg/harness.h @@ -84,6 +84,9 @@ static char test[1024]; #define CHECK_STRING_STARTS_WITH(ACTUAL, EXPECTED_PREFIX) \ check_string_starts_with ((ACTUAL), (EXPECTED_PREFIX)); +#define CHECK_STRING_CONTAINS(ACTUAL, EXPECTED_SUBSTRING) \ + check_string_contains (#ACTUAL, (ACTUAL), (EXPECTED_SUBSTRING)); + #define CHECK(COND) \ do { \ if (COND) \ @@ -110,6 +113,11 @@ extern void check_string_starts_with (const char *actual, const char *expected_prefix); +extern void +check_string_contains (const char *name, + const char *actual, + const char *expected_substring); + /* Implement framework needed for turning the testcase hooks into an executable. test-combination.c and test-threads.c each combine multiple testcases into larger testcases, so we have COMBINED_TEST as a way of @@ -168,6 +176,31 @@ check_string_starts_with (const char *actual, test, actual, expected_prefix); } +void +check_string_contains (const char *name, + const char *actual, + const char *expected_substring) +{ + if (!actual) + { + fail ("%s: %s: actual: NULL does not contain expected substring: \"%s\"", + test, name, expected_substring); + fprintf (stderr, "incorrect value\n"); + abort (); + } + + if (!strstr (actual, expected_substring)) + { + fail ("%s: %s: actual: \"%s\" did not contain expected substring: \"%s\"", + test, name, actual, expected_substring); + fprintf (stderr, "incorrect value\n"); + abort (); + } + + pass ("%s: %s: found substring: \"%s\"", + test, name, expected_substring); +} + static void set_options (gcc_jit_context *ctxt, const char *argv0) { /* Set up options. */ diff --git a/gcc/testsuite/jit.dg/test-error-unrecognized-dump.c b/gcc/testsuite/jit.dg/test-error-unrecognized-dump.c new file mode 100644 index 0000000..0b73360 --- /dev/null +++ b/gcc/testsuite/jit.dg/test-error-unrecognized-dump.c @@ -0,0 +1,27 @@ +#include <stdlib.h> +#include <stdio.h> + +#include "libgccjit.h" + +#include "harness.h" + +static char *dump; + +void +create_code (gcc_jit_context *ctxt, void *user_data) +{ + gcc_jit_context_enable_dump (ctxt, + "not-a-valid-dump-switch", + &dump); +} + +void +verify_code (gcc_jit_context *ctxt, gcc_jit_result *result) +{ + CHECK_VALUE (result, NULL); + + /* Verify that the correct error message was emitted. */ + CHECK_STRING_VALUE (gcc_jit_context_get_first_error (ctxt), + "unrecognized dump: not-a-valid-dump-switch"); +} + diff --git a/gcc/testsuite/jit.dg/test-functions.c b/gcc/testsuite/jit.dg/test-functions.c index 3d03ada..45c24d6 100644 --- a/gcc/testsuite/jit.dg/test-functions.c +++ b/gcc/testsuite/jit.dg/test-functions.c @@ -167,6 +167,9 @@ create_test_of_builtin_strcmp (gcc_jit_context *ctxt) gcc_jit_block_end_with_return (initial, NULL, call); } +static char *trig_sincos_dump; +static char *trig_statistics_dump; + static void create_test_of_builtin_trig (gcc_jit_context *ctxt) { @@ -178,6 +181,14 @@ create_test_of_builtin_trig (gcc_jit_context *ctxt) } (in theory, optimizable to sin (2 * theta)) */ + + gcc_jit_context_enable_dump (ctxt, + "tree-sincos", + &trig_sincos_dump); + gcc_jit_context_enable_dump (ctxt, + "statistics", + &trig_statistics_dump); + gcc_jit_type *double_t = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_DOUBLE); @@ -266,6 +277,22 @@ verify_test_of_builtin_trig (gcc_jit_context *ctxt, gcc_jit_result *result) CHECK_DOUBLE_VALUE (test_of_builtin_trig (M_PI_2 ), 0.0); CHECK_DOUBLE_VALUE (test_of_builtin_trig (M_PI_4 * 3.0), -1.0); CHECK_DOUBLE_VALUE (test_of_builtin_trig (M_PI ), 0.0); + + /* PR jit/64020: + The "sincos" pass merges sin/cos calls into the cexpi builtin. + Verify that a dump of the "sincos" pass was provided, and that it + shows a call to the cexpi builtin on a SSA name of "theta". */ + CHECK_NON_NULL (trig_sincos_dump); + CHECK_STRING_CONTAINS (trig_sincos_dump, " = __builtin_cexpi (theta_"); + free (trig_sincos_dump); + + /* Similarly, verify that the statistics dump was provided, and that + it shows the sincos optimization. */ + CHECK_NON_NULL (trig_statistics_dump); + CHECK_STRING_CONTAINS ( + trig_statistics_dump, + "sincos \"sincos statements inserted\" \"test_of_builtin_trig\" 1"); + free (trig_statistics_dump); } static void diff --git a/gcc/testsuite/jit.dg/test-sum-of-squares.c b/gcc/testsuite/jit.dg/test-sum-of-squares.c index d6fdcf6..46fd5c2 100644 --- a/gcc/testsuite/jit.dg/test-sum-of-squares.c +++ b/gcc/testsuite/jit.dg/test-sum-of-squares.c @@ -6,6 +6,8 @@ #include "harness.h" +static char *dump_vrp1; + void create_code (gcc_jit_context *ctxt, void *user_data) { @@ -22,6 +24,8 @@ create_code (gcc_jit_context *ctxt, void *user_data) } return sum; */ + gcc_jit_context_enable_dump (ctxt, "tree-vrp1", &dump_vrp1); + gcc_jit_type *the_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT); gcc_jit_type *return_type = the_type; @@ -123,4 +127,16 @@ verify_code (gcc_jit_context *ctxt, gcc_jit_result *result) int val = loop_test (10); note ("loop_test returned: %d", val); CHECK_VALUE (val, 285); + + CHECK_NON_NULL (dump_vrp1); + /* PR jit/64166 + An example of using gcc_jit_context_enable_dump to verify a property + of the compile. + + In this case, verify that vrp is able to deduce the + bounds of the iteration variable. Specifically, verify that some + variable is known to be in the range negative infinity to some + expression based on param "n" (actually n-1). */ + CHECK_STRING_CONTAINS (dump_vrp1, ": [-INF, n_"); + free (dump_vrp1); } -- 1.8.5.3