diff options
author | Niklas Hallqvist <niklas@cvs.openbsd.org> | 1998-02-14 19:12:00 +0000 |
---|---|---|
committer | Niklas Hallqvist <niklas@cvs.openbsd.org> | 1998-02-14 19:12:00 +0000 |
commit | df96e1901c8f6b0f125702972a33f062d4c00179 (patch) | |
tree | dfe9429b4514f8fb7a993da328e10843f3cc0567 /gnu/usr.bin/gcc/gcov.c | |
parent | aff5322427b69c6098ef28e5b51155683046fcc4 (diff) |
Diffstat (limited to 'gnu/usr.bin/gcc/gcov.c')
-rw-r--r-- | gnu/usr.bin/gcc/gcov.c | 1391 |
1 files changed, 1391 insertions, 0 deletions
diff --git a/gnu/usr.bin/gcc/gcov.c b/gnu/usr.bin/gcc/gcov.c new file mode 100644 index 00000000000..2dda0d48ee8 --- /dev/null +++ b/gnu/usr.bin/gcc/gcov.c @@ -0,0 +1,1391 @@ +/* Gcov.c: prepend line execution counts and branch probabilities to a + source file. + Copyright (C) 1990, 91, 92, 93, 94, 96, 1997 Free Software Foundation, Inc. + Contributed by James E. Wilson of Cygnus Support. + Mangled by Bob Manson of Cygnus Support. + +Gcov 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 2, or (at your option) +any later version. + +Gcov 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 Gcov; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* ??? The code in final.c that produces the struct bb assumes that there is + no padding between the fields. This is not necessary true. The current + code can only be trusted if longs and pointers are the same size. */ + +/* ??? No need to print an execution count on every line, could just print + it on the first line of each block, and only print it on a subsequent + line in the same block if the count changes. */ + +/* ??? Print a list of the ten blocks with the highest execution counts, + and list the line numbers corresponding to those blocks. Also, perhaps + list the line numbers with the highest execution counts, only printing + the first if there are several which are all listed in the same block. */ + +/* ??? Should have an option to print the number of basic blocks, and the + percent of them that are covered. */ + +/* ??? Does not correctly handle the case where two .bb files refer to the + same included source file. For example, if one has a short file containing + only inline functions, which is then included in two other files, then + there will be two .bb files which refer to the include file, but there + is no way to get the total execution counts for the included file, can + only get execution counts for one or the other of the including files. */ + +#include "config.h" +#include <stdio.h> +#include "gansidecl.h" +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#else +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#endif + +#include "gcov-io.h" + +#ifdef NEED_DECLARATION_RINDEX +extern char *rindex (); +#endif + +/* The .bb file format consists of several lists of 4-byte integers + which are the line numbers of each basic block in the file. Each + list is terminated by a zero. These lists correspond to the basic + blocks in the reconstructed program flow graph. + + A line number of -1 indicates that a source file name (padded to a + long boundary) follows. The padded file name is followed by + another -1 to make it easy to scan past file names. A -2 indicates + that a function name (padded to a long boundary) follows; the name + is followed by another -2 to make it easy to scan past the function + name. + + The .bbg file contains enough info to enable gcov to reconstruct the + program flow graph. The first word is the number of basic blocks, + the second word is the number of arcs, followed by the list of arcs + (source bb, dest bb pairs), then a -1, then the number of instrumented + arcs followed by the instrumented arcs, followed by another -1. This + is repeated for each function. + + The .da file contains the execution count for each instrumented branch. + + The .bb and .bbg files are created by giving GCC the -ftest-coverage option, + and the .da files are created when an executable compiled with + -fprofile-arcs is run. */ + +/* The functions in this file for creating and solution program flow graphs + are very similar to functions in the gcc source file profile.c. */ + +char gcov_version_string[] = "GNU gcov version 1.5\n"; + +/* This is the size of the buffer used to read in source file lines. */ + +#define STRING_SIZE 200 + +/* One copy of this structure is created for each source file mentioned in the + .bb file. */ + +struct sourcefile +{ + char *name; + int maxlineno; + struct sourcefile *next; +}; + +/* This points to the head of the sourcefile structure list. */ + +struct sourcefile *sources; + +/* One of these is dynamically created whenever we identify an arc in the + function. */ + +struct adj_list { + int source; + int target; + int arc_count; + unsigned int count_valid : 1; + unsigned int on_tree : 1; + unsigned int fake : 1; + unsigned int fall_through : 1; +#if 0 + /* Not needed for gcov, but defined in profile.c. */ + rtx branch_insn; +#endif + struct adj_list *pred_next; + struct adj_list *succ_next; +}; + +/* Count the number of basic blocks, and create an array of these structures, + one for each bb in the function. */ + +struct bb_info { + struct adj_list *succ; + struct adj_list *pred; + int succ_count; + int pred_count; + int exec_count; + unsigned int count_valid : 1; + unsigned int on_tree : 1; +#if 0 + /* Not needed for gcov, but defined in profile.c. */ + rtx first_insn; +#endif +}; + +/* When outputting branch probabilities, one of these structures is created + for each branch/call. */ + +struct arcdata +{ + int prob; + int call_insn; + struct arcdata *next; +}; + +/* Used to save the list of bb_graphs, one per function. */ + +struct bb_info_list { + /* Indexed by block number, holds the basic block graph for one function. */ + struct bb_info *bb_graph; + int num_blocks; + struct bb_info_list *next; +}; + +/* Holds a list of function basic block graphs. */ + +static struct bb_info_list *bb_graph_list = 0; + +/* Name and file pointer of the input file for the basic block graph. */ + +static char *bbg_file_name; +static FILE *bbg_file; + +/* Name and file pointer of the input file for the arc count data. */ + +static char *da_file_name; +static FILE *da_file; + +/* Name and file pointer of the input file for the basic block line counts. */ + +static char *bb_file_name; +static FILE *bb_file; + +/* Holds the entire contents of the bb_file read into memory. */ + +static char *bb_data; + +/* Size of bb_data array in longs. */ + +static long bb_data_size; + +/* Name and file pointer of the output file. */ + +static char *gcov_file_name; +static FILE *gcov_file; + +/* Name of the file mentioned on the command line. */ + +static char *input_file_name = 0; + +/* Output branch probabilities if true. */ + +static int output_branch_probs = 0; + +/* Output a gcov file if this is true. This is on by default, and can + be turned off by the -n option. */ + +static int output_gcov_file = 1; + +/* For included files, make the gcov output file name include the name of + the input source file. For example, if x.h is included in a.c, then the + output file name is a.c.x.h.gcov instead of x.h.gcov. This works only + when a single source file is specified. */ + +static int output_long_names = 0; + +/* Output summary info for each function. */ + +static int output_function_summary = 0; + +/* Object directory file prefix. This is the directory where .bb and .bbg + files are looked for, if non-zero. */ + +static char *object_directory = 0; + +/* Forward declarations. */ +static void process_args (); +static void open_files (); +static void read_files (); +static void scan_for_source_files (); +static void output_data (); +char * xmalloc (); + +int +main (argc, argv) + int argc; + char **argv; +{ + process_args (argc, argv); + + open_files (); + + read_files (); + + scan_for_source_files (); + + output_data (); + + return 0; +} + +char * +xmalloc (size) + unsigned size; +{ + register char *value = (char *) malloc (size); + if (value == 0) + { + fprintf (stderr, "error: virtual memory exhausted"); + exit (FATAL_EXIT_CODE); + } + return value; +} + +/* More 'friendly' abort that prints the line and file. + config.h can #define abort fancy_abort if you like that sort of thing. */ + +void +fancy_abort () +{ + fprintf (stderr, "Internal gcc abort.\n"); + exit (FATAL_EXIT_CODE); +} + +/* Print a usage message and exit. */ + +static void +print_usage () +{ + fprintf (stderr, "gcov [-b] [-v] [-n] [-l] [-f] [-o OBJDIR] file\n"); + exit (FATAL_EXIT_CODE); +} + +/* Parse the command line. */ + +static void +process_args (argc, argv) + int argc; + char **argv; +{ + int i; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + if (argv[i][1] == 'b') + output_branch_probs = 1; + else if (argv[i][1] == 'v') + fputs (gcov_version_string, stderr); + else if (argv[i][1] == 'n') + output_gcov_file = 0; + else if (argv[i][1] == 'l') + output_long_names = 1; + else if (argv[i][1] == 'f') + output_function_summary = 1; + else if (argv[i][1] == 'o' && argv[i][2] == '\0') + object_directory = argv[++i]; + else + print_usage (); + } + else if (! input_file_name) + input_file_name = argv[i]; + else + print_usage (); + } + + if (! input_file_name) + print_usage (); +} + + +/* Find and open the .bb, .da, and .bbg files. */ + +static void +open_files () +{ + int count, objdir_count; + char *cptr; + + /* Determine the names of the .bb, .bbg, and .da files. Strip off the + extension, if any, and append the new extensions. */ + count = strlen (input_file_name); + if (object_directory) + objdir_count = strlen (object_directory); + else + objdir_count = 0; + + da_file_name = xmalloc (count + objdir_count + 4); + bb_file_name = xmalloc (count + objdir_count + 4); + bbg_file_name = xmalloc (count + objdir_count + 5); + + if (object_directory) + { + strcpy (da_file_name, object_directory); + strcpy (bb_file_name, object_directory); + strcpy (bbg_file_name, object_directory); + + if (object_directory[objdir_count - 1] != '/') + { + strcat (da_file_name, "/"); + strcat (bb_file_name, "/"); + strcat (bbg_file_name, "/"); + } + + cptr = rindex (input_file_name, '/'); + if (cptr) + { + strcat (da_file_name, cptr + 1); + strcat (bb_file_name, cptr + 1); + strcat (bbg_file_name, cptr + 1); + } + else + { + strcat (da_file_name, input_file_name); + strcat (bb_file_name, input_file_name); + strcat (bbg_file_name, input_file_name); + } + } + else + { + strcpy (da_file_name, input_file_name); + strcpy (bb_file_name, input_file_name); + strcpy (bbg_file_name, input_file_name); + } + + cptr = rindex (bb_file_name, '.'); + if (cptr) + strcpy (cptr, ".bb"); + else + strcat (bb_file_name, ".bb"); + + cptr = rindex (da_file_name, '.'); + if (cptr) + strcpy (cptr, ".da"); + else + strcat (da_file_name, ".da"); + + cptr = rindex (bbg_file_name, '.'); + if (cptr) + strcpy (cptr, ".bbg"); + else + strcat (bbg_file_name, ".bbg"); + + bb_file = fopen (bb_file_name, "r"); + if (bb_file == NULL) + { + fprintf (stderr, "Could not open basic block file %s.\n", bb_file_name); + exit (FATAL_EXIT_CODE); + } + + /* If none of the functions in the file were executed, then there won't + be a .da file. Just assume that all counts are zero in this case. */ + da_file = fopen (da_file_name, "r"); + if (da_file == NULL) + { + fprintf (stderr, "Could not open data file %s.\n", da_file_name); + fprintf (stderr, "Assuming that all execution counts are zero.\n"); + } + + bbg_file = fopen (bbg_file_name, "r"); + if (bbg_file == NULL) + { + fprintf (stderr, "Could not open program flow graph file %s.\n", + bbg_file_name); + exit (FATAL_EXIT_CODE); + } + + /* Check for empty .bbg file. This indicates that there is no executable + code in this source file. */ + /* Set the EOF condition if at the end of file. */ + ungetc (getc (bbg_file), bbg_file); + if (feof (bbg_file)) + { + fprintf (stderr, "No executable code associated with file %s.\n", + input_file_name); + exit (FATAL_EXIT_CODE); + } +} + +/* Initialize a new arc. */ + +static void +init_arc (arcptr, source, target, bb_graph) + struct adj_list *arcptr; + int source, target; + struct bb_info *bb_graph; +{ + arcptr->target = target; + arcptr->source = source; + + arcptr->arc_count = 0; + arcptr->count_valid = 0; + arcptr->on_tree = 0; + arcptr->fake = 0; + arcptr->fall_through = 0; + + arcptr->succ_next = bb_graph[source].succ; + bb_graph[source].succ = arcptr; + bb_graph[source].succ_count++; + + arcptr->pred_next = bb_graph[target].pred; + bb_graph[target].pred = arcptr; + bb_graph[target].pred_count++; +} + + +/* Reverse the arcs on a arc list. */ + +static struct adj_list * +reverse_arcs (arcptr) + struct adj_list *arcptr; +{ + struct adj_list *prev = 0; + struct adj_list *next; + + for ( ; arcptr; arcptr = next) + { + next = arcptr->succ_next; + arcptr->succ_next = prev; + prev = arcptr; + } + + return prev; +} + + +/* Construct the program flow graph from the .bbg file, and read in the data + in the .da file. */ + +static void +create_program_flow_graph (bptr) + struct bb_info_list *bptr; +{ + long num_blocks, number_arcs, src, dest, flag_bits, num_arcs_per_block; + int i; + struct adj_list *arcptr; + struct bb_info *bb_graph; + + /* Read the number of blocks. */ + __read_long (&num_blocks, bbg_file, 4); + + /* Create an array of size bb number of bb_info structs. Bzero it. */ + bb_graph = (struct bb_info *) xmalloc (num_blocks + * sizeof (struct bb_info)); + bzero ((char *) bb_graph, sizeof (struct bb_info) * num_blocks); + + bptr->bb_graph = bb_graph; + bptr->num_blocks = num_blocks; + + /* Read and create each arc from the .bbg file. */ + __read_long (&number_arcs, bbg_file, 4); + for (i = 0; i < num_blocks; i++) + { + int j; + + __read_long (&num_arcs_per_block, bbg_file, 4); + for (j = 0; j < num_arcs_per_block; j++) + { + if (number_arcs-- < 0) + abort (); + + src = i; + __read_long (&dest, bbg_file, 4); + + arcptr = (struct adj_list *) xmalloc (sizeof (struct adj_list)); + init_arc (arcptr, src, dest, bb_graph); + + __read_long (&flag_bits, bbg_file, 4); + arcptr->on_tree = flag_bits & 0x1; + arcptr->fake = !! (flag_bits & 0x2); + arcptr->fall_through = !! (flag_bits & 0x4); + } + } + + if (number_arcs) + abort (); + + /* Read and ignore the -1 separating the arc list from the arc list of the + next function. */ + __read_long (&src, bbg_file, 4); + if (src != -1) + abort (); + + /* Must reverse the order of all succ arcs, to ensure that they match + the order of the data in the .da file. */ + + for (i = 0; i < num_blocks; i++) + if (bb_graph[i].succ) + bb_graph[i].succ = reverse_arcs (bb_graph[i].succ); + + /* For each arc not on the spanning tree, set its execution count from + the .da file. */ + + /* The first count in the .da file is the number of times that the function + was entered. This is the exec_count for block zero. */ + + /* This duplicates code in branch_prob in profile.c. */ + + for (i = 0; i < num_blocks; i++) + for (arcptr = bb_graph[i].succ; arcptr; arcptr = arcptr->succ_next) + if (! arcptr->on_tree) + { + long tmp_count = 0;; + if (da_file && __read_long (&tmp_count, da_file, 8)) + abort(); + + arcptr->arc_count = tmp_count; + arcptr->count_valid = 1; + bb_graph[i].succ_count--; + bb_graph[arcptr->target].pred_count--; + } +} + +static void +solve_program_flow_graph (bptr) + struct bb_info_list *bptr; +{ + int passes, changes, total; + int i; + struct adj_list *arcptr; + struct bb_info *bb_graph; + int num_blocks; + + num_blocks = bptr->num_blocks; + bb_graph = bptr->bb_graph; + + /* For every block in the file, + - if every exit/entrance arc has a known count, then set the block count + - if the block count is known, and every exit/entrance arc but one has + a known execution count, then set the count of the remaining arc + + As arc counts are set, decrement the succ/pred count, but don't delete + the arc, that way we can easily tell when all arcs are known, or only + one arc is unknown. */ + + /* The order that the basic blocks are iterated through is important. + Since the code that finds spanning trees starts with block 0, low numbered + arcs are put on the spanning tree in preference to high numbered arcs. + Hence, most instrumented arcs are at the end. Graph solving works much + faster if we propagate numbers from the end to the start. + + This takes an average of slightly more than 3 passes. */ + + changes = 1; + passes = 0; + while (changes) + { + passes++; + changes = 0; + + for (i = num_blocks - 1; i >= 0; i--) + { + if (! bb_graph[i].count_valid) + { + if (bb_graph[i].succ_count == 0) + { + total = 0; + for (arcptr = bb_graph[i].succ; arcptr; + arcptr = arcptr->succ_next) + total += arcptr->arc_count; + bb_graph[i].exec_count = total; + bb_graph[i].count_valid = 1; + changes = 1; + } + else if (bb_graph[i].pred_count == 0) + { + total = 0; + for (arcptr = bb_graph[i].pred; arcptr; + arcptr = arcptr->pred_next) + total += arcptr->arc_count; + bb_graph[i].exec_count = total; + bb_graph[i].count_valid = 1; + changes = 1; + } + } + if (bb_graph[i].count_valid) + { + if (bb_graph[i].succ_count == 1) + { + total = 0; + /* One of the counts will be invalid, but it is zero, + so adding it in also doesn't hurt. */ + for (arcptr = bb_graph[i].succ; arcptr; + arcptr = arcptr->succ_next) + total += arcptr->arc_count; + /* Calculate count for remaining arc by conservation. */ + total = bb_graph[i].exec_count - total; + /* Search for the invalid arc, and set its count. */ + for (arcptr = bb_graph[i].succ; arcptr; + arcptr = arcptr->succ_next) + if (! arcptr->count_valid) + break; + if (! arcptr) + abort (); + arcptr->count_valid = 1; + arcptr->arc_count = total; + bb_graph[i].succ_count--; + + bb_graph[arcptr->target].pred_count--; + changes = 1; + } + if (bb_graph[i].pred_count == 1) + { + total = 0; + /* One of the counts will be invalid, but it is zero, + so adding it in also doesn't hurt. */ + for (arcptr = bb_graph[i].pred; arcptr; + arcptr = arcptr->pred_next) + total += arcptr->arc_count; + /* Calculate count for remaining arc by conservation. */ + total = bb_graph[i].exec_count - total; + /* Search for the invalid arc, and set its count. */ + for (arcptr = bb_graph[i].pred; arcptr; + arcptr = arcptr->pred_next) + if (! arcptr->count_valid) + break; + if (! arcptr) + abort (); + arcptr->count_valid = 1; + arcptr->arc_count = total; + bb_graph[i].pred_count--; + + bb_graph[arcptr->source].succ_count--; + changes = 1; + } + } + } + } + + /* If the graph has been correctly solved, every block will have a + succ and pred count of zero. */ + for (i = 0; i < num_blocks; i++) + if (bb_graph[i].succ_count || bb_graph[i].pred_count) + abort (); +} + + +static void +read_files () +{ + struct stat buf; + struct bb_info_list *list_end = 0; + struct bb_info_list *b_ptr; + long total, first_time; + + /* Read and ignore the first word of the .da file, which is the count of + how many numbers follow. */ + if (da_file && __read_long (&total, da_file, 8)) + abort(); + + while (! feof (bbg_file)) + { + b_ptr = (struct bb_info_list *) xmalloc (sizeof (struct bb_info_list)); + + b_ptr->next = 0; + if (list_end) + list_end->next = b_ptr; + else + bb_graph_list = b_ptr; + list_end = b_ptr; + + /* Read in the data in the .bbg file and reconstruct the program flow + graph for one function. */ + create_program_flow_graph (b_ptr, first_time); + + /* Set the EOF condition if at the end of file. */ + ungetc (getc (bbg_file), bbg_file); + } + + /* Check to make sure the .da file data is valid. */ + + if (da_file) + { + if (feof (da_file)) + fprintf (stderr, ".da file contents exhausted too early\n"); + /* Should be at end of file now. */ + if (__read_long (&total, da_file, 8) == 0) + fprintf (stderr, ".da file contents not exhausted\n"); + } + + /* Calculate all of the basic block execution counts and branch + taken probabilities. */ + + for (b_ptr = bb_graph_list; b_ptr; b_ptr = b_ptr->next) + solve_program_flow_graph (b_ptr); + + /* Read in all of the data from the .bb file. This info will be accessed + sequentially twice. */ + stat (bb_file_name, &buf); + bb_data_size = buf.st_size / 4; + + bb_data = (char *) xmalloc ((unsigned) buf.st_size); + fread (bb_data, sizeof (char), buf.st_size, bb_file); + + fclose (bb_file); + if (da_file) + fclose (da_file); + fclose (bbg_file); +} + + +/* Scan the data in the .bb file to find all source files referenced, + and the largest line number mentioned in each one. */ + +static void +scan_for_source_files () +{ + struct sourcefile *s_ptr; + char *ptr; + int count; + long line_num; + + /* Search the bb_data to find: + 1) The number of sources files contained herein, and + 2) The largest line number for each source file. */ + + ptr = bb_data; + sources = 0; + for (count = 0; count < bb_data_size; count++) + { + __fetch_long (&line_num, ptr, 4); + ptr += 4; + if (line_num == -1) + { + /* A source file name follows. Check to see if we already have + a sourcefile structure for this file. */ + s_ptr = sources; + while (s_ptr && strcmp (s_ptr->name, ptr)) + s_ptr = s_ptr->next; + + if (s_ptr == 0) + { + /* No sourcefile structure for this file name exists, create + a new one, and append it to the front of the sources list. */ + s_ptr = (struct sourcefile *) xmalloc (sizeof(struct sourcefile)); + s_ptr->name = xmalloc (strlen ((char *) ptr) + 1); + strcpy (s_ptr->name, (char *) ptr); + s_ptr->maxlineno = 0; + s_ptr->next = sources; + sources = s_ptr; + } + + /* Scan past the file name. */ + { + long delim; + do { + count++; + __fetch_long (&delim, ptr, 4); + ptr += 4; + } while (delim != line_num); + } + } + else if (line_num == -2) + { + long delim; + + /* A function name follows. Ignore it. */ + do { + count++; + __fetch_long (&delim, ptr, 4); + ptr += 4; + } while (delim != line_num); + } + /* There will be a zero before the first file name, in which case s_ptr + will still be uninitialized. So, only try to set the maxlineno + field if line_num is non-zero. */ + else if (line_num > 0) + { + if (s_ptr->maxlineno <= line_num) + s_ptr->maxlineno = line_num + 1; + } + else if (line_num < 0) + { + /* Don't know what this is, but it's garbage. */ + abort(); + } + } +} + +/* For calculating coverage at the function level. */ + +static int function_source_lines; +static int function_source_lines_executed; +static int function_branches; +static int function_branches_executed; +static int function_branches_taken; +static int function_calls; +static int function_calls_executed; +static char *function_name; + +/* Calculate the branch taken probabilities for all arcs branches at the + end of this block. */ + +static void +calculate_branch_probs (current_graph, block_num, branch_probs, last_line_num) + struct bb_info_list *current_graph; + int block_num; + struct arcdata **branch_probs; + int last_line_num; +{ + int total; + struct adj_list *arcptr; + struct arcdata *end_ptr, *a_ptr; + + total = current_graph->bb_graph[block_num].exec_count; + for (arcptr = current_graph->bb_graph[block_num].succ; arcptr; + arcptr = arcptr->succ_next) + { + /* Ignore fall through arcs as they aren't really branches. */ + + if (arcptr->fall_through) + continue; + + a_ptr = (struct arcdata *) xmalloc (sizeof (struct arcdata)); + if (total == 0) + a_ptr->prob = -1; + else + a_ptr->prob = ((arcptr->arc_count * 100) + (total >> 1)) / total; + a_ptr->call_insn = arcptr->fake; + + if (output_function_summary) + { + if (a_ptr->call_insn) + { + function_calls++; + if (a_ptr->prob != -1) + function_calls_executed++; + } + else + { + function_branches++; + if (a_ptr->prob != -1) + function_branches_executed++; + if (a_ptr->prob > 0) + function_branches_taken++; + } + } + + /* Append the new branch to the end of the list. */ + a_ptr->next = 0; + if (! branch_probs[last_line_num]) + branch_probs[last_line_num] = a_ptr; + else + { + end_ptr = branch_probs[last_line_num]; + while (end_ptr->next != 0) + end_ptr = end_ptr->next; + end_ptr->next = a_ptr; + } + } +} + +/* Output summary info for a function. */ + +static void +function_summary () +{ + if (function_source_lines) + fprintf (stdout, "%6.2lf%% of %d source lines executed in function %s\n", + (((double) function_source_lines_executed / function_source_lines) + * 100), function_source_lines, function_name); + else + fprintf (stdout, "No executable source lines in function %s\n", + function_name); + + if (output_branch_probs) + { + if (function_branches) + { + fprintf (stdout, "%6.2lf%% of %d branches executed in function %s\n", + (((double) function_branches_executed / function_branches) + * 100), function_branches, function_name); + fprintf (stdout, + "%6.2lf%% of %d branches taken at least once in function %s\n", + (((double) function_branches_taken / function_branches) + * 100), function_branches, function_name); + } + else + fprintf (stdout, "No branches in function %s\n", function_name); + if (function_calls) + fprintf (stdout, "%6.2lf%% of %d calls executed in function %s\n", + (((double) function_calls_executed / function_calls) + * 100), function_calls, function_name); + else + fprintf (stdout, "No calls in function %s\n", function_name); + } +} + +/* Calculate line execution counts, and output the data to a .tcov file. */ + +static void +output_data () +{ + /* When scanning data, this is true only if the data applies to the + current source file. */ + int this_file; + /* An array indexed by line number which indicates how many times that line + was executed. */ + long *line_counts; + /* An array indexed by line number which indicates whether the line was + present in the bb file (i.e. whether it had code associate with it). + Lines never executed are those which both exist, and have zero execution + counts. */ + char *line_exists; + /* An array indexed by line number, which contains a list of branch + probabilities, one for each branch on that line. */ + struct arcdata **branch_probs; + struct sourcefile *s_ptr; + char *source_file_name; + FILE *source_file; + struct bb_info_list *current_graph; + int count; + char *cptr; + long block_num; + long line_num; + long last_line_num; + int i; + struct arcdata *a_ptr; + /* Buffer used for reading in lines from the source file. */ + char string[STRING_SIZE]; + /* For calculating coverage at the file level. */ + int total_source_lines; + int total_source_lines_executed; + int total_branches; + int total_branches_executed; + int total_branches_taken; + int total_calls; + int total_calls_executed; + + /* Now, for each source file, allocate an array big enough to hold a count + for each line. Scan through the bb_data, and when the file name matches + the current file name, then for each following line number, increment + the line number execution count indicated by the execution count of + the appropriate basic block. */ + + for (s_ptr = sources; s_ptr; s_ptr = s_ptr->next) + { + /* If this is a relative file name, and an object directory has been + specified, then make it relative to the object directory name. */ + if (*s_ptr->name != '/' && object_directory != 0 + && *object_directory != '\0') + { + int objdir_count = strlen (object_directory); + source_file_name = xmalloc (objdir_count + strlen (s_ptr->name) + 2); + strcpy (source_file_name, object_directory); + if (object_directory[objdir_count - 1] != '/') + source_file_name[objdir_count++] = '/'; + strcpy (source_file_name + objdir_count, s_ptr->name); + } + else + source_file_name = s_ptr->name; + + line_counts = (long *) xmalloc (sizeof (long) * s_ptr->maxlineno); + bzero ((char *) line_counts, sizeof (long) * s_ptr->maxlineno); + line_exists = xmalloc (s_ptr->maxlineno); + bzero (line_exists, s_ptr->maxlineno); + if (output_branch_probs) + { + branch_probs = (struct arcdata **) xmalloc (sizeof (struct arcdata **) + * s_ptr->maxlineno); + bzero ((char *) branch_probs, + sizeof (struct arcdata **) * s_ptr->maxlineno); + } + + /* There will be a zero at the beginning of the bb info, before the + first list of line numbers, so must initialize block_num to 0. */ + block_num = 0; + this_file = 0; + current_graph = 0; + { + /* Pointer into the bb_data, incremented while scanning the data. */ + char *ptr = bb_data; + for (count = 0; count < bb_data_size; count++) + { + long delim; + + __fetch_long (&line_num, ptr, 4); + ptr += 4; + if (line_num == -1) + { + /* Marks the beginning of a file name. Check to see whether + this is the filename we are currently collecting data for. */ + + if (strcmp (s_ptr->name, ptr)) + this_file = 0; + else + this_file = 1; + + /* Scan past the file name. */ + do { + count++; + __fetch_long (&delim, ptr, 4); + ptr += 4; + } while (delim != line_num); + } + else if (line_num == -2) + { + /* Marks the start of a new function. Advance to the next + program flow graph. */ + + if (! current_graph) + current_graph = bb_graph_list; + else + { + if (block_num == current_graph->num_blocks - 1) + /* Last block falls through to exit. */ + ; + else if (block_num == current_graph->num_blocks - 2) + { + if (output_branch_probs && this_file) + calculate_branch_probs (current_graph, block_num, + branch_probs, last_line_num); + } + else + { + fprintf (stderr, + "didn't use all bb entries of graph, function %s\n", + function_name); + fprintf (stderr, "block_num = %d, num_blocks = %d\n", + block_num, current_graph->num_blocks); + } + + current_graph = current_graph->next; + block_num = 0; + + if (output_function_summary && this_file) + function_summary (); + } + + if (output_function_summary) + { + function_source_lines = 0; + function_source_lines_executed = 0; + function_branches = 0; + function_branches_executed = 0; + function_branches_taken = 0; + function_calls = 0; + function_calls_executed = 0; + } + + /* Save the function name for later use. */ + function_name = ptr; + + /* Scan past the file name. */ + do { + count++; + __fetch_long (&delim, ptr, 4); + ptr += 4; + } while (delim != line_num); + } + else if (line_num == 0) + { + /* Marks the end of a block. */ + + if (block_num >= current_graph->num_blocks) + { + fprintf (stderr, "ERROR: too many basic blocks in .bb file %s\n", + function_name); + abort (); + } + + if (output_branch_probs && this_file) + calculate_branch_probs (current_graph, block_num, + branch_probs, last_line_num); + + block_num++; + } + else if (this_file) + { + if (output_function_summary) + { + if (line_exists[line_num] == 0) + function_source_lines++; + if (line_counts[line_num] == 0 + && current_graph->bb_graph[block_num].exec_count != 0) + function_source_lines_executed++; + } + + /* Accumulate execution data for this line number. */ + + line_counts[line_num] + += current_graph->bb_graph[block_num].exec_count; + line_exists[line_num] = 1; + last_line_num = line_num; + } + } + } + + if (output_function_summary && this_file) + function_summary (); + + /* Calculate summary test coverage statistics. */ + + total_source_lines = 0; + total_source_lines_executed = 0; + total_branches = 0; + total_branches_executed = 0; + total_branches_taken = 0; + total_calls = 0; + total_calls_executed = 0; + + for (count = 1; count < s_ptr->maxlineno; count++) + { + if (line_exists[count]) + { + total_source_lines++; + if (line_counts[count]) + total_source_lines_executed++; + } + if (output_branch_probs) + { + for (a_ptr = branch_probs[count]; a_ptr; a_ptr = a_ptr->next) + { + if (a_ptr->call_insn) + { + total_calls++; + if (a_ptr->prob != -1) + total_calls_executed++; + } + else + { + total_branches++; + if (a_ptr->prob != -1) + total_branches_executed++; + if (a_ptr->prob > 0) + total_branches_taken++; + } + } + } + } + + if (total_source_lines) + fprintf (stdout, + "%6.2lf%% of %d source lines executed in file %s\n", + (((double) total_source_lines_executed / total_source_lines) + * 100), total_source_lines, source_file_name); + else + fprintf (stdout, "No executable source lines in file %s\n", + source_file_name); + + if (output_branch_probs) + { + if (total_branches) + { + fprintf (stdout, "%6.2lf%% of %d branches executed in file %s\n", + (((double) total_branches_executed / total_branches) + * 100), total_branches, source_file_name); + fprintf (stdout, + "%6.2lf%% of %d branches taken at least once in file %s\n", + (((double) total_branches_taken / total_branches) + * 100), total_branches, source_file_name); + } + else + fprintf (stdout, "No branches in file %s\n", source_file_name); + if (total_calls) + fprintf (stdout, "%6.2lf%% of %d calls executed in file %s\n", + (((double) total_calls_executed / total_calls) + * 100), total_calls, source_file_name); + else + fprintf (stdout, "No calls in file %s\n", source_file_name); + } + + if (output_gcov_file) + { + /* Now the statistics are ready. Read in the source file one line + at a time, and output that line to the gcov file preceded by + its execution count if non zero. */ + + source_file = fopen (source_file_name, "r"); + if (source_file == NULL) + { + fprintf (stderr, "Could not open source file %s.\n", + source_file_name); + free (line_counts); + free (line_exists); + continue; + } + + count = strlen (source_file_name); + cptr = rindex (s_ptr->name, '/'); + if (cptr) + cptr = cptr + 1; + else + cptr = s_ptr->name; + if (output_long_names && strcmp (cptr, input_file_name)) + { + gcov_file_name = xmalloc (count + 7 + strlen (input_file_name)); + + cptr = rindex (input_file_name, '/'); + if (cptr) + strcpy (gcov_file_name, cptr + 1); + else + strcpy (gcov_file_name, input_file_name); + + strcat (gcov_file_name, "."); + + cptr = rindex (source_file_name, '/'); + if (cptr) + strcat (gcov_file_name, cptr + 1); + else + strcat (gcov_file_name, source_file_name); + } + else + { + gcov_file_name = xmalloc (count + 6); + cptr = rindex (source_file_name, '/'); + if (cptr) + strcpy (gcov_file_name, cptr + 1); + else + strcpy (gcov_file_name, source_file_name); + } + + /* Don't strip off the ending for compatibility with tcov, since + this results in confusion if there is more than one file with + the same basename, e.g. tmp.c and tmp.h. */ + strcat (gcov_file_name, ".gcov"); + + gcov_file = fopen (gcov_file_name, "w"); + + if (gcov_file == NULL) + { + fprintf (stderr, "Could not open output file %s.\n", + gcov_file_name); + fclose (source_file); + free (line_counts); + free (line_exists); + continue; + } + + fprintf (stdout, "Creating %s.\n", gcov_file_name); + + for (count = 1; count < s_ptr->maxlineno; count++) + { + char *retval; + int len; + + retval = fgets (string, STRING_SIZE, source_file); + + /* For lines which don't exist in the .bb file, print nothing + before the source line. For lines which exist but were never + executed, print ###### before the source line. Otherwise, + print the execution count before the source line. */ + /* There are 16 spaces of indentation added before the source + line so that tabs won't be messed up. */ + if (line_exists[count]) + { + if (line_counts[count]) + fprintf (gcov_file, "%12d %s", line_counts[count], + string); + else + fprintf (gcov_file, " ###### %s", string); + } + else + fprintf (gcov_file, "\t\t%s", string); + + /* In case the source file line is larger than our buffer, keep + reading and outputting lines until we get a newline. */ + len = strlen (string); + while ((len == 0 || string[strlen (string) - 1] != '\n') + && retval != NULL) + { + retval = fgets (string, STRING_SIZE, source_file); + fputs (string, gcov_file); + } + + if (output_branch_probs) + { + for (i = 0, a_ptr = branch_probs[count]; a_ptr; + a_ptr = a_ptr->next, i++) + { + if (a_ptr->call_insn) + { + if (a_ptr->prob == -1) + fprintf (gcov_file, "call %d never executed\n", i); + else + fprintf (gcov_file, + "call %d returns = %d%%\n", + i, 100 - a_ptr->prob); + } + else + { + if (a_ptr->prob == -1) + fprintf (gcov_file, "branch %d never executed\n", + i); + else + fprintf (gcov_file, "branch %d taken = %d%%\n", i, + a_ptr->prob); + } + } + } + + /* Gracefully handle errors while reading the source file. */ + if (retval == NULL) + { + fprintf (stderr, + "Unexpected EOF while reading source file %s.\n", + source_file_name); + break; + } + } + + /* Handle all remaining source lines. There may be lines + after the last line of code. */ + + { + char *retval = fgets (string, STRING_SIZE, source_file); + while (retval != NULL) + { + int len; + + fprintf (gcov_file, "\t\t%s", string); + + /* In case the source file line is larger than our buffer, keep + reading and outputting lines until we get a newline. */ + len = strlen (string); + while ((len == 0 || string[strlen (string) - 1] != '\n') + && retval != NULL) + { + retval = fgets (string, STRING_SIZE, source_file); + fputs (string, gcov_file); + } + + retval = fgets (string, STRING_SIZE, source_file); + } + } + + fclose (source_file); + fclose (gcov_file); + } + + free (line_counts); + free (line_exists); + } +} |