1 | /* Emit optimization information as JSON files. |
2 | Copyright (C) 2018-2023 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 (key: "format" , v: new json::string ("1" )); |
62 | json::object *generator = new json::object (); |
63 | metadata->set (key: "generator" , v: generator); |
64 | generator->set (key: "name" , v: new json::string (lang_hooks.name)); |
65 | generator->set (key: "pkgversion" , v: new json::string (pkgversion_string)); |
66 | generator->set (key: "version" , v: new json::string (version_string)); |
67 | /* TARGET_NAME is passed in by the Makefile. */ |
68 | generator->set (key: "target" , v: new json::string (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); |
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 (key: "file" , v: new json::string (loc.m_file)); |
184 | obj->set (key: "line" , v: new json::integer_number (loc.m_line)); |
185 | if (loc.m_function) |
186 | obj->set (key: "function" , v: new json::string (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 (key: "file" , v: new json::string (exploc.file)); |
199 | obj->set (key: "line" , v: new json::integer_number (exploc.line)); |
200 | obj->set (key: "column" , v: new json::integer_number (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 (key: "value" , v: new json::integer_number (count.to_gcov_type ())); |
211 | obj->set (key: "quality" , |
212 | v: 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 * |
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 * |
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 (key: "id" , v: get_id_value_for_pass (pass)); |
253 | obj->set (key: "type" , v: new json::string (type)); |
254 | obj->set (key: "name" , v: new json::string (pass->name)); |
255 | /* Represent the optgroup flags as an array. */ |
256 | { |
257 | json::array *optgroups = new json::array (); |
258 | obj->set (key: "optgroups" , v: 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 (v: new json::string (optgroup->name)); |
264 | } |
265 | obj->set (key: "num" , v: 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 * |
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 (key: "fndecl" , v: new json::string (printable_name)); |
319 | if (LOCATION_LOCUS (*locus) != UNKNOWN_LOCATION) |
320 | obj->set (key: "site" , v: location_to_json (loc: *locus)); |
321 | array->append (v: obj); |
322 | } |
323 | } |
324 | |
325 | return array; |
326 | } |
327 | |
328 | /* Create a JSON object representing OPTINFO. */ |
329 | |
330 | json::object * |
331 | optrecord_json_writer::optinfo_to_json (const optinfo *optinfo) |
332 | { |
333 | json::object *obj = new json::object (); |
334 | |
335 | obj->set (key: "impl_location" , |
336 | v: impl_location_to_json (loc: optinfo->get_impl_location ())); |
337 | |
338 | const char *kind_str = optinfo_kind_to_string (kind: optinfo->get_kind ()); |
339 | obj->set (key: "kind" , v: new json::string (kind_str)); |
340 | json::array *message = new json::array (); |
341 | obj->set (key: "message" , v: 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 (v: 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 (key: "expr" , v: 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 (key: "location" , |
362 | v: location_to_json (loc: item->get_location ())); |
363 | |
364 | message->append (v: json_item); |
365 | } |
366 | break; |
367 | case OPTINFO_ITEM_KIND_GIMPLE: |
368 | { |
369 | json::object *json_item = new json::object (); |
370 | json_item->set (key: "stmt" , v: 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 (key: "location" , |
375 | v: location_to_json (loc: item->get_location ())); |
376 | |
377 | message->append (v: 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 (key: "symtab_node" , v: 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 (key: "location" , |
388 | v: location_to_json (loc: item->get_location ())); |
389 | message->append (v: json_item); |
390 | } |
391 | break; |
392 | } |
393 | } |
394 | |
395 | if (optinfo->get_pass ()) |
396 | obj->set (key: "pass" , v: get_id_value_for_pass (pass: optinfo->get_pass ())); |
397 | |
398 | profile_count count = optinfo->get_count (); |
399 | if (count.initialized_p ()) |
400 | obj->set (key: "count" , v: 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 (set: line_table, loc) != UNKNOWN_LOCATION) |
406 | { |
407 | // TOOD: record the location (just caret for now) |
408 | // TODO: start/finish also? |
409 | obj->set (key: "location" , v: 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 (key: "function" , v: new json::string (fnname)); |
417 | } |
418 | |
419 | if (loc != UNKNOWN_LOCATION) |
420 | obj->set (key: "inlining_chain" , v: 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 |
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 (v: pass_obj); |
435 | if (pass->sub) |
436 | { |
437 | json::array *sub = new json::array (); |
438 | pass_obj->set (key: "children" , v: sub); |
439 | add_pass_list (arr: sub, pass: 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 |
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 (optinfo: info); |
466 | ASSERT_TRUE (json_obj != NULL); |
467 | |
468 | /* Verify that the json is sane. */ |
469 | pretty_printer pp; |
470 | json_obj->print (pp: &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 |
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 | |