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 | |
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 | |
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.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 | |
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 |
103 | optrecord_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 | |
138 | void |
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 (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 | |
158 | void |
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 (v: obj); |
164 | } |
165 | |
166 | /* Pop the innermost scope. */ |
167 | |
168 | void |
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 * |
180 | optrecord_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 | |
192 | json::object * |
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_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 | |
206 | json::object * |
207 | optrecord_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 | |
218 | json::string * |
219 | optrecord_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 | |
229 | json::object * |
230 | optrecord_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 | |
272 | json::value * |
273 | optrecord_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 | |
329 | json::object * |
330 | optrecord_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 | |
427 | void |
428 | optrecord_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 | |
447 | namespace selftest { |
448 | |
449 | /* Verify that we can build a JSON optimization record from dump_* |
450 | calls. */ |
451 | |
452 | static void |
453 | test_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 | |
480 | void |
481 | optinfo_emit_json_cc_tests () |
482 | { |
483 | test_building_json_from_dump_calls (); |
484 | } |
485 | |
486 | } // namespace selftest |
487 | |
488 | #endif /* CHECKING_P */ |
489 | |