/* Emit optimization information as JSON files. Copyright (C) 2018-2024 Free Software Foundation, Inc. Contributed by David Malcolm . 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 . */ #include "config.h" #define INCLUDE_MEMORY #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 /* 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_string ("format", "1"); json::object *generator = new json::object (); metadata->set ("generator", generator); generator->set_string ("name", lang_hooks.name); generator->set_string ("pkgversion", pkgversion_string); generator->set_string ("version", version_string); /* TARGET_NAME is passed in by the Makefile. */ generator->set_string ("target", 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, false); 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_string ("file", loc.m_file); obj->set_integer ("line", loc.m_line); if (loc.m_function) obj->set_string ("function", 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_string ("file", exploc.file); obj->set_integer ("line", exploc.line); obj->set_integer ("column", 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_integer ("value", count.to_gcov_type ()); obj->set_string ("quality", 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 (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_string ("type", type); obj->set_string ("name", pass->name); /* Represent the optgroup flags as an array. */ { json::array *optgroups = new json::array (); obj->set ("optgroups", optgroups); for (const kv_pair *optgroup = optgroup_options; optgroup->name != NULL; optgroup++) if (optgroup->value != OPTGROUP_ALL && (pass->optinfo_flags & optgroup->value)) optgroups->append_string (optgroup->name); } obj->set_integer ("num", 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_string ("fndecl", 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_string ("kind", 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_string (item->get_text ()); } break; case OPTINFO_ITEM_KIND_TREE: { json::object *json_item = new json::object (); json_item->set_string ("expr", 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_string ("stmt", 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_string ("symtab_node", 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_string ("function", 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, false); 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 */