| /* Emit optimization information as JSON files. |
| Copyright (C) 2018-2022 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| |
| #include "backend.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "diagnostic-core.h" |
| |
| #include "profile.h" |
| #include "output.h" |
| #include "tree-pass.h" |
| |
| #include "optinfo.h" |
| #include "optinfo-emit-json.h" |
| #include "json.h" |
| #include "pretty-print.h" |
| #include "tree-pretty-print.h" |
| #include "gimple-pretty-print.h" |
| #include "cgraph.h" |
| |
| #include "langhooks.h" |
| #include "version.h" |
| #include "context.h" |
| #include "pass_manager.h" |
| #include "selftest.h" |
| #include "dump-context.h" |
| #include <zlib.h> |
| |
| /* optrecord_json_writer's ctor. Populate the top-level parts of the |
| in-memory JSON representation. */ |
| |
| optrecord_json_writer::optrecord_json_writer () |
| : m_root_tuple (NULL), m_scopes () |
| { |
| m_root_tuple = new json::array (); |
| |
| /* Populate with metadata; compare with toplev.cc: print_version. */ |
| json::object *metadata = new json::object (); |
| m_root_tuple->append (metadata); |
| metadata->set ("format", new json::string ("1")); |
| json::object *generator = new json::object (); |
| metadata->set ("generator", generator); |
| generator->set ("name", new json::string (lang_hooks.name)); |
| generator->set ("pkgversion", new json::string (pkgversion_string)); |
| generator->set ("version", new json::string (version_string)); |
| /* TARGET_NAME is passed in by the Makefile. */ |
| generator->set ("target", new json::string (TARGET_NAME)); |
| |
| /* TODO: capture command-line? |
| see gen_producer_string in dwarf2out.cc (currently static). */ |
| |
| /* TODO: capture "any plugins?" flag (or the plugins themselves). */ |
| |
| json::array *passes = new json::array (); |
| m_root_tuple->append (passes); |
| |
| /* Call add_pass_list for all of the pass lists. */ |
| { |
| #define DEF_PASS_LIST(LIST) \ |
| add_pass_list (passes, g->get_passes ()->LIST); |
| GCC_PASS_LISTS |
| #undef DEF_PASS_LIST |
| } |
| |
| json::array *records = new json::array (); |
| m_root_tuple->append (records); |
| |
| m_scopes.safe_push (records); |
| } |
| |
| /* optrecord_json_writer's ctor. |
| Delete the in-memory JSON representation. */ |
| |
| optrecord_json_writer::~optrecord_json_writer () |
| { |
| delete m_root_tuple; |
| } |
| |
| /* Choose an appropriate filename, and write the saved records to it. */ |
| |
| void |
| optrecord_json_writer::write () const |
| { |
| pretty_printer pp; |
| m_root_tuple->print (&pp); |
| |
| bool emitted_error = false; |
| char *filename = concat (dump_base_name, ".opt-record.json.gz", NULL); |
| gzFile outfile = gzopen (filename, "w"); |
| if (outfile == NULL) |
| { |
| error_at (UNKNOWN_LOCATION, "cannot open file %qs for writing optimization records", |
| filename); // FIXME: more info? |
| goto cleanup; |
| } |
| |
| if (gzputs (outfile, pp_formatted_text (&pp)) <= 0) |
| { |
| int tmp; |
| error_at (UNKNOWN_LOCATION, "error writing optimization records to %qs: %s", |
| filename, gzerror (outfile, &tmp)); |
| emitted_error = true; |
| } |
| |
| cleanup: |
| if (outfile) |
| if (gzclose (outfile) != Z_OK) |
| if (!emitted_error) |
| error_at (UNKNOWN_LOCATION, "error closing optimization records %qs", |
| filename); |
| |
| free (filename); |
| } |
| |
| /* Add a record for OPTINFO to the queue of records to be written. */ |
| |
| void |
| optrecord_json_writer::add_record (const optinfo *optinfo) |
| { |
| json::object *obj = optinfo_to_json (optinfo); |
| |
| add_record (obj); |
| |
| /* Potentially push the scope. */ |
| if (optinfo->get_kind () == OPTINFO_KIND_SCOPE) |
| { |
| json::array *children = new json::array (); |
| obj->set ("children", children); |
| m_scopes.safe_push (children); |
| } |
| } |
| |
| /* Private methods of optrecord_json_writer. */ |
| |
| /* Add record OBJ to the innermost scope. */ |
| |
| void |
| optrecord_json_writer::add_record (json::object *obj) |
| { |
| /* Add to innermost scope. */ |
| gcc_assert (m_scopes.length () > 0); |
| m_scopes[m_scopes.length () - 1]->append (obj); |
| } |
| |
| /* Pop the innermost scope. */ |
| |
| void |
| optrecord_json_writer::pop_scope () |
| { |
| m_scopes.pop (); |
| |
| /* We should never pop the top-level records array. */ |
| gcc_assert (m_scopes.length () > 0); |
| } |
| |
| /* Create a JSON object representing LOC. */ |
| |
| json::object * |
| optrecord_json_writer::impl_location_to_json (dump_impl_location_t loc) |
| { |
| json::object *obj = new json::object (); |
| obj->set ("file", new json::string (loc.m_file)); |
| obj->set ("line", new json::integer_number (loc.m_line)); |
| if (loc.m_function) |
| obj->set ("function", new json::string (loc.m_function)); |
| return obj; |
| } |
| |
| /* Create a JSON object representing LOC. */ |
| |
| json::object * |
| optrecord_json_writer::location_to_json (location_t loc) |
| { |
| gcc_assert (LOCATION_LOCUS (loc) != UNKNOWN_LOCATION); |
| expanded_location exploc = expand_location (loc); |
| json::object *obj = new json::object (); |
| obj->set ("file", new json::string (exploc.file)); |
| obj->set ("line", new json::integer_number (exploc.line)); |
| obj->set ("column", new json::integer_number (exploc.column)); |
| return obj; |
| } |
| |
| /* Create a JSON object representing COUNT. */ |
| |
| json::object * |
| optrecord_json_writer::profile_count_to_json (profile_count count) |
| { |
| json::object *obj = new json::object (); |
| obj->set ("value", new json::integer_number (count.to_gcov_type ())); |
| obj->set ("quality", |
| new json::string (profile_quality_as_string (count.quality ()))); |
| return obj; |
| } |
| |
| /* Get a string for use when referring to PASS in the saved optimization |
| records. */ |
| |
| json::string * |
| optrecord_json_writer::get_id_value_for_pass (opt_pass *pass) |
| { |
| pretty_printer pp; |
| /* this is host-dependent, but will be consistent for a given host. */ |
| pp_pointer (&pp, static_cast<void *> (pass)); |
| return new json::string (pp_formatted_text (&pp)); |
| } |
| |
| /* Create a JSON object representing PASS. */ |
| |
| json::object * |
| optrecord_json_writer::pass_to_json (opt_pass *pass) |
| { |
| json::object *obj = new json::object (); |
| const char *type = NULL; |
| switch (pass->type) |
| { |
| default: |
| gcc_unreachable (); |
| case GIMPLE_PASS: |
| type = "gimple"; |
| break; |
| case RTL_PASS: |
| type = "rtl"; |
| break; |
| case SIMPLE_IPA_PASS: |
| type = "simple_ipa"; |
| break; |
| case IPA_PASS: |
| type = "ipa"; |
| break; |
| } |
| obj->set ("id", get_id_value_for_pass (pass)); |
| obj->set ("type", new json::string (type)); |
| obj->set ("name", new json::string (pass->name)); |
| /* Represent the optgroup flags as an array. */ |
| { |
| json::array *optgroups = new json::array (); |
| obj->set ("optgroups", optgroups); |
| for (const kv_pair<optgroup_flags_t> *optgroup = optgroup_options; |
| optgroup->name != NULL; optgroup++) |
| if (optgroup->value != OPTGROUP_ALL |
| && (pass->optinfo_flags & optgroup->value)) |
| optgroups->append (new json::string (optgroup->name)); |
| } |
| obj->set ("num", new json::integer_number (pass->static_pass_number)); |
| return obj; |
| } |
| |
| /* Create a JSON array for LOC representing the chain of inlining |
| locations. |
| Compare with lhd_print_error_function and cp_print_error_function. */ |
| |
| json::value * |
| optrecord_json_writer::inlining_chain_to_json (location_t loc) |
| { |
| json::array *array = new json::array (); |
| |
| tree abstract_origin = LOCATION_BLOCK (loc); |
| |
| while (abstract_origin) |
| { |
| location_t *locus; |
| tree block = abstract_origin; |
| |
| locus = &BLOCK_SOURCE_LOCATION (block); |
| tree fndecl = NULL; |
| block = BLOCK_SUPERCONTEXT (block); |
| while (block && TREE_CODE (block) == BLOCK |
| && BLOCK_ABSTRACT_ORIGIN (block)) |
| { |
| tree ao = BLOCK_ABSTRACT_ORIGIN (block); |
| if (TREE_CODE (ao) == FUNCTION_DECL) |
| { |
| fndecl = ao; |
| break; |
| } |
| else if (TREE_CODE (ao) != BLOCK) |
| break; |
| |
| block = BLOCK_SUPERCONTEXT (block); |
| } |
| if (fndecl) |
| abstract_origin = block; |
| else |
| { |
| while (block && TREE_CODE (block) == BLOCK) |
| block = BLOCK_SUPERCONTEXT (block); |
| |
| if (block && TREE_CODE (block) == FUNCTION_DECL) |
| fndecl = block; |
| abstract_origin = NULL; |
| } |
| if (fndecl) |
| { |
| json::object *obj = new json::object (); |
| const char *printable_name |
| = lang_hooks.decl_printable_name (fndecl, 2); |
| obj->set ("fndecl", new json::string (printable_name)); |
| if (LOCATION_LOCUS (*locus) != UNKNOWN_LOCATION) |
| obj->set ("site", location_to_json (*locus)); |
| array->append (obj); |
| } |
| } |
| |
| return array; |
| } |
| |
| /* Create a JSON object representing OPTINFO. */ |
| |
| json::object * |
| optrecord_json_writer::optinfo_to_json (const optinfo *optinfo) |
| { |
| json::object *obj = new json::object (); |
| |
| obj->set ("impl_location", |
| impl_location_to_json (optinfo->get_impl_location ())); |
| |
| const char *kind_str = optinfo_kind_to_string (optinfo->get_kind ()); |
| obj->set ("kind", new json::string (kind_str)); |
| json::array *message = new json::array (); |
| obj->set ("message", message); |
| for (unsigned i = 0; i < optinfo->num_items (); i++) |
| { |
| const optinfo_item *item = optinfo->get_item (i); |
| switch (item->get_kind ()) |
| { |
| default: |
| gcc_unreachable (); |
| case OPTINFO_ITEM_KIND_TEXT: |
| { |
| message->append (new json::string (item->get_text ())); |
| } |
| break; |
| case OPTINFO_ITEM_KIND_TREE: |
| { |
| json::object *json_item = new json::object (); |
| json_item->set ("expr", new json::string (item->get_text ())); |
| |
| /* Capture any location for the node. */ |
| if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION) |
| json_item->set ("location", |
| location_to_json (item->get_location ())); |
| |
| message->append (json_item); |
| } |
| break; |
| case OPTINFO_ITEM_KIND_GIMPLE: |
| { |
| json::object *json_item = new json::object (); |
| json_item->set ("stmt", new json::string (item->get_text ())); |
| |
| /* Capture any location for the stmt. */ |
| if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION) |
| json_item->set ("location", |
| location_to_json (item->get_location ())); |
| |
| message->append (json_item); |
| } |
| break; |
| case OPTINFO_ITEM_KIND_SYMTAB_NODE: |
| { |
| json::object *json_item = new json::object (); |
| json_item->set ("symtab_node", new json::string (item->get_text ())); |
| |
| /* Capture any location for the node. */ |
| if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION) |
| json_item->set ("location", |
| location_to_json (item->get_location ())); |
| message->append (json_item); |
| } |
| break; |
| } |
| } |
| |
| if (optinfo->get_pass ()) |
| obj->set ("pass", get_id_value_for_pass (optinfo->get_pass ())); |
| |
| profile_count count = optinfo->get_count (); |
| if (count.initialized_p ()) |
| obj->set ("count", profile_count_to_json (count)); |
| |
| /* Record any location, handling the case where of an UNKNOWN_LOCATION |
| within an inlined block. */ |
| location_t loc = optinfo->get_location_t (); |
| if (get_pure_location (line_table, loc) != UNKNOWN_LOCATION) |
| { |
| // TOOD: record the location (just caret for now) |
| // TODO: start/finish also? |
| obj->set ("location", location_to_json (loc)); |
| } |
| |
| if (current_function_decl) |
| { |
| const char *fnname |
| = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (current_function_decl)); |
| obj->set ("function", new json::string (fnname)); |
| } |
| |
| if (loc != UNKNOWN_LOCATION) |
| obj->set ("inlining_chain", inlining_chain_to_json (loc)); |
| |
| return obj; |
| } |
| |
| /* Add a json description of PASS and its siblings to ARR, recursing into |
| child passes (adding their descriptions within a "children" array). */ |
| |
| void |
| optrecord_json_writer::add_pass_list (json::array *arr, opt_pass *pass) |
| { |
| do |
| { |
| json::object *pass_obj = pass_to_json (pass); |
| arr->append (pass_obj); |
| if (pass->sub) |
| { |
| json::array *sub = new json::array (); |
| pass_obj->set ("children", sub); |
| add_pass_list (sub, pass->sub); |
| } |
| pass = pass->next; |
| } |
| while (pass); |
| } |
| |
| #if CHECKING_P |
| |
| namespace selftest { |
| |
| /* Verify that we can build a JSON optimization record from dump_* |
| calls. */ |
| |
| static void |
| test_building_json_from_dump_calls () |
| { |
| temp_dump_context tmp (true, true, MSG_NOTE); |
| dump_user_location_t loc; |
| dump_printf_loc (MSG_NOTE, loc, "test of tree: "); |
| dump_generic_expr (MSG_NOTE, TDF_SLIM, integer_zero_node); |
| optinfo *info = tmp.get_pending_optinfo (); |
| ASSERT_TRUE (info != NULL); |
| ASSERT_EQ (info->num_items (), 2); |
| |
| optrecord_json_writer writer; |
| json::object *json_obj = writer.optinfo_to_json (info); |
| ASSERT_TRUE (json_obj != NULL); |
| |
| /* Verify that the json is sane. */ |
| pretty_printer pp; |
| json_obj->print (&pp); |
| const char *json_str = pp_formatted_text (&pp); |
| ASSERT_STR_CONTAINS (json_str, "impl_location"); |
| ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\""); |
| ASSERT_STR_CONTAINS (json_str, |
| "\"message\": [\"test of tree: \", {\"expr\": \"0\"}]"); |
| delete json_obj; |
| } |
| |
| /* Run all of the selftests within this file. */ |
| |
| void |
| optinfo_emit_json_cc_tests () |
| { |
| test_building_json_from_dump_calls (); |
| } |
| |
| } // namespace selftest |
| |
| #endif /* CHECKING_P */ |