/* Dump infrastructure for optimizations and intermediate representation. Copyright (C) 2012-2013 Free Software Foundation, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "config.h" #include "system.h" #include "coretypes.h" #include "diagnostic-core.h" #include "dumpfile.h" #include "gimple-pretty-print.h" #include "tree.h" /* If non-NULL, return one past-the-end of the matching SUBPART of the WHOLE string. */ #define skip_leading_substring(whole, part) \ (strncmp (whole, part, strlen (part)) ? NULL : whole + strlen (part)) static int pflags; /* current dump_flags */ static int alt_flags; /* current opt_info flags */ static void dump_loc (int, FILE *, source_location); static int dump_phase_enabled_p (int); static FILE *dump_open_alternate_stream (struct dump_file_info *); /* These are currently used for communicating between passes. However, instead of accessing them directly, the passes can use dump_printf () for dumps. */ FILE *dump_file = NULL; FILE *alt_dump_file = NULL; const char *dump_file_name; int dump_flags; /* Table of tree dump switches. This must be consistent with the TREE_DUMP_INDEX enumeration in dumpfile.h. */ static struct dump_file_info dump_files[TDI_end] = { {NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0}, {".cgraph", "ipa-cgraph", NULL, NULL, NULL, NULL, NULL, TDF_IPA, 0, 0, 0, 0, 0}, {".tu", "translation-unit", NULL, NULL, NULL, NULL, NULL, TDF_TREE, 0, 0, 0, 0, 1}, {".class", "class-hierarchy", NULL, NULL, NULL, NULL, NULL, TDF_TREE, 0, 0, 0, 0, 2}, {".original", "tree-original", NULL, NULL, NULL, NULL, NULL, TDF_TREE, 0, 0, 0, 0, 3}, {".gimple", "tree-gimple", NULL, NULL, NULL, NULL, NULL, TDF_TREE, 0, 0, 0, 0, 4}, {".nested", "tree-nested", NULL, NULL, NULL, NULL, NULL, TDF_TREE, 0, 0, 0, 0, 5}, #define FIRST_AUTO_NUMBERED_DUMP 6 {NULL, "tree-all", NULL, NULL, NULL, NULL, NULL, TDF_TREE, 0, 0, 0, 0, 0}, {NULL, "rtl-all", NULL, NULL, NULL, NULL, NULL, TDF_RTL, 0, 0, 0, 0, 0}, {NULL, "ipa-all", NULL, NULL, NULL, NULL, NULL, TDF_IPA, 0, 0, 0, 0, 0}, }; /* Dynamically registered tree dump files and switches. */ static struct dump_file_info *extra_dump_files; static size_t extra_dump_files_in_use; static size_t extra_dump_files_alloced; /* Define a name->number mapping for a dump flag value. */ struct dump_option_value_info { const char *const name; /* the name of the value */ const int value; /* the value of the name */ }; /* Table of dump options. This must be consistent with the TDF_* flags in dumpfile.h and opt_info_options below. */ static const struct dump_option_value_info dump_options[] = { {"address", TDF_ADDRESS}, {"asmname", TDF_ASMNAME}, {"slim", TDF_SLIM}, {"raw", TDF_RAW}, {"graph", TDF_GRAPH}, {"details", (TDF_DETAILS | MSG_OPTIMIZED_LOCATIONS | MSG_MISSED_OPTIMIZATION | MSG_NOTE)}, {"cselib", TDF_CSELIB}, {"stats", TDF_STATS}, {"blocks", TDF_BLOCKS}, {"vops", TDF_VOPS}, {"lineno", TDF_LINENO}, {"uid", TDF_UID}, {"stmtaddr", TDF_STMTADDR}, {"memsyms", TDF_MEMSYMS}, {"verbose", TDF_VERBOSE}, {"eh", TDF_EH}, {"alias", TDF_ALIAS}, {"nouid", TDF_NOUID}, {"enumerate_locals", TDF_ENUMERATE_LOCALS}, {"scev", TDF_SCEV}, {"all", ~(TDF_RAW | TDF_SLIM | TDF_LINENO | TDF_TREE | TDF_RTL | TDF_IPA | TDF_STMTADDR | TDF_GRAPH | TDF_DIAGNOSTIC | TDF_VERBOSE | TDF_RHS_ONLY | TDF_NOUID | TDF_ENUMERATE_LOCALS | TDF_SCEV)}, {NULL, 0} }; /* A subset of the dump_options table which is used for -fopt-info types. This must be consistent with the MSG_* flags in dumpfile.h. */ static const struct dump_option_value_info optinfo_verbosity_options[] = { {"optimized", MSG_OPTIMIZED_LOCATIONS}, {"missed", MSG_MISSED_OPTIMIZATION}, {"note", MSG_NOTE}, {"all", MSG_ALL}, {NULL, 0} }; /* Flags used for -fopt-info groups. */ static const struct dump_option_value_info optgroup_options[] = { {"ipa", OPTGROUP_IPA}, {"loop", OPTGROUP_LOOP}, {"inline", OPTGROUP_INLINE}, {"vec", OPTGROUP_VEC}, {"optall", OPTGROUP_ALL}, {NULL, 0} }; unsigned int dump_register (const char *suffix, const char *swtch, const char *glob, int flags, int optgroup_flags) { static int next_dump = FIRST_AUTO_NUMBERED_DUMP; int num = next_dump++; size_t count = extra_dump_files_in_use++; if (count >= extra_dump_files_alloced) { if (extra_dump_files_alloced == 0) extra_dump_files_alloced = 32; else extra_dump_files_alloced *= 2; extra_dump_files = XRESIZEVEC (struct dump_file_info, extra_dump_files, extra_dump_files_alloced); } memset (&extra_dump_files[count], 0, sizeof (struct dump_file_info)); extra_dump_files[count].suffix = suffix; extra_dump_files[count].swtch = swtch; extra_dump_files[count].glob = glob; extra_dump_files[count].pflags = flags; extra_dump_files[count].optgroup_flags = optgroup_flags; extra_dump_files[count].num = num; return count + TDI_end; } /* Return the dump_file_info for the given phase. */ struct dump_file_info * get_dump_file_info (int phase) { if (phase < TDI_end) return &dump_files[phase]; else if ((size_t) (phase - TDI_end) >= extra_dump_files_in_use) return NULL; else return extra_dump_files + (phase - TDI_end); } /* Return the name of the dump file for the given phase. If the dump is not enabled, returns NULL. */ char * get_dump_file_name (int phase) { char dump_id[10]; struct dump_file_info *dfi; if (phase == TDI_none) return NULL; dfi = get_dump_file_info (phase); if (dfi->pstate == 0) return NULL; /* If available, use the command line dump filename. */ if (dfi->pfilename) return xstrdup (dfi->pfilename); if (dfi->num < 0) dump_id[0] = '\0'; else { char suffix; if (dfi->pflags & TDF_TREE) suffix = 't'; else if (dfi->pflags & TDF_IPA) suffix = 'i'; else suffix = 'r'; if (snprintf (dump_id, sizeof (dump_id), ".%03d%c", dfi->num, suffix) < 0) dump_id[0] = '\0'; } return concat (dump_base_name, dump_id, dfi->suffix, NULL); } /* For a given DFI, open an alternate dump filename (which could also be a standard stream such as stdout/stderr). If the alternate dump file cannot be opened, return NULL. */ static FILE * dump_open_alternate_stream (struct dump_file_info *dfi) { FILE *stream ; if (!dfi->alt_filename) return NULL; if (dfi->alt_stream) return dfi->alt_stream; stream = strcmp("stderr", dfi->alt_filename) == 0 ? stderr : strcmp("stdout", dfi->alt_filename) == 0 ? stdout : fopen (dfi->alt_filename, dfi->alt_state < 0 ? "w" : "a"); if (!stream) error ("could not open dump file %qs: %m", dfi->alt_filename); else dfi->alt_state = 1; return stream; } /* Print source location on DFILE if enabled. */ void dump_loc (int dump_kind, FILE *dfile, source_location loc) { /* Currently vectorization passes print location information. */ if (dump_kind) { if (LOCATION_LOCUS (loc) > BUILTINS_LOCATION) fprintf (dfile, "\n%s:%d: note: ", LOCATION_FILE (loc), LOCATION_LINE (loc)); else if (current_function_decl) fprintf (dfile, "\n%s:%d: note: ", DECL_SOURCE_FILE (current_function_decl), DECL_SOURCE_LINE (current_function_decl)); } } /* Dump gimple statement GS with SPC indentation spaces and EXTRA_DUMP_FLAGS on the dump streams if DUMP_KIND is enabled. */ void dump_gimple_stmt (int dump_kind, int extra_dump_flags, gimple gs, int spc) { if (dump_file && (dump_kind & pflags)) print_gimple_stmt (dump_file, gs, spc, dump_flags | extra_dump_flags); if (alt_dump_file && (dump_kind & alt_flags)) print_gimple_stmt (alt_dump_file, gs, spc, dump_flags | extra_dump_flags); } /* Similar to dump_gimple_stmt, except additionally print source location. */ void dump_gimple_stmt_loc (int dump_kind, source_location loc, int extra_dump_flags, gimple gs, int spc) { if (dump_file && (dump_kind & pflags)) { dump_loc (dump_kind, dump_file, loc); print_gimple_stmt (dump_file, gs, spc, dump_flags | extra_dump_flags); } if (alt_dump_file && (dump_kind & alt_flags)) { dump_loc (dump_kind, alt_dump_file, loc); print_gimple_stmt (alt_dump_file, gs, spc, dump_flags | extra_dump_flags); } } /* Dump expression tree T using EXTRA_DUMP_FLAGS on dump streams if DUMP_KIND is enabled. */ void dump_generic_expr (int dump_kind, int extra_dump_flags, tree t) { if (dump_file && (dump_kind & pflags)) print_generic_expr (dump_file, t, dump_flags | extra_dump_flags); if (alt_dump_file && (dump_kind & alt_flags)) print_generic_expr (alt_dump_file, t, dump_flags | extra_dump_flags); } /* Similar to dump_generic_expr, except additionally print the source location. */ void dump_generic_expr_loc (int dump_kind, source_location loc, int extra_dump_flags, tree t) { if (dump_file && (dump_kind & pflags)) { dump_loc (dump_kind, dump_file, loc); print_generic_expr (dump_file, t, dump_flags | extra_dump_flags); } if (alt_dump_file && (dump_kind & alt_flags)) { dump_loc (dump_kind, alt_dump_file, loc); print_generic_expr (alt_dump_file, t, dump_flags | extra_dump_flags); } } /* Output a formatted message using FORMAT on appropriate dump streams. */ void dump_printf (int dump_kind, const char *format, ...) { if (dump_file && (dump_kind & pflags)) { va_list ap; va_start (ap, format); vfprintf (dump_file, format, ap); va_end (ap); } if (alt_dump_file && (dump_kind & alt_flags)) { va_list ap; va_start (ap, format); vfprintf (alt_dump_file, format, ap); va_end (ap); } } /* Similar to dump_printf, except source location is also printed. */ void dump_printf_loc (int dump_kind, source_location loc, const char *format, ...) { if (dump_file && (dump_kind & pflags)) { va_list ap; dump_loc (dump_kind, dump_file, loc); va_start (ap, format); vfprintf (dump_file, format, ap); va_end (ap); } if (alt_dump_file && (dump_kind & alt_flags)) { va_list ap; dump_loc (dump_kind, alt_dump_file, loc); va_start (ap, format); vfprintf (alt_dump_file, format, ap); va_end (ap); } } /* Start a dump for PHASE. Store user-supplied dump flags in *FLAG_PTR. Return the number of streams opened. Set globals DUMP_FILE, and ALT_DUMP_FILE to point to the opened streams, and set dump_flags appropriately for both pass dump stream and -fopt-info stream. */ int dump_start (int phase, int *flag_ptr) { int count = 0; char *name; struct dump_file_info *dfi; FILE *stream; if (phase == TDI_none || !dump_phase_enabled_p (phase)) return 0; dfi = get_dump_file_info (phase); name = get_dump_file_name (phase); if (name) { stream = strcmp("stderr", name) == 0 ? stderr : strcmp("stdout", name) == 0 ? stdout : fopen (name, dfi->pstate < 0 ? "w" : "a"); if (!stream) error ("could not open dump file %qs: %m", name); else { dfi->pstate = 1; count++; } free (name); dfi->pstream = stream; dump_file = dfi->pstream; /* Initialize current dump flags. */ pflags = dfi->pflags; } stream = dump_open_alternate_stream (dfi); if (stream) { dfi->alt_stream = stream; count++; alt_dump_file = dfi->alt_stream; /* Initialize current -fopt-info flags. */ alt_flags = dfi->alt_flags; } if (flag_ptr) *flag_ptr = dfi->pflags; return count; } /* Finish a tree dump for PHASE and close associated dump streams. Also reset the globals DUMP_FILE, ALT_DUMP_FILE, and DUMP_FLAGS. */ void dump_finish (int phase) { struct dump_file_info *dfi; if (phase < 0) return; dfi = get_dump_file_info (phase); if (dfi->pstream) fclose (dfi->pstream); if (dfi->alt_stream && strcmp("stderr", dfi->alt_filename) != 0 && strcmp("stdout", dfi->alt_filename) != 0) fclose (dfi->alt_stream); dfi->alt_stream = NULL; dfi->pstream = NULL; dump_file = NULL; alt_dump_file = NULL; dump_flags = TDI_none; alt_flags = 0; pflags = 0; } /* Begin a tree dump for PHASE. Stores any user supplied flag in *FLAG_PTR and returns a stream to write to. If the dump is not enabled, returns NULL. Multiple calls will reopen and append to the dump file. */ FILE * dump_begin (int phase, int *flag_ptr) { char *name; struct dump_file_info *dfi; FILE *stream; if (phase == TDI_none || !dump_phase_enabled_p (phase)) return NULL; name = get_dump_file_name (phase); if (!name) return NULL; dfi = get_dump_file_info (phase); stream = strcmp("stderr", name) == 0 ? stderr : strcmp("stdout", name) == 0 ? stdout : fopen (name, dfi->pstate < 0 ? "w" : "a"); if (!stream) error ("could not open dump file %qs: %m", name); else dfi->pstate = 1; free (name); if (flag_ptr) *flag_ptr = dfi->pflags; /* Initialize current flags */ pflags = dfi->pflags; return stream; } /* Returns nonzero if dump PHASE is enabled for at least one stream. If PHASE is TDI_tree_all, return nonzero if any dump is enabled for any phase. */ static int dump_phase_enabled_p (int phase) { if (phase == TDI_tree_all) { size_t i; for (i = TDI_none + 1; i < (size_t) TDI_end; i++) if (dump_files[i].pstate || dump_files[i].alt_state) return 1; for (i = 0; i < extra_dump_files_in_use; i++) if (extra_dump_files[i].pstate || extra_dump_files[i].alt_state) return 1; return 0; } else { struct dump_file_info *dfi = get_dump_file_info (phase); return dfi->pstate || dfi->alt_state; } } /* Returns nonzero if tree dump PHASE has been initialized. */ int dump_initialized_p (int phase) { struct dump_file_info *dfi = get_dump_file_info (phase); return dfi->pstate > 0 || dfi->alt_state > 0; } /* Returns the switch name of PHASE. */ const char * dump_flag_name (int phase) { struct dump_file_info *dfi = get_dump_file_info (phase); return dfi->swtch; } /* Finish a tree dump for PHASE. STREAM is the stream created by dump_begin. */ void dump_end (int phase ATTRIBUTE_UNUSED, FILE *stream) { if (stream != stderr && stream != stdout) fclose (stream); } /* Enable all tree dumps with FLAGS on FILENAME. Return number of enabled tree dumps. */ static int dump_enable_all (int flags, const char *filename) { int ir_dump_type = (flags & (TDF_TREE | TDF_RTL | TDF_IPA)); int n = 0; size_t i; for (i = TDI_none + 1; i < (size_t) TDI_end; i++) { if ((dump_files[i].pflags & ir_dump_type)) { const char *old_filename = dump_files[i].pfilename; dump_files[i].pstate = -1; dump_files[i].pflags |= flags; n++; /* Override the existing filename. */ if (filename) { dump_files[i].pfilename = xstrdup (filename); /* Since it is a command-line provided file, which is common to all the phases, use it in append mode. */ dump_files[i].pstate = 1; } if (old_filename && filename != old_filename) free (CONST_CAST (char *, old_filename)); } } for (i = 0; i < extra_dump_files_in_use; i++) { if ((extra_dump_files[i].pflags & ir_dump_type)) { const char *old_filename = extra_dump_files[i].pfilename; extra_dump_files[i].pstate = -1; extra_dump_files[i].pflags |= flags; n++; /* Override the existing filename. */ if (filename) { extra_dump_files[i].pfilename = xstrdup (filename); /* Since it is a command-line provided file, which is common to all the phases, use it in append mode. */ extra_dump_files[i].pstate = 1; } if (old_filename && filename != old_filename) free (CONST_CAST (char *, old_filename)); } } return n; } /* Enable -fopt-info dumps on all dump files matching OPTGROUP_FLAGS. Enable dumps with FLAGS on FILENAME. Return the number of enabled dumps. */ static int opt_info_enable_passes (int optgroup_flags, int flags, const char *filename) { int n = 0; size_t i; for (i = TDI_none + 1; i < (size_t) TDI_end; i++) { if ((dump_files[i].optgroup_flags & optgroup_flags)) { const char *old_filename = dump_files[i].alt_filename; /* Since this file is shared among different passes, it should be opened in append mode. */ dump_files[i].alt_state = 1; dump_files[i].alt_flags |= flags; n++; /* Override the existing filename. */ if (filename) dump_files[i].alt_filename = xstrdup (filename); if (old_filename && filename != old_filename) free (CONST_CAST (char *, old_filename)); } } for (i = 0; i < extra_dump_files_in_use; i++) { if ((extra_dump_files[i].optgroup_flags & optgroup_flags)) { const char *old_filename = extra_dump_files[i].alt_filename; /* Since this file is shared among different passes, it should be opened in append mode. */ extra_dump_files[i].alt_state = 1; extra_dump_files[i].alt_flags |= flags; n++; /* Override the existing filename. */ if (filename) extra_dump_files[i].alt_filename = xstrdup (filename); if (old_filename && filename != old_filename) free (CONST_CAST (char *, old_filename)); } } return n; } /* Parse ARG as a dump switch. Return nonzero if it is, and store the relevant details in the dump_files array. */ static int dump_switch_p_1 (const char *arg, struct dump_file_info *dfi, bool doglob) { const char *option_value; const char *ptr; int flags; if (doglob && !dfi->glob) return 0; option_value = skip_leading_substring (arg, doglob ? dfi->glob : dfi->swtch); if (!option_value) return 0; if (*option_value && *option_value != '-' && *option_value != '=') return 0; ptr = option_value; flags = 0; while (*ptr) { const struct dump_option_value_info *option_ptr; const char *end_ptr; const char *eq_ptr; unsigned length; while (*ptr == '-') ptr++; end_ptr = strchr (ptr, '-'); eq_ptr = strchr (ptr, '='); if (eq_ptr && !end_ptr) end_ptr = eq_ptr; if (!end_ptr) end_ptr = ptr + strlen (ptr); length = end_ptr - ptr; for (option_ptr = dump_options; option_ptr->name; option_ptr++) if (strlen (option_ptr->name) == length && !memcmp (option_ptr->name, ptr, length)) { flags |= option_ptr->value; goto found; } if (*ptr == '=') { /* Interpret rest of the argument as a dump filename. This filename overrides other command line filenames. */ if (dfi->pfilename) free (CONST_CAST (char *, dfi->pfilename)); dfi->pfilename = xstrdup (ptr + 1); break; } else warning (0, "ignoring unknown option %q.*s in %<-fdump-%s%>", length, ptr, dfi->swtch); found:; ptr = end_ptr; } dfi->pstate = -1; dfi->pflags |= flags; /* Process -fdump-tree-all and -fdump-rtl-all, by enabling all the known dumps. */ if (dfi->suffix == NULL) dump_enable_all (dfi->pflags, dfi->pfilename); return 1; } int dump_switch_p (const char *arg) { size_t i; int any = 0; for (i = TDI_none + 1; i != TDI_end; i++) any |= dump_switch_p_1 (arg, &dump_files[i], false); /* Don't glob if we got a hit already */ if (!any) for (i = TDI_none + 1; i != TDI_end; i++) any |= dump_switch_p_1 (arg, &dump_files[i], true); for (i = 0; i < extra_dump_files_in_use; i++) any |= dump_switch_p_1 (arg, &extra_dump_files[i], false); if (!any) for (i = 0; i < extra_dump_files_in_use; i++) any |= dump_switch_p_1 (arg, &extra_dump_files[i], true); return any; } /* Parse ARG as a -fopt-info switch and store flags, optgroup_flags and filename. Return non-zero if it is a recognized switch. */ static int opt_info_switch_p_1 (const char *arg, int *flags, int *optgroup_flags, char **filename) { const char *option_value; const char *ptr; option_value = arg; ptr = option_value; *filename = NULL; *flags = 0; *optgroup_flags = 0; if (!ptr) return 1; /* Handle '-fopt-info' without any additional options. */ while (*ptr) { const struct dump_option_value_info *option_ptr; const char *end_ptr; const char *eq_ptr; unsigned length; while (*ptr == '-') ptr++; end_ptr = strchr (ptr, '-'); eq_ptr = strchr (ptr, '='); if (eq_ptr && !end_ptr) end_ptr = eq_ptr; if (!end_ptr) end_ptr = ptr + strlen (ptr); length = end_ptr - ptr; for (option_ptr = optinfo_verbosity_options; option_ptr->name; option_ptr++) if (strlen (option_ptr->name) == length && !memcmp (option_ptr->name, ptr, length)) { *flags |= option_ptr->value; goto found; } for (option_ptr = optgroup_options; option_ptr->name; option_ptr++) if (strlen (option_ptr->name) == length && !memcmp (option_ptr->name, ptr, length)) { *optgroup_flags |= option_ptr->value; goto found; } if (*ptr == '=') { /* Interpret rest of the argument as a dump filename. This filename overrides other command line filenames. */ *filename = xstrdup (ptr + 1); break; } else { warning (0, "unknown option %q.*s in %<-fopt-info-%s%>", length, ptr, arg); return 0; } found:; ptr = end_ptr; } return 1; } /* Return non-zero if ARG is a recognized switch for -fopt-info. Return zero otherwise. */ int opt_info_switch_p (const char *arg) { int flags; int optgroup_flags; char *filename; static char *file_seen = NULL; if (!opt_info_switch_p_1 (arg, &flags, &optgroup_flags, &filename)) return 0; if (!filename) filename = xstrdup ("stderr"); /* Bail out if a different filename has been specified. */ if (file_seen && strcmp (file_seen, filename)) { warning (0, "ignoring possibly conflicting option %<-fopt-info-%s%>", arg); return 1; } file_seen = xstrdup (filename); if (!flags) flags = MSG_OPTIMIZED_LOCATIONS; if (!optgroup_flags) optgroup_flags = OPTGROUP_ALL; return opt_info_enable_passes (optgroup_flags, flags, filename); } /* Print basic block on the dump streams. */ void dump_basic_block (int dump_kind, basic_block bb, int indent) { if (dump_file && (dump_kind & pflags)) dump_bb (dump_file, bb, indent, TDF_DETAILS); if (alt_dump_file && (dump_kind & alt_flags)) dump_bb (alt_dump_file, bb, indent, TDF_DETAILS); } /* Print information from the combine pass on dump_file. */ void print_combine_total_stats (void) { if (dump_file) dump_combine_total_stats (dump_file); } /* Enable RTL dump for all the RTL passes. */ bool enable_rtl_dump_file (void) { return dump_enable_all (TDF_RTL | TDF_DETAILS | TDF_BLOCKS, NULL) > 0; }