131
|
1 /* Emit optimization information as JSON files.
|
|
2 Copyright (C) 2018 Free Software Foundation, Inc.
|
|
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
4
|
|
5 This file is part of GCC.
|
|
6
|
|
7 GCC is free software; you can redistribute it and/or modify it under
|
|
8 the terms of the GNU General Public License as published by the Free
|
|
9 Software Foundation; either version 3, or (at your option) any later
|
|
10 version.
|
|
11
|
|
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
15 for more details.
|
|
16
|
|
17 You should have received a copy of the GNU General Public License
|
|
18 along 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
|
|
49 /* A class for writing out optimization records in JSON format. */
|
|
50
|
|
51 class optrecord_json_writer
|
|
52 {
|
|
53 public:
|
|
54 optrecord_json_writer ();
|
|
55 ~optrecord_json_writer ();
|
|
56 void write () const;
|
|
57 void add_record (const optinfo *optinfo);
|
|
58 void pop_scope ();
|
|
59
|
|
60 void add_record (json::object *obj);
|
|
61 json::object *impl_location_to_json (dump_impl_location_t loc);
|
|
62 json::object *location_to_json (location_t loc);
|
|
63 json::object *profile_count_to_json (profile_count count);
|
|
64 json::string *get_id_value_for_pass (opt_pass *pass);
|
|
65 json::object *pass_to_json (opt_pass *pass);
|
|
66 json::value *inlining_chain_to_json (location_t loc);
|
|
67 json::object *optinfo_to_json (const optinfo *optinfo);
|
|
68 void add_pass_list (json::array *arr, opt_pass *pass);
|
|
69
|
|
70 private:
|
|
71 /* The root value for the JSON file.
|
|
72 Currently the JSON values are stored in memory, and flushed when the
|
|
73 compiler exits. It would probably be better to simply write out
|
|
74 the JSON as we go. */
|
|
75 json::array *m_root_tuple;
|
|
76
|
|
77 /* The currently open scopes, for expressing nested optimization records. */
|
|
78 auto_vec<json::array *> m_scopes;
|
|
79 };
|
|
80
|
|
81 /* optrecord_json_writer's ctor. Populate the top-level parts of the
|
|
82 in-memory JSON representation. */
|
|
83
|
|
84 optrecord_json_writer::optrecord_json_writer ()
|
|
85 : m_root_tuple (NULL), m_scopes ()
|
|
86 {
|
|
87 m_root_tuple = new json::array ();
|
|
88
|
|
89 /* Populate with metadata; compare with toplev.c: print_version. */
|
|
90 json::object *metadata = new json::object ();
|
|
91 m_root_tuple->append (metadata);
|
|
92 metadata->set ("format", new json::string ("1"));
|
|
93 json::object *generator = new json::object ();
|
|
94 metadata->set ("generator", generator);
|
|
95 generator->set ("name", new json::string (lang_hooks.name));
|
|
96 generator->set ("pkgversion", new json::string (pkgversion_string));
|
|
97 generator->set ("version", new json::string (version_string));
|
|
98 /* TARGET_NAME is passed in by the Makefile. */
|
|
99 generator->set ("target", new json::string (TARGET_NAME));
|
|
100
|
|
101 /* TODO: capture command-line?
|
|
102 see gen_producer_string in dwarf2out.c (currently static). */
|
|
103
|
|
104 /* TODO: capture "any plugins?" flag (or the plugins themselves). */
|
|
105
|
|
106 json::array *passes = new json::array ();
|
|
107 m_root_tuple->append (passes);
|
|
108
|
|
109 /* Call add_pass_list for all of the pass lists. */
|
|
110 {
|
|
111 #define DEF_PASS_LIST(LIST) \
|
|
112 add_pass_list (passes, g->get_passes ()->LIST);
|
|
113 GCC_PASS_LISTS
|
|
114 #undef DEF_PASS_LIST
|
|
115 }
|
|
116
|
|
117 json::array *records = new json::array ();
|
|
118 m_root_tuple->append (records);
|
|
119
|
|
120 m_scopes.safe_push (records);
|
|
121 }
|
|
122
|
|
123 /* optrecord_json_writer's ctor.
|
|
124 Delete the in-memory JSON representation. */
|
|
125
|
|
126 optrecord_json_writer::~optrecord_json_writer ()
|
|
127 {
|
|
128 delete m_root_tuple;
|
|
129 }
|
|
130
|
|
131 /* Choose an appropriate filename, and write the saved records to it. */
|
|
132
|
|
133 void
|
|
134 optrecord_json_writer::write () const
|
|
135 {
|
|
136 char *filename = concat (dump_base_name, ".opt-record.json", NULL);
|
|
137 FILE *outfile = fopen (filename, "w");
|
|
138 if (outfile)
|
|
139 {
|
|
140 m_root_tuple->dump (outfile);
|
|
141 fclose (outfile);
|
|
142 }
|
|
143 else
|
|
144 error_at (UNKNOWN_LOCATION, "unable to write optimization records to %qs",
|
|
145 filename); // FIXME: more info?
|
|
146 free (filename);
|
|
147 }
|
|
148
|
|
149 /* Add a record for OPTINFO to the queue of records to be written. */
|
|
150
|
|
151 void
|
|
152 optrecord_json_writer::add_record (const optinfo *optinfo)
|
|
153 {
|
|
154 json::object *obj = optinfo_to_json (optinfo);
|
|
155
|
|
156 add_record (obj);
|
|
157
|
|
158 /* Potentially push the scope. */
|
|
159 if (optinfo->get_kind () == OPTINFO_KIND_SCOPE)
|
|
160 {
|
|
161 json::array *children = new json::array ();
|
|
162 obj->set ("children", children);
|
|
163 m_scopes.safe_push (children);
|
|
164 }
|
|
165 }
|
|
166
|
|
167 /* Private methods of optrecord_json_writer. */
|
|
168
|
|
169 /* Add record OBJ to the the innermost scope. */
|
|
170
|
|
171 void
|
|
172 optrecord_json_writer::add_record (json::object *obj)
|
|
173 {
|
|
174 /* Add to innermost scope. */
|
|
175 gcc_assert (m_scopes.length () > 0);
|
|
176 m_scopes[m_scopes.length () - 1]->append (obj);
|
|
177 }
|
|
178
|
|
179 /* Pop the innermost scope. */
|
|
180
|
|
181 void
|
|
182 optrecord_json_writer::pop_scope ()
|
|
183 {
|
|
184 m_scopes.pop ();
|
|
185 }
|
|
186
|
|
187 /* Create a JSON object representing LOC. */
|
|
188
|
|
189 json::object *
|
|
190 optrecord_json_writer::impl_location_to_json (dump_impl_location_t loc)
|
|
191 {
|
|
192 json::object *obj = new json::object ();
|
|
193 obj->set ("file", new json::string (loc.m_file));
|
|
194 obj->set ("line", new json::number (loc.m_line));
|
|
195 if (loc.m_function)
|
|
196 obj->set ("function", new json::string (loc.m_function));
|
|
197 return obj;
|
|
198 }
|
|
199
|
|
200 /* Create a JSON object representing LOC. */
|
|
201
|
|
202 json::object *
|
|
203 optrecord_json_writer::location_to_json (location_t loc)
|
|
204 {
|
|
205 gcc_assert (LOCATION_LOCUS (loc) != UNKNOWN_LOCATION);
|
|
206 expanded_location exploc = expand_location (loc);
|
|
207 json::object *obj = new json::object ();
|
|
208 obj->set ("file", new json::string (exploc.file));
|
|
209 obj->set ("line", new json::number (exploc.line));
|
|
210 obj->set ("column", new json::number (exploc.column));
|
|
211 return obj;
|
|
212 }
|
|
213
|
|
214 /* Create a JSON object representing COUNT. */
|
|
215
|
|
216 json::object *
|
|
217 optrecord_json_writer::profile_count_to_json (profile_count count)
|
|
218 {
|
|
219 json::object *obj = new json::object ();
|
|
220 obj->set ("value", new json::number (count.to_gcov_type ()));
|
|
221 obj->set ("quality",
|
|
222 new json::string (profile_quality_as_string (count.quality ())));
|
|
223 return obj;
|
|
224 }
|
|
225
|
|
226 /* Get a string for use when referring to PASS in the saved optimization
|
|
227 records. */
|
|
228
|
|
229 json::string *
|
|
230 optrecord_json_writer::get_id_value_for_pass (opt_pass *pass)
|
|
231 {
|
|
232 pretty_printer pp;
|
|
233 /* this is host-dependent, but will be consistent for a given host. */
|
|
234 pp_pointer (&pp, static_cast<void *> (pass));
|
|
235 return new json::string (pp_formatted_text (&pp));
|
|
236 }
|
|
237
|
|
238 /* Create a JSON object representing PASS. */
|
|
239
|
|
240 json::object *
|
|
241 optrecord_json_writer::pass_to_json (opt_pass *pass)
|
|
242 {
|
|
243 json::object *obj = new json::object ();
|
|
244 const char *type = NULL;
|
|
245 switch (pass->type)
|
|
246 {
|
|
247 default:
|
|
248 gcc_unreachable ();
|
|
249 case GIMPLE_PASS:
|
|
250 type = "gimple";
|
|
251 break;
|
|
252 case RTL_PASS:
|
|
253 type = "rtl";
|
|
254 break;
|
|
255 case SIMPLE_IPA_PASS:
|
|
256 type = "simple_ipa";
|
|
257 break;
|
|
258 case IPA_PASS:
|
|
259 type = "ipa";
|
|
260 break;
|
|
261 }
|
|
262 obj->set ("id", get_id_value_for_pass (pass));
|
|
263 obj->set ("type", new json::string (type));
|
|
264 obj->set ("name", new json::string (pass->name));
|
|
265 /* Represent the optgroup flags as an array. */
|
|
266 {
|
|
267 json::array *optgroups = new json::array ();
|
|
268 obj->set ("optgroups", optgroups);
|
|
269 for (const kv_pair<optgroup_flags_t> *optgroup = optgroup_options;
|
|
270 optgroup->name != NULL; optgroup++)
|
|
271 if (optgroup->value != OPTGROUP_ALL
|
|
272 && (pass->optinfo_flags & optgroup->value))
|
|
273 optgroups->append (new json::string (optgroup->name));
|
|
274 }
|
|
275 obj->set ("num", new json::number (pass->static_pass_number));
|
|
276 return obj;
|
|
277 }
|
|
278
|
|
279 /* Create a JSON array for LOC representing the chain of inlining
|
|
280 locations.
|
|
281 Compare with lhd_print_error_function and cp_print_error_function. */
|
|
282
|
|
283 json::value *
|
|
284 optrecord_json_writer::inlining_chain_to_json (location_t loc)
|
|
285 {
|
|
286 json::array *array = new json::array ();
|
|
287
|
|
288 tree abstract_origin = LOCATION_BLOCK (loc);
|
|
289
|
|
290 while (abstract_origin)
|
|
291 {
|
|
292 location_t *locus;
|
|
293 tree block = abstract_origin;
|
|
294
|
|
295 locus = &BLOCK_SOURCE_LOCATION (block);
|
|
296 tree fndecl = NULL;
|
|
297 block = BLOCK_SUPERCONTEXT (block);
|
|
298 while (block && TREE_CODE (block) == BLOCK
|
|
299 && BLOCK_ABSTRACT_ORIGIN (block))
|
|
300 {
|
|
301 tree ao = BLOCK_ABSTRACT_ORIGIN (block);
|
|
302 if (TREE_CODE (ao) == FUNCTION_DECL)
|
|
303 {
|
|
304 fndecl = ao;
|
|
305 break;
|
|
306 }
|
|
307 else if (TREE_CODE (ao) != BLOCK)
|
|
308 break;
|
|
309
|
|
310 block = BLOCK_SUPERCONTEXT (block);
|
|
311 }
|
|
312 if (fndecl)
|
|
313 abstract_origin = block;
|
|
314 else
|
|
315 {
|
|
316 while (block && TREE_CODE (block) == BLOCK)
|
|
317 block = BLOCK_SUPERCONTEXT (block);
|
|
318
|
|
319 if (block && TREE_CODE (block) == FUNCTION_DECL)
|
|
320 fndecl = block;
|
|
321 abstract_origin = NULL;
|
|
322 }
|
|
323 if (fndecl)
|
|
324 {
|
|
325 json::object *obj = new json::object ();
|
|
326 const char *printable_name
|
|
327 = lang_hooks.decl_printable_name (fndecl, 2);
|
|
328 obj->set ("fndecl", new json::string (printable_name));
|
|
329 if (LOCATION_LOCUS (*locus) != UNKNOWN_LOCATION)
|
|
330 obj->set ("site", location_to_json (*locus));
|
|
331 array->append (obj);
|
|
332 }
|
|
333 }
|
|
334
|
|
335 return array;
|
|
336 }
|
|
337
|
|
338 /* Create a JSON object representing OPTINFO. */
|
|
339
|
|
340 json::object *
|
|
341 optrecord_json_writer::optinfo_to_json (const optinfo *optinfo)
|
|
342 {
|
|
343 json::object *obj = new json::object ();
|
|
344
|
|
345 obj->set ("impl_location",
|
|
346 impl_location_to_json (optinfo->get_impl_location ()));
|
|
347
|
|
348 const char *kind_str = optinfo_kind_to_string (optinfo->get_kind ());
|
|
349 obj->set ("kind", new json::string (kind_str));
|
|
350 json::array *message = new json::array ();
|
|
351 obj->set ("message", message);
|
|
352 for (unsigned i = 0; i < optinfo->num_items (); i++)
|
|
353 {
|
|
354 const optinfo_item *item = optinfo->get_item (i);
|
|
355 switch (item->get_kind ())
|
|
356 {
|
|
357 default:
|
|
358 gcc_unreachable ();
|
|
359 case OPTINFO_ITEM_KIND_TEXT:
|
|
360 {
|
|
361 message->append (new json::string (item->get_text ()));
|
|
362 }
|
|
363 break;
|
|
364 case OPTINFO_ITEM_KIND_TREE:
|
|
365 {
|
|
366 json::object *json_item = new json::object ();
|
|
367 json_item->set ("expr", new json::string (item->get_text ()));
|
|
368
|
|
369 /* Capture any location for the node. */
|
|
370 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION)
|
|
371 json_item->set ("location",
|
|
372 location_to_json (item->get_location ()));
|
|
373
|
|
374 message->append (json_item);
|
|
375 }
|
|
376 break;
|
|
377 case OPTINFO_ITEM_KIND_GIMPLE:
|
|
378 {
|
|
379 json::object *json_item = new json::object ();
|
|
380 json_item->set ("stmt", new json::string (item->get_text ()));
|
|
381
|
|
382 /* Capture any location for the stmt. */
|
|
383 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION)
|
|
384 json_item->set ("location",
|
|
385 location_to_json (item->get_location ()));
|
|
386
|
|
387 message->append (json_item);
|
|
388 }
|
|
389 break;
|
|
390 case OPTINFO_ITEM_KIND_SYMTAB_NODE:
|
|
391 {
|
|
392 json::object *json_item = new json::object ();
|
|
393 json_item->set ("symtab_node", new json::string (item->get_text ()));
|
|
394
|
|
395 /* Capture any location for the node. */
|
|
396 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION)
|
|
397 json_item->set ("location",
|
|
398 location_to_json (item->get_location ()));
|
|
399 message->append (json_item);
|
|
400 }
|
|
401 break;
|
|
402 }
|
|
403 }
|
|
404
|
|
405 if (optinfo->get_pass ())
|
|
406 obj->set ("pass", get_id_value_for_pass (optinfo->get_pass ()));
|
|
407
|
|
408 profile_count count = optinfo->get_count ();
|
|
409 if (count.initialized_p ())
|
|
410 obj->set ("count", profile_count_to_json (count));
|
|
411
|
|
412 /* Record any location, handling the case where of an UNKNOWN_LOCATION
|
|
413 within an inlined block. */
|
|
414 location_t loc = optinfo->get_location_t ();
|
|
415 if (get_pure_location (line_table, loc) != UNKNOWN_LOCATION)
|
|
416 {
|
|
417 // TOOD: record the location (just caret for now)
|
|
418 // TODO: start/finish also?
|
|
419 obj->set ("location", location_to_json (loc));
|
|
420 }
|
|
421
|
|
422 if (current_function_decl)
|
|
423 {
|
|
424 const char *fnname = get_fnname_from_decl (current_function_decl);
|
|
425 obj->set ("function", new json::string (fnname));
|
|
426 }
|
|
427
|
|
428 if (loc != UNKNOWN_LOCATION)
|
|
429 obj->set ("inlining_chain", inlining_chain_to_json (loc));
|
|
430
|
|
431 return obj;
|
|
432 }
|
|
433
|
|
434 /* Add a json description of PASS and its siblings to ARR, recursing into
|
|
435 child passes (adding their descriptions within a "children" array). */
|
|
436
|
|
437 void
|
|
438 optrecord_json_writer::add_pass_list (json::array *arr, opt_pass *pass)
|
|
439 {
|
|
440 do
|
|
441 {
|
|
442 json::object *pass_obj = pass_to_json (pass);
|
|
443 arr->append (pass_obj);
|
|
444 if (pass->sub)
|
|
445 {
|
|
446 json::array *sub = new json::array ();
|
|
447 pass_obj->set ("children", sub);
|
|
448 add_pass_list (sub, pass->sub);
|
|
449 }
|
|
450 pass = pass->next;
|
|
451 }
|
|
452 while (pass);
|
|
453 }
|
|
454
|
|
455 /* File-level interface to rest of compiler (to avoid exposing
|
|
456 class optrecord_json_writer outside of this file). */
|
|
457
|
|
458 static optrecord_json_writer *the_json_writer;
|
|
459
|
|
460 /* Perform startup activity for -fsave-optimization-record. */
|
|
461
|
|
462 void
|
|
463 optimization_records_start ()
|
|
464 {
|
|
465 /* Bail if recording not enabled. */
|
|
466 if (!flag_save_optimization_record)
|
|
467 return;
|
|
468
|
|
469 the_json_writer = new optrecord_json_writer ();
|
|
470 }
|
|
471
|
|
472 /* Perform cleanup activity for -fsave-optimization-record.
|
|
473
|
|
474 Currently, the file is written out here in one go, before cleaning
|
|
475 up. */
|
|
476
|
|
477 void
|
|
478 optimization_records_finish ()
|
|
479 {
|
|
480 /* Bail if recording not enabled. */
|
|
481 if (!the_json_writer)
|
|
482 return;
|
|
483
|
|
484 the_json_writer->write ();
|
|
485
|
|
486 delete the_json_writer;
|
|
487 the_json_writer = NULL;
|
|
488 }
|
|
489
|
|
490 /* Did the user request optimization records to be written out? */
|
|
491
|
|
492 bool
|
|
493 optimization_records_enabled_p ()
|
|
494 {
|
|
495 return the_json_writer != NULL;
|
|
496 }
|
|
497
|
|
498 /* If optimization records were requested, then add a record for OPTINFO
|
|
499 to the queue of records to be written. */
|
|
500
|
|
501 void
|
|
502 optimization_records_maybe_record_optinfo (const optinfo *optinfo)
|
|
503 {
|
|
504 /* Bail if recording not enabled. */
|
|
505 if (!the_json_writer)
|
|
506 return;
|
|
507
|
|
508 the_json_writer->add_record (optinfo);
|
|
509 }
|
|
510
|
|
511 /* Handling for the end of a dump scope for the
|
|
512 optimization records sink. */
|
|
513
|
|
514 void
|
|
515 optimization_records_maybe_pop_dump_scope ()
|
|
516 {
|
|
517 /* Bail if recording not enabled. */
|
|
518 if (!the_json_writer)
|
|
519 return;
|
|
520
|
|
521 the_json_writer->pop_scope ();
|
|
522 }
|
|
523
|
|
524 #if CHECKING_P
|
|
525
|
|
526 namespace selftest {
|
|
527
|
|
528 /* Verify that we can build a JSON optimization record from dump_*
|
|
529 calls. */
|
|
530
|
|
531 static void
|
|
532 test_building_json_from_dump_calls ()
|
|
533 {
|
|
534 temp_dump_context tmp (true, true, MSG_NOTE);
|
|
535 dump_location_t loc;
|
|
536 dump_printf_loc (MSG_NOTE, loc, "test of tree: ");
|
|
537 dump_generic_expr (MSG_NOTE, TDF_SLIM, integer_zero_node);
|
|
538 optinfo *info = tmp.get_pending_optinfo ();
|
|
539 ASSERT_TRUE (info != NULL);
|
|
540 ASSERT_EQ (info->num_items (), 2);
|
|
541
|
|
542 optrecord_json_writer writer;
|
|
543 json::object *json_obj = writer.optinfo_to_json (info);
|
|
544 ASSERT_TRUE (json_obj != NULL);
|
|
545
|
|
546 /* Verify that the json is sane. */
|
|
547 pretty_printer pp;
|
|
548 json_obj->print (&pp);
|
|
549 const char *json_str = pp_formatted_text (&pp);
|
|
550 ASSERT_STR_CONTAINS (json_str, "impl_location");
|
|
551 ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\"");
|
|
552 ASSERT_STR_CONTAINS (json_str,
|
|
553 "\"message\": [\"test of tree: \", {\"expr\": \"0\"}]");
|
|
554 delete json_obj;
|
|
555 }
|
|
556
|
|
557 /* Run all of the selftests within this file. */
|
|
558
|
|
559 void
|
|
560 optinfo_emit_json_cc_tests ()
|
|
561 {
|
|
562 test_building_json_from_dump_calls ();
|
|
563 }
|
|
564
|
|
565 } // namespace selftest
|
|
566
|
|
567 #endif /* CHECKING_P */
|