1/* Emit optimization information as JSON files.
2 Copyright (C) 2018-2024 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.cc: print_version. */
59 json::object *metadata = new json::object ();
60 m_root_tuple->append (v: metadata);
61 metadata->set_string (key: "format", utf8_value: "1");
62 json::object *generator = new json::object ();
63 metadata->set (key: "generator", v: generator);
64 generator->set_string (key: "name", utf8_value: lang_hooks.name);
65 generator->set_string (key: "pkgversion", pkgversion_string);
66 generator->set_string (key: "version", version_string);
67 /* TARGET_NAME is passed in by the Makefile. */
68 generator->set_string (key: "target", TARGET_NAME);
69
70 /* TODO: capture command-line?
71 see gen_producer_string in dwarf2out.cc (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 (v: 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 (v: records);
88
89 m_scopes.safe_push (obj: 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: &pp, formatted: false);
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 (file: outfile, s: pp_formatted_text (&pp)) <= 0)
119 {
120 int tmp;
121 error_at (UNKNOWN_LOCATION, "error writing optimization records to %qs: %s",
122 filename, gzerror (file: outfile, errnum: &tmp));
123 emitted_error = true;
124 }
125
126 cleanup:
127 if (outfile)
128 if (gzclose (file: outfile) != Z_OK)
129 if (!emitted_error)
130 error_at (UNKNOWN_LOCATION, "error closing optimization records %qs",
131 filename);
132
133 free (ptr: 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 (key: "children", v: children);
150 m_scopes.safe_push (obj: 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 (v: 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_string (key: "file", utf8_value: loc.m_file);
184 obj->set_integer (key: "line", v: loc.m_line);
185 if (loc.m_function)
186 obj->set_string (key: "function", utf8_value: 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_string (key: "file", utf8_value: exploc.file);
199 obj->set_integer (key: "line", v: exploc.line);
200 obj->set_integer (key: "column", v: 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_integer (key: "value", v: count.to_gcov_type ());
211 obj->set_string (key: "quality", utf8_value: profile_quality_as_string (count.quality ()));
212 return obj;
213}
214
215/* Get a string for use when referring to PASS in the saved optimization
216 records. */
217
218json::string *
219optrecord_json_writer::get_id_value_for_pass (opt_pass *pass)
220{
221 pretty_printer pp;
222 /* this is host-dependent, but will be consistent for a given host. */
223 pp_pointer (&pp, static_cast<void *> (pass));
224 return new json::string (pp_formatted_text (&pp));
225}
226
227/* Create a JSON object representing PASS. */
228
229json::object *
230optrecord_json_writer::pass_to_json (opt_pass *pass)
231{
232 json::object *obj = new json::object ();
233 const char *type = NULL;
234 switch (pass->type)
235 {
236 default:
237 gcc_unreachable ();
238 case GIMPLE_PASS:
239 type = "gimple";
240 break;
241 case RTL_PASS:
242 type = "rtl";
243 break;
244 case SIMPLE_IPA_PASS:
245 type = "simple_ipa";
246 break;
247 case IPA_PASS:
248 type = "ipa";
249 break;
250 }
251 obj->set (key: "id", v: get_id_value_for_pass (pass));
252 obj->set_string (key: "type", utf8_value: type);
253 obj->set_string (key: "name", utf8_value: pass->name);
254 /* Represent the optgroup flags as an array. */
255 {
256 json::array *optgroups = new json::array ();
257 obj->set (key: "optgroups", v: optgroups);
258 for (const kv_pair<optgroup_flags_t> *optgroup = optgroup_options;
259 optgroup->name != NULL; optgroup++)
260 if (optgroup->value != OPTGROUP_ALL
261 && (pass->optinfo_flags & optgroup->value))
262 optgroups->append (v: new json::string (optgroup->name));
263 }
264 obj->set_integer (key: "num", v: pass->static_pass_number);
265 return obj;
266}
267
268/* Create a JSON array for LOC representing the chain of inlining
269 locations.
270 Compare with lhd_print_error_function and cp_print_error_function. */
271
272json::value *
273optrecord_json_writer::inlining_chain_to_json (location_t loc)
274{
275 json::array *array = new json::array ();
276
277 tree abstract_origin = LOCATION_BLOCK (loc);
278
279 while (abstract_origin)
280 {
281 location_t *locus;
282 tree block = abstract_origin;
283
284 locus = &BLOCK_SOURCE_LOCATION (block);
285 tree fndecl = NULL;
286 block = BLOCK_SUPERCONTEXT (block);
287 while (block && TREE_CODE (block) == BLOCK
288 && BLOCK_ABSTRACT_ORIGIN (block))
289 {
290 tree ao = BLOCK_ABSTRACT_ORIGIN (block);
291 if (TREE_CODE (ao) == FUNCTION_DECL)
292 {
293 fndecl = ao;
294 break;
295 }
296 else if (TREE_CODE (ao) != BLOCK)
297 break;
298
299 block = BLOCK_SUPERCONTEXT (block);
300 }
301 if (fndecl)
302 abstract_origin = block;
303 else
304 {
305 while (block && TREE_CODE (block) == BLOCK)
306 block = BLOCK_SUPERCONTEXT (block);
307
308 if (block && TREE_CODE (block) == FUNCTION_DECL)
309 fndecl = block;
310 abstract_origin = NULL;
311 }
312 if (fndecl)
313 {
314 json::object *obj = new json::object ();
315 const char *printable_name
316 = lang_hooks.decl_printable_name (fndecl, 2);
317 obj->set_string (key: "fndecl", utf8_value: printable_name);
318 if (LOCATION_LOCUS (*locus) != UNKNOWN_LOCATION)
319 obj->set (key: "site", v: location_to_json (loc: *locus));
320 array->append (v: obj);
321 }
322 }
323
324 return array;
325}
326
327/* Create a JSON object representing OPTINFO. */
328
329json::object *
330optrecord_json_writer::optinfo_to_json (const optinfo *optinfo)
331{
332 json::object *obj = new json::object ();
333
334 obj->set (key: "impl_location",
335 v: impl_location_to_json (loc: optinfo->get_impl_location ()));
336
337 const char *kind_str = optinfo_kind_to_string (kind: optinfo->get_kind ());
338 obj->set_string (key: "kind", utf8_value: kind_str);
339 json::array *message = new json::array ();
340 obj->set (key: "message", v: message);
341 for (unsigned i = 0; i < optinfo->num_items (); i++)
342 {
343 const optinfo_item *item = optinfo->get_item (i);
344 switch (item->get_kind ())
345 {
346 default:
347 gcc_unreachable ();
348 case OPTINFO_ITEM_KIND_TEXT:
349 {
350 message->append (v: new json::string (item->get_text ()));
351 }
352 break;
353 case OPTINFO_ITEM_KIND_TREE:
354 {
355 json::object *json_item = new json::object ();
356 json_item->set_string (key: "expr", utf8_value: item->get_text ());
357
358 /* Capture any location for the node. */
359 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION)
360 json_item->set (key: "location",
361 v: location_to_json (loc: item->get_location ()));
362
363 message->append (v: json_item);
364 }
365 break;
366 case OPTINFO_ITEM_KIND_GIMPLE:
367 {
368 json::object *json_item = new json::object ();
369 json_item->set_string (key: "stmt", utf8_value: item->get_text ());
370
371 /* Capture any location for the stmt. */
372 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION)
373 json_item->set (key: "location",
374 v: location_to_json (loc: item->get_location ()));
375
376 message->append (v: json_item);
377 }
378 break;
379 case OPTINFO_ITEM_KIND_SYMTAB_NODE:
380 {
381 json::object *json_item = new json::object ();
382 json_item->set_string (key: "symtab_node", utf8_value: item->get_text ());
383
384 /* Capture any location for the node. */
385 if (LOCATION_LOCUS (item->get_location ()) != UNKNOWN_LOCATION)
386 json_item->set (key: "location",
387 v: location_to_json (loc: item->get_location ()));
388 message->append (v: json_item);
389 }
390 break;
391 }
392 }
393
394 if (optinfo->get_pass ())
395 obj->set (key: "pass", v: get_id_value_for_pass (pass: optinfo->get_pass ()));
396
397 profile_count count = optinfo->get_count ();
398 if (count.initialized_p ())
399 obj->set (key: "count", v: profile_count_to_json (count));
400
401 /* Record any location, handling the case where of an UNKNOWN_LOCATION
402 within an inlined block. */
403 location_t loc = optinfo->get_location_t ();
404 if (get_pure_location (set: line_table, loc) != UNKNOWN_LOCATION)
405 {
406 // TOOD: record the location (just caret for now)
407 // TODO: start/finish also?
408 obj->set (key: "location", v: location_to_json (loc));
409 }
410
411 if (current_function_decl)
412 {
413 const char *fnname
414 = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (current_function_decl));
415 obj->set_string (key: "function", utf8_value: fnname);
416 }
417
418 if (loc != UNKNOWN_LOCATION)
419 obj->set (key: "inlining_chain", v: inlining_chain_to_json (loc));
420
421 return obj;
422}
423
424/* Add a json description of PASS and its siblings to ARR, recursing into
425 child passes (adding their descriptions within a "children" array). */
426
427void
428optrecord_json_writer::add_pass_list (json::array *arr, opt_pass *pass)
429{
430 do
431 {
432 json::object *pass_obj = pass_to_json (pass);
433 arr->append (v: pass_obj);
434 if (pass->sub)
435 {
436 json::array *sub = new json::array ();
437 pass_obj->set (key: "children", v: sub);
438 add_pass_list (arr: sub, pass: pass->sub);
439 }
440 pass = pass->next;
441 }
442 while (pass);
443}
444
445#if CHECKING_P
446
447namespace selftest {
448
449/* Verify that we can build a JSON optimization record from dump_*
450 calls. */
451
452static void
453test_building_json_from_dump_calls ()
454{
455 temp_dump_context tmp (true, true, MSG_NOTE);
456 dump_user_location_t loc;
457 dump_printf_loc (MSG_NOTE, loc, "test of tree: ");
458 dump_generic_expr (MSG_NOTE, TDF_SLIM, integer_zero_node);
459 optinfo *info = tmp.get_pending_optinfo ();
460 ASSERT_TRUE (info != NULL);
461 ASSERT_EQ (info->num_items (), 2);
462
463 optrecord_json_writer writer;
464 json::object *json_obj = writer.optinfo_to_json (optinfo: info);
465 ASSERT_TRUE (json_obj != NULL);
466
467 /* Verify that the json is sane. */
468 pretty_printer pp;
469 json_obj->print (pp: &pp, formatted: false);
470 const char *json_str = pp_formatted_text (&pp);
471 ASSERT_STR_CONTAINS (json_str, "impl_location");
472 ASSERT_STR_CONTAINS (json_str, "\"kind\": \"note\"");
473 ASSERT_STR_CONTAINS (json_str,
474 "\"message\": [\"test of tree: \", {\"expr\": \"0\"}]");
475 delete json_obj;
476}
477
478/* Run all of the selftests within this file. */
479
480void
481optinfo_emit_json_cc_tests ()
482{
483 test_building_json_from_dump_calls ();
484}
485
486} // namespace selftest
487
488#endif /* CHECKING_P */
489

source code of gcc/optinfo-emit-json.cc