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