1/* Emit optimization information as JSON files. 2 Copyright (C) 2018-2020 Free Software Foundation, Inc. 3 Contributed by David Malcolm <dmalcolm@redhat.com>. 4 5This file is part of GCC. 6 7GCC is free software; you can redistribute it and/or modify it under 8the terms of the GNU General Public License as published by the Free 9Software Foundation; either version 3, or (at your option) any later 10version. 11 12GCC is distributed in the hope that it will be useful, but WITHOUT ANY 13WARRANTY; without even the implied warranty of MERCHANTABILITY or 14FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15for more details. 16 17You should have received a copy of the GNU General Public License 18along with GCC; see the file COPYING3. If not see 19<http://www.gnu.org/licenses/>. */ 20 21#include "config.h" 22#include "system.h" 23#include "coretypes.h" 24 25#include "backend.h" 26#include "tree.h" 27#include "gimple.h" 28#include "diagnostic-core.h" 29 30#include "profile.h" 31#include "output.h" 32#include "tree-pass.h" 33 34#include "optinfo.h" 35#include "optinfo-emit-json.h" 36#include "json.h" 37#include "pretty-print.h" 38#include "tree-pretty-print.h" 39#include "gimple-pretty-print.h" 40#include "cgraph.h" 41 42#include "langhooks.h" 43#include "version.h" 44#include "context.h" 45#include "pass_manager.h" 46#include "selftest.h" 47#include "dump-context.h" 48#include <zlib.h> 49 50/* optrecord_json_writer's ctor. Populate the top-level parts of the 51 in-memory JSON representation. */ 52 53optrecord_json_writer::optrecord_json_writer () 54 : m_root_tuple (NULL), m_scopes () 55{ 56 m_root_tuple = new json::array (); 57 58 /* Populate with metadata; compare with toplev.c: print_version. */ 59 json::object *metadata = new json::object (); 60 m_root_tuple->append (metadata); 61 metadata->set ("format", new json::string ("1")); 62 json::object *generator = new json::object (); 63 metadata->set ("generator", generator); 64 generator->set ("name", new json::string (lang_hooks.name)); 65 generator->set ("pkgversion", new json::string (pkgversion_string)); 66 generator->set ("version", new json::string (version_string)); 67 /* TARGET_NAME is passed in by the Makefile. */ 68 generator->set ("target", new json::string (TARGET_NAME)); 69 70 /* TODO: capture command-line? 71 see gen_producer_string in dwarf2out.c (currently static). */ 72 73 /* TODO: capture "any plugins?" flag (or the plugins themselves). */ 74 75 json::array *passes = new json::array (); 76 m_root_tuple->append (passes); 77 78 /* Call add_pass_list for all of the pass lists. */ 79 { 80#define DEF_PASS_LIST(LIST) \ 81 add_pass_list (passes, g->get_passes ()->LIST); 82 GCC_PASS_LISTS 83#undef DEF_PASS_LIST 84 } 85 86 json::array *records = new json::array (); 87 m_root_tuple->append (records); 88 89 m_scopes.safe_push (records); 90} 91 92/* optrecord_json_writer's ctor. 93 Delete the in-memory JSON representation. */ 94 95optrecord_json_writer::~optrecord_json_writer () 96{ 97 delete m_root_tuple; 98} 99 100/* Choose an appropriate filename, and write the saved records to it. */ 101 102void 103optrecord_json_writer::write () const 104{ 105 pretty_printer pp; 106 m_root_tuple->print (&pp); 107 108 bool emitted_error = false; 109 char *filename = concat (dump_base_name, ".opt-record.json.gz", NULL); 110 gzFile outfile = gzopen (filename, "w"); 111 if (outfile == NULL) 112 { 113 error_at (UNKNOWN_LOCATION, "cannot open file %qs for writing optimization records", 114 filename); // FIXME: more info? 115 goto cleanup; 116 } 117 118 if (gzputs (outfile, pp_formatted_text (&pp)) <= 0) 119 { 120 int tmp; 121 error_at (UNKNOWN_LOCATION, "error writing optimization records to %qs: %s", 122 filename, gzerror (outfile, &tmp)); 123 emitted_error = true; 124 } 125 126 cleanup: 127 if (outfile) 128 if (gzclose (outfile) != Z_OK) 129 if (!emitted_error) 130 error_at (UNKNOWN_LOCATION, "error closing optimization records %qs", 131 filename); 132 133 free (filename); 134} 135 136/* Add a record for OPTINFO to the queue of records to be written. */ 137 138void 139optrecord_json_writer::add_record (const optinfo *optinfo) 140{ 141 json::object *obj = optinfo_to_json (optinfo); 142 143 add_record (obj); 144 145 /* Potentially push the scope. */ 146 if (optinfo->get_kind () == OPTINFO_KIND_SCOPE) 147 { 148 json::array *children = new json::array (); 149 obj->set ("children", children); 150 m_scopes.safe_push (children); 151 } 152} 153 154/* Private methods of optrecord_json_writer. */ 155 156/* Add record OBJ to the innermost scope. */ 157 158void 159optrecord_json_writer::add_record (json::object *obj) 160{ 161 /* Add to innermost scope. */ 162 gcc_assert (m_scopes.length () > 0); 163 m_scopes[m_scopes.length () - 1]->append (obj); 164} 165 166/* Pop the innermost scope. */ 167 168void 169optrecord_json_writer::pop_scope () 170{ 171 m_scopes.pop (); 172 173 /* We should never pop the top-level records array. */ 174 gcc_assert (m_scopes.length () > 0); 175} 176 177/* Create a JSON object representing LOC. */ 178 179json::object * 180optrecord_json_writer::impl_location_to_json (dump_impl_location_t loc) 181{ 182 json::object *obj = new json::object (); 183 obj->set ("file", new json::string (loc.m_file)); 184 obj->set ("line", new json::integer_number (loc.m_line)); 185 if (loc.m_function) 186 obj->set ("function", new json::string (loc.m_function)); 187 return obj; 188} 189 190/* Create a JSON object representing LOC. */ 191 192json::object * 193optrecord_json_writer::location_to_json (location_t loc) 194{ 195 gcc_assert (LOCATION_LOCUS (loc) != UNKNOWN_LOCATION); 196 expanded_location exploc = expand_location (loc); 197 json::object *obj = new json::object (); 198 obj->set ("file", new json::string (exploc.file)); 199 obj->set ("line", new json::integer_number (exploc.line)); 200 obj->set ("column", new json::integer_number (exploc.column)); 201 return obj; 202} 203 204/* Create a JSON object representing COUNT. */ 205 206json::object * 207optrecord_json_writer::profile_count_to_json (profile_count count) 208{ 209 json::object *obj = new json::object (); 210 obj->set ("value", new json::integer_number (count.to_gcov_type ())); 211 obj->set ("quality", 212 new json::string (profile_quality_as_string (count.quality ()))); 213 return obj; 214} 215 216/* Get a string for use when referring to PASS in the saved optimization 217 records. */ 218 219json::string * 220optrecord_json_writer::get_id_value_for_pass (opt_pass *pass) 221{ 222 pretty_printer pp; 223 /* this is host-dependent, but will be consistent for a given host. */ 224 pp_pointer (&pp, static_cast<void *> (pass)); 225 return new json::string (pp_formatted_text (&pp)); 226} 227 228/* Create a JSON object representing PASS. */ 229 230json::object * 231optrecord_json_writer::pass_to_json (opt_pass *pass) 232{ 233 json::object *obj = new json::object (); 234 const char *type = NULL; 235 switch (pass->type) 236 { 237 default: 238 gcc_unreachable (); 239 case GIMPLE_PASS: 240 type = "gimple"; 241 break; 242 case RTL_PASS: 243 type = "rtl"; 244 break; 245 case SIMPLE_IPA_PASS: 246 type = "simple_ipa"; 247 break; 248 case IPA_PASS: 249 type = "ipa"; 250 break; 251 } 252 obj->set ("id", get_id_value_for_pass (pass)); 253 obj->set ("type", new json::string (type)); 254 obj->set ("name", new json::string (pass->name)); 255 /* Represent the optgroup flags as an array. */ 256 { 257 json::array *optgroups = new json::array (); 258 obj->set ("optgroups", optgroups); 259 for (const kv_pair<optgroup_flags_t> *optgroup = optgroup_options; 260 optgroup->name != NULL; optgroup++) 261 if (optgroup->value != OPTGROUP_ALL 262 && (pass->optinfo_flags & optgroup->value)) 263 optgroups->append (new json::string (optgroup->name)); 264 } 265 obj->set ("num", new json::integer_number (pass->static_pass_number)); 266 return obj; 267} 268 269/* Create a JSON array for LOC representing the chain of inlining 270 locations. 271 Compare with lhd_print_error_function and cp_print_error_function. */ 272 273json::value * 274optrecord_json_writer::inlining_chain_to_json (location_t loc) 275{ 276 json::array *array = new json::array (); 277 278 tree abstract_origin = LOCATION_BLOCK (loc); 279 280 while (abstract_origin) 281 { 282 location_t *locus; 283 tree block = abstract_origin; 284 285 locus = &BLOCK_SOURCE_LOCATION (block); 286 tree fndecl = NULL; 287 block = BLOCK_SUPERCONTEXT (block); 288 while (block && TREE_CODE (block) == BLOCK 289 && BLOCK_ABSTRACT_ORIGIN (block)) 290 { 291 tree ao = BLOCK_ABSTRACT_ORIGIN (block); 292 if (TREE_CODE (ao) == FUNCTION_DECL) 293 { 294 fndecl = ao; 295 break; 296 } 297 else if (TREE_CODE (ao) != BLOCK) 298 break; 299 300 block = BLOCK_SUPERCONTEXT (block); 301 } 302 if (fndecl) 303 abstract_origin = block; 304 else 305 { 306 while (block && TREE_CODE (block) == BLOCK) 307 block = BLOCK_SUPERCONTEXT (block); 308 309 if (block && TREE_CODE (block) == FUNCTION_DECL) 310 fndecl = block; 311 abstract_origin = NULL; 312 } 313 if (fndecl) 314 { 315 json::object *obj = new json::object (); 316 const char *printable_name 317 = lang_hooks.decl_printable_name (fndecl, 2); 318 obj->set ("fndecl", new json::string (printable_name)); 319 if (LOCATION_LOCUS (*locus) != UNKNOWN_LOCATION) 320 obj->set ("site", location_to_json (*locus)); 321 array->append (obj); 322 } 323 } 324 325 return array; 326} 327 328/* Create a JSON object representing OPTINFO. */ 329 330json::object * 331optrecord_json_writer::optinfo_to_json (const optinfo *optinfo) 332{ 333 json::object *obj = new json::object (); 334 335 obj->set ("impl_location", 336 impl_location_to_json (optinfo->get_impl_location ())); 337 338 const char *kind_str = optinfo_kind_to_string (optinfo->get_kind ()); 339 obj->set ("kind", new json::string (kind_str)); 340 json::array *message = new json::array (); 341 obj->set ("message", message); 342 for (unsigned i = 0; i < optinfo->num_items (); i++) 343 { 344 const optinfo_item *item = optinfo->get_item (i); 345 switch (item->get_kind ()) 346 { 347 default: 348 gcc_unreachable (); 349 case OPTINFO_ITEM_KIND_TEXT: 350 { 351 message->append (new json::string (item->get_text ())); 352 } 353 break; 354 case OPTINFO_ITEM_KIND_TREE: 355 { 356 json::object *json_item = new json::object (); 357 json_item->set ("expr", new json::string (item->get_text ())); 358 359 /* Capture any location for the node. */ 360 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION) 361 json_item->set ("location", 362 location_to_json (item->get_location ())); 363 364 message->append (json_item); 365 } 366 break; 367 case OPTINFO_ITEM_KIND_GIMPLE: 368 { 369 json::object *json_item = new json::object (); 370 json_item->set ("stmt", new json::string (item->get_text ())); 371 372 /* Capture any location for the stmt. */ 373 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION) 374 json_item->set ("location", 375 location_to_json (item->get_location ())); 376 377 message->append (json_item); 378 } 379 break; 380 case OPTINFO_ITEM_KIND_SYMTAB_NODE: 381 { 382 json::object *json_item = new json::object (); 383 json_item->set ("symtab_node", new json::string (item->get_text ())); 384 385 /* Capture any location for the node. */ 386 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION) 387 json_item->set ("location", 388 location_to_json (item->get_location ())); 389 message->append (json_item); 390 } 391 break; 392 } 393 } 394 395 if (optinfo->get_pass ()) 396 obj->set ("pass", get_id_value_for_pass (optinfo->get_pass ())); 397 398 profile_count count = optinfo->get_count (); 399 if (count.initialized_p ()) 400 obj->set ("count", profile_count_to_json (count)); 401 402 /* Record any location, handling the case where of an UNKNOWN_LOCATION 403 within an inlined block. */ 404 location_t loc = optinfo->get_location_t (); 405 if (get_pure_location (line_table, loc) != UNKNOWN_LOCATION) 406 { 407 // TOOD: record the location (just caret for now) 408 // TODO: start/finish also? 409 obj->set ("location", location_to_json (loc)); 410 } 411 412 if (current_function_decl) 413 { 414 const char *fnname 415 = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (current_function_decl)); 416 obj->set ("function", new json::string (fnname)); 417 } 418 419 if (loc != UNKNOWN_LOCATION) 420 obj->set ("inlining_chain", inlining_chain_to_json (loc)); 421 422 return obj; 423} 424 425/* Add a json description of PASS and its siblings to ARR, recursing into 426 child passes (adding their descriptions within a "children" array). */ 427 428void 429optrecord_json_writer::add_pass_list (json::array *arr, opt_pass *pass) 430{ 431 do 432 { 433 json::object *pass_obj = pass_to_json (pass); 434 arr->append (pass_obj); 435 if (pass->sub) 436 { 437 json::array *sub = new json::array (); 438 pass_obj->set ("children", sub); 439 add_pass_list (sub, pass->sub); 440 } 441 pass = pass->next; 442 } 443 while (pass); 444} 445 446#if CHECKING_P 447 448namespace selftest { 449 450/* Verify that we can build a JSON optimization record from dump_* 451 calls. */ 452 453static void 454test_building_json_from_dump_calls () 455{ 456 temp_dump_context tmp (true, true, MSG_NOTE); 457 dump_user_location_t loc; 458 dump_printf_loc (MSG_NOTE, loc, "test of tree: "); 459 dump_generic_expr (MSG_NOTE, TDF_SLIM, integer_zero_node); 460 optinfo *info = tmp.get_pending_optinfo (); 461 ASSERT_TRUE (info != NULL); 462 ASSERT_EQ (info->num_items (), 2); 463 464 optrecord_json_writer writer; 465 json::object *json_obj = writer.optinfo_to_json (info); 466 ASSERT_TRUE (json_obj != NULL); 467 468 /* Verify that the json is sane. */ 469 pretty_printer pp; 470 json_obj->print (&pp); 471 const char *json_str = pp_formatted_text (&pp); 472 ASSERT_STR_CONTAINS (json_str, "impl_location"); 473 ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\""); 474 ASSERT_STR_CONTAINS (json_str, 475 "\"message\": [\"test of tree: \", {\"expr\": \"0\"}]"); 476 delete json_obj; 477} 478 479/* Run all of the selftests within this file. */ 480 481void 482optinfo_emit_json_cc_tests () 483{ 484 test_building_json_from_dump_calls (); 485} 486 487} // namespace selftest 488 489#endif /* CHECKING_P */ 490