1 /* Emit optimization information as JSON files.
2    Copyright (C) 2018-2021 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 #include <zlib.h>
49 
50 /* optrecord_json_writer's ctor.  Populate the top-level parts of the
51    in-memory JSON representation.  */
52 
optrecord_json_writer()53 optrecord_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 
~optrecord_json_writer()95 optrecord_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 
102 void
write() const103 optrecord_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 
138 void
add_record(const optinfo * optinfo)139 optrecord_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 
158 void
add_record(json::object * obj)159 optrecord_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 
168 void
pop_scope()169 optrecord_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 
179 json::object *
impl_location_to_json(dump_impl_location_t loc)180 optrecord_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 
192 json::object *
location_to_json(location_t loc)193 optrecord_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 
206 json::object *
profile_count_to_json(profile_count count)207 optrecord_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 
219 json::string *
get_id_value_for_pass(opt_pass * pass)220 optrecord_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 
230 json::object *
pass_to_json(opt_pass * pass)231 optrecord_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 
273 json::value *
inlining_chain_to_json(location_t loc)274 optrecord_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 
330 json::object *
optinfo_to_json(const optinfo * optinfo)331 optrecord_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 
428 void
add_pass_list(json::array * arr,opt_pass * pass)429 optrecord_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 
448 namespace selftest {
449 
450 /* Verify that we can build a JSON optimization record from dump_*
451    calls.  */
452 
453 static void
test_building_json_from_dump_calls()454 test_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 
481 void
optinfo_emit_json_cc_tests()482 optinfo_emit_json_cc_tests ()
483 {
484   test_building_json_from_dump_calls ();
485 }
486 
487 } // namespace selftest
488 
489 #endif /* CHECKING_P */
490