diff gcc/gcov.c @ 0:a06113de4d67

first commit
author kent <kent@cr.ie.u-ryukyu.ac.jp>
date Fri, 17 Jul 2009 14:47:48 +0900
parents
children 3bfb6c00c1e0
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gcc/gcov.c	Fri Jul 17 14:47:48 2009 +0900
@@ -0,0 +1,1986 @@
+/* Gcov.c: prepend line execution counts and branch probabilities to a
+   source file.
+   Copyright (C) 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 1999,
+   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
+   Free Software Foundation, Inc.
+   Contributed by James E. Wilson of Cygnus Support.
+   Mangled by Bob Manson of Cygnus Support.
+   Mangled further by Nathan Sidwell <nathan@codesourcery.com>
+
+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 3, 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 COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* ??? 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.  */
+
+/* Need an option to show individual block counts, and show
+   probabilities of fall through arcs.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "intl.h"
+#include "version.h"
+
+#include <getopt.h>
+
+#define IN_GCOV 1
+#include "gcov-io.h"
+#include "gcov-io.c"
+
+/* The gcno file is generated by -ftest-coverage option. The gcda file is
+   generated by a program compiled with -fprofile-arcs. Their formats
+   are documented in gcov-io.h.  */
+
+/* The functions in this file for creating and solution program flow graphs
+   are very similar to functions in the gcc source file profile.c.  In
+   some places we make use of the knowledge of how profile.c works to
+   select particular algorithms here.  */
+
+/* This is the size of the buffer used to read in source file lines.  */
+
+#define STRING_SIZE 200
+
+struct function_info;
+struct block_info;
+struct source_info;
+
+/* Describes an arc between two basic blocks.  */
+
+typedef struct arc_info
+{
+  /* source and destination blocks.  */
+  struct block_info *src;
+  struct block_info *dst;
+
+  /* transition counts.  */
+  gcov_type count;
+  /* used in cycle search, so that we do not clobber original counts.  */
+  gcov_type cs_count;
+
+  unsigned int count_valid : 1;
+  unsigned int on_tree : 1;
+  unsigned int fake : 1;
+  unsigned int fall_through : 1;
+
+  /* Arc is for a function that abnormally returns.  */
+  unsigned int is_call_non_return : 1;
+
+  /* Arc is for catch/setjmp.  */
+  unsigned int is_nonlocal_return : 1;
+
+  /* Is an unconditional branch.  */
+  unsigned int is_unconditional : 1;
+
+  /* Loop making arc.  */
+  unsigned int cycle : 1;
+
+  /* Next branch on line.  */
+  struct arc_info *line_next;
+
+  /* Links to next arc on src and dst lists.  */
+  struct arc_info *succ_next;
+  struct arc_info *pred_next;
+} arc_t;
+
+/* Describes a basic block. Contains lists of arcs to successor and
+   predecessor blocks.  */
+
+typedef struct block_info
+{
+  /* Chain of exit and entry arcs.  */
+  arc_t *succ;
+  arc_t *pred;
+
+  /* Number of unprocessed exit and entry arcs.  */
+  gcov_type num_succ;
+  gcov_type num_pred;
+
+  /* Block execution count.  */
+  gcov_type count;
+  unsigned flags : 13;
+  unsigned count_valid : 1;
+  unsigned valid_chain : 1;
+  unsigned invalid_chain : 1;
+
+  /* Block is a call instrumenting site.  */
+  unsigned is_call_site : 1; /* Does the call.  */
+  unsigned is_call_return : 1; /* Is the return.  */
+
+  /* Block is a landing pad for longjmp or throw.  */
+  unsigned is_nonlocal_return : 1;
+
+  union
+  {
+    struct
+    {
+     /* Array of line numbers and source files. source files are
+        introduced by a linenumber of zero, the next 'line number' is
+        the number of the source file.  Always starts with a source
+        file.  */
+      unsigned *encoding;
+      unsigned num;
+    } line; /* Valid until blocks are linked onto lines */
+    struct
+    {
+      /* Single line graph cycle workspace.  Used for all-blocks
+	 mode.  */
+      arc_t *arc;
+      unsigned ident;
+    } cycle; /* Used in all-blocks mode, after blocks are linked onto
+	       lines.  */
+  } u;
+
+  /* Temporary chain for solving graph, and for chaining blocks on one
+     line.  */
+  struct block_info *chain;
+
+} block_t;
+
+/* Describes a single function. Contains an array of basic blocks.  */
+
+typedef struct function_info
+{
+  /* Name of function.  */
+  char *name;
+  unsigned ident;
+  unsigned checksum;
+
+  /* Array of basic blocks.  */
+  block_t *blocks;
+  unsigned num_blocks;
+  unsigned blocks_executed;
+
+  /* Raw arc coverage counts.  */
+  gcov_type *counts;
+  unsigned num_counts;
+
+  /* First line number.  */
+  unsigned line;
+  struct source_info *src;
+
+  /* Next function in same source file.  */
+  struct function_info *line_next;
+
+  /* Next function.  */
+  struct function_info *next;
+} function_t;
+
+/* Describes coverage of a file or function.  */
+
+typedef struct coverage_info
+{
+  int lines;
+  int lines_executed;
+
+  int branches;
+  int branches_executed;
+  int branches_taken;
+
+  int calls;
+  int calls_executed;
+
+  char *name;
+} coverage_t;
+
+/* Describes a single line of source. Contains a chain of basic blocks
+   with code on it.  */
+
+typedef struct line_info
+{
+  gcov_type count;	   /* execution count */
+  union
+  {
+    arc_t *branches;	   /* branches from blocks that end on this
+			      line. Used for branch-counts when not
+			      all-blocks mode.  */
+    block_t *blocks;       /* blocks which start on this line.  Used
+			      in all-blocks mode.  */
+  } u;
+  unsigned exists : 1;
+} line_t;
+
+/* Describes a file mentioned in the block graph.  Contains an array
+   of line info.  */
+
+typedef struct source_info
+{
+  /* Name of source file.  */
+  char *name;
+  unsigned index;
+  time_t file_time;
+
+  /* Array of line information.  */
+  line_t *lines;
+  unsigned num_lines;
+
+  coverage_t coverage;
+
+  /* Functions in this source file.  These are in ascending line
+     number order.  */
+  function_t *functions;
+
+  /* Next source file.  */
+  struct source_info *next;
+} source_t;
+
+/* Holds a list of function basic block graphs.  */
+
+static function_t *functions;
+
+/* This points to the head of the sourcefile structure list.  New elements
+   are always prepended.  */
+
+static source_t *sources;
+
+/* Next index for a source file.  */
+
+static unsigned source_index;
+
+/* This holds data summary information.  */
+
+static struct gcov_summary object_summary;
+static unsigned program_count;
+
+/* Modification time of graph file.  */
+
+static time_t bbg_file_time;
+
+/* Name and file pointer of the input file for the basic block graph.  */
+
+static char *bbg_file_name;
+
+/* Stamp of the bbg file */
+static unsigned bbg_stamp;
+
+/* Name and file pointer of the input file for the arc count data.  */
+
+static char *da_file_name;
+
+/* Data file is missing.  */
+
+static int no_data_file;
+
+/* If there is several input files, compute and display results after
+   reading all data files.  This way if two or more gcda file refer to
+   the same source file (eg inline subprograms in a .h file), the
+   counts are added.  */
+
+static int multiple_files = 0;
+
+/* Output branch probabilities.  */
+
+static int flag_branches = 0;
+
+/* Show unconditional branches too.  */
+static int flag_unconditional = 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 flag_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.  */
+
+static int flag_long_names = 0;
+
+/* Output count information for every basic block, not merely those
+   that contain line number information.  */
+
+static int flag_all_blocks = 0;
+
+/* Output summary info for each function.  */
+
+static int flag_function_summary = 0;
+
+/* Object directory file prefix.  This is the directory/file where the
+   graph and data files are looked for, if nonzero.  */
+
+static char *object_directory = 0;
+
+/* Preserve all pathname components. Needed when object files and
+   source files are in subdirectories. '/' is mangled as '#', '.' is
+   elided and '..' mangled to '^'.  */
+
+static int flag_preserve_paths = 0;
+
+/* Output the number of times a branch was taken as opposed to the percentage
+   of times it was taken.  */
+
+static int flag_counts = 0;
+
+/* Forward declarations.  */
+static void fnotice (FILE *, const char *, ...) ATTRIBUTE_PRINTF_2;
+static int process_args (int, char **);
+static void print_usage (int) ATTRIBUTE_NORETURN;
+static void print_version (void) ATTRIBUTE_NORETURN;
+static void process_file (const char *);
+static void generate_results (const char *);
+static void create_file_names (const char *);
+static source_t *find_source (const char *);
+static int read_graph_file (void);
+static int read_count_file (void);
+static void solve_flow_graph (function_t *);
+static void add_branch_counts (coverage_t *, const arc_t *);
+static void add_line_counts (coverage_t *, function_t *);
+static void function_summary (const coverage_t *, const char *);
+static const char *format_gcov (gcov_type, gcov_type, int);
+static void accumulate_line_counts (source_t *);
+static int output_branch_count (FILE *, int, const arc_t *);
+static void output_lines (FILE *, const source_t *);
+static char *make_gcov_file_name (const char *, const char *);
+static void release_structures (void);
+extern int main (int, char **);
+
+int
+main (int argc, char **argv)
+{
+  int argno;
+
+  /* Unlock the stdio streams.  */
+  unlock_std_streams ();
+
+  gcc_init_libintl ();
+
+  /* Handle response files.  */
+  expandargv (&argc, &argv);
+
+  argno = process_args (argc, argv);
+  if (optind == argc)
+    print_usage (true);
+
+  if (argc - argno > 1)
+    multiple_files = 1;
+
+  for (; argno != argc; argno++)
+    process_file (argv[argno]);
+
+  generate_results (multiple_files ? NULL : argv[argc - 1]);
+
+  release_structures ();
+
+  return 0;
+}
+
+static void
+fnotice (FILE *file, const char *cmsgid, ...)
+{
+  va_list ap;
+
+  va_start (ap, cmsgid);
+  vfprintf (file, _(cmsgid), ap);
+  va_end (ap);
+}
+
+/* Print a usage message and exit.  If ERROR_P is nonzero, this is an error,
+   otherwise the output of --help.  */
+
+static void
+print_usage (int error_p)
+{
+  FILE *file = error_p ? stderr : stdout;
+  int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
+
+  fnotice (file, "Usage: gcov [OPTION]... SOURCEFILE...\n\n");
+  fnotice (file, "Print code coverage information.\n\n");
+  fnotice (file, "  -h, --help                      Print this help, then exit\n");
+  fnotice (file, "  -v, --version                   Print version number, then exit\n");
+  fnotice (file, "  -a, --all-blocks                Show information for every basic block\n");
+  fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
+  fnotice (file, "  -c, --branch-counts             Given counts of branches taken\n\
+                                    rather than percentages\n");
+  fnotice (file, "  -n, --no-output                 Do not create an output file\n");
+  fnotice (file, "  -l, --long-file-names           Use long output file names for included\n\
+                                    source files\n");
+  fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
+  fnotice (file, "  -o, --object-directory DIR|FILE Search for object files in DIR or called FILE\n");
+  fnotice (file, "  -p, --preserve-paths            Preserve all pathname components\n");
+  fnotice (file, "  -u, --unconditional-branches    Show unconditional branch counts too\n");
+  fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
+	   bug_report_url);
+  exit (status);
+}
+
+/* Print version information and exit.  */
+
+static void
+print_version (void)
+{
+  fnotice (stdout, "gcov %s%s\n", pkgversion_string, version_string);
+  fprintf (stdout, "Copyright %s 2009 Free Software Foundation, Inc.\n",
+	   _("(C)"));
+  fnotice (stdout,
+	   _("This is free software; see the source for copying conditions.\n"
+	     "There is NO warranty; not even for MERCHANTABILITY or \n"
+	     "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
+  exit (SUCCESS_EXIT_CODE);
+}
+
+static const struct option options[] =
+{
+  { "help",                 no_argument,       NULL, 'h' },
+  { "version",              no_argument,       NULL, 'v' },
+  { "all-blocks",           no_argument,       NULL, 'a' },
+  { "branch-probabilities", no_argument,       NULL, 'b' },
+  { "branch-counts",        no_argument,       NULL, 'c' },
+  { "no-output",            no_argument,       NULL, 'n' },
+  { "long-file-names",      no_argument,       NULL, 'l' },
+  { "function-summaries",   no_argument,       NULL, 'f' },
+  { "preserve-paths",       no_argument,       NULL, 'p' },
+  { "object-directory",     required_argument, NULL, 'o' },
+  { "object-file",          required_argument, NULL, 'o' },
+  { "unconditional-branches", no_argument,     NULL, 'u' },
+  { 0, 0, 0, 0 }
+};
+
+/* Process args, return index to first non-arg.  */
+
+static int
+process_args (int argc, char **argv)
+{
+  int opt;
+
+  while ((opt = getopt_long (argc, argv, "abcfhlno:puv", options, NULL)) != -1)
+    {
+      switch (opt)
+	{
+	case 'a':
+	  flag_all_blocks = 1;
+	  break;
+	case 'b':
+	  flag_branches = 1;
+	  break;
+	case 'c':
+	  flag_counts = 1;
+	  break;
+	case 'f':
+	  flag_function_summary = 1;
+	  break;
+	case 'h':
+	  print_usage (false);
+	  /* print_usage will exit.  */
+	case 'l':
+	  flag_long_names = 1;
+	  break;
+	case 'n':
+	  flag_gcov_file = 0;
+	  break;
+	case 'o':
+	  object_directory = optarg;
+	  break;
+	case 'p':
+	  flag_preserve_paths = 1;
+	  break;
+	case 'u':
+	  flag_unconditional = 1;
+	  break;
+	case 'v':
+	  print_version ();
+	  /* print_version will exit.  */
+	default:
+	  print_usage (true);
+	  /* print_usage will exit.  */
+	}
+    }
+
+  return optind;
+}
+
+/* Process a single source file.  */
+
+static void
+process_file (const char *file_name)
+{
+  function_t *fn;
+  function_t *fn_p;
+  function_t *old_functions;
+
+  /* Save and clear the list of current functions.  They will be appended
+     later.  */
+  old_functions = functions;
+  functions = NULL;
+
+  create_file_names (file_name);
+  if (read_graph_file ())
+    return;
+
+  if (!functions)
+    {
+      fnotice (stderr, "%s:no functions found\n", bbg_file_name);
+      return;
+    }
+
+  if (read_count_file ())
+    return;
+
+  for (fn_p = NULL, fn = functions; fn; fn_p = fn, fn = fn->next)
+    solve_flow_graph (fn);
+
+  if (fn_p)
+    fn_p->next = old_functions;
+}
+
+static void
+generate_results (const char *file_name)
+{
+  source_t *src;
+  function_t *fn;
+
+  for (src = sources; src; src = src->next)
+    src->lines = XCNEWVEC (line_t, src->num_lines);
+  for (fn = functions; fn; fn = fn->next)
+    {
+      coverage_t coverage;
+
+      memset (&coverage, 0, sizeof (coverage));
+      coverage.name = fn->name;
+      add_line_counts (flag_function_summary ? &coverage : NULL, fn);
+      if (flag_function_summary)
+	{
+	  function_summary (&coverage, "Function");
+	  fnotice (stdout, "\n");
+	}
+    }
+
+  for (src = sources; src; src = src->next)
+    {
+      accumulate_line_counts (src);
+      function_summary (&src->coverage, "File");
+      if (flag_gcov_file)
+	{
+	  char *gcov_file_name = make_gcov_file_name (file_name, src->name);
+	  FILE *gcov_file = fopen (gcov_file_name, "w");
+
+	  if (gcov_file)
+	    {
+	      fnotice (stdout, "%s:creating '%s'\n",
+		       src->name, gcov_file_name);
+	      output_lines (gcov_file, src);
+	      if (ferror (gcov_file))
+		    fnotice (stderr, "%s:error writing output file '%s'\n",
+			     src->name, gcov_file_name);
+	      fclose (gcov_file);
+	    }
+	  else
+	    fnotice (stderr, "%s:could not open output file '%s'\n",
+		     src->name, gcov_file_name);
+	  free (gcov_file_name);
+	}
+      fnotice (stdout, "\n");
+    }
+}
+
+/* Release all memory used.  */
+
+static void
+release_structures (void)
+{
+  function_t *fn;
+  source_t *src;
+
+  while ((src = sources))
+    {
+      sources = src->next;
+
+      free (src->name);
+      free (src->lines);
+    }
+
+  while ((fn = functions))
+    {
+      unsigned ix;
+      block_t *block;
+
+      functions = fn->next;
+      for (ix = fn->num_blocks, block = fn->blocks; ix--; block++)
+	{
+	  arc_t *arc, *arc_n;
+
+	  for (arc = block->succ; arc; arc = arc_n)
+	    {
+	      arc_n = arc->succ_next;
+	      free (arc);
+	    }
+	}
+      free (fn->blocks);
+      free (fn->counts);
+    }
+}
+
+/* Generate the names of the graph and data files. If OBJECT_DIRECTORY
+   is not specified, these are looked for in the current directory,
+   and named from the basename of the FILE_NAME sans extension. If
+   OBJECT_DIRECTORY is specified and is a directory, the files are in
+   that directory, but named from the basename of the FILE_NAME, sans
+   extension. Otherwise OBJECT_DIRECTORY is taken to be the name of
+   the object *file*, and the data files are named from that.  */
+
+static void
+create_file_names (const char *file_name)
+{
+  char *cptr;
+  char *name;
+  int length = strlen (file_name);
+  int base;
+
+  /* Free previous file names.  */
+  if (bbg_file_name)
+    free (bbg_file_name);
+  if (da_file_name)
+    free (da_file_name);
+  da_file_name = bbg_file_name = NULL;
+  bbg_file_time = 0;
+  bbg_stamp = 0;
+
+  if (object_directory && object_directory[0])
+    {
+      struct stat status;
+
+      length += strlen (object_directory) + 2;
+      name = XNEWVEC (char, length);
+      name[0] = 0;
+
+      base = !stat (object_directory, &status) && S_ISDIR (status.st_mode);
+      strcat (name, object_directory);
+      if (base && (! IS_DIR_SEPARATOR (name[strlen (name) - 1])))
+	strcat (name, "/");
+    }
+  else
+    {
+      name = XNEWVEC (char, length + 1);
+      name[0] = 0;
+      base = 1;
+    }
+
+  if (base)
+    {
+      /* Append source file name.  */
+      const char *cptr = lbasename (file_name);
+      strcat (name, cptr ? cptr : file_name);
+    }
+
+  /* Remove the extension.  */
+  cptr = strrchr (name, '.');
+  if (cptr)
+    *cptr = 0;
+
+  length = strlen (name);
+  
+  bbg_file_name = XNEWVEC (char, length + strlen (GCOV_NOTE_SUFFIX) + 1);
+  strcpy (bbg_file_name, name);
+  strcpy (bbg_file_name + length, GCOV_NOTE_SUFFIX);
+
+  da_file_name = XNEWVEC (char, length + strlen (GCOV_DATA_SUFFIX) + 1);
+  strcpy (da_file_name, name);
+  strcpy (da_file_name + length, GCOV_DATA_SUFFIX);
+
+  free (name);
+  return;
+}
+
+/* Find or create a source file structure for FILE_NAME. Copies
+   FILE_NAME on creation */
+
+static source_t *
+find_source (const char *file_name)
+{
+  source_t *src;
+  struct stat status;
+
+  if (!file_name)
+    file_name = "<unknown>";
+
+  for (src = sources; src; src = src->next)
+    if (!strcmp (file_name, src->name))
+      break;
+
+  if (!src)
+    {
+      src = XCNEW (source_t);
+      src->name = xstrdup (file_name);
+      src->coverage.name = src->name;
+      src->index = source_index++;
+      src->next = sources;
+      sources = src;
+      
+      if (!stat (file_name, &status))
+	src->file_time = status.st_mtime;
+    }
+
+  if (src->file_time > bbg_file_time)
+    {
+      static int info_emitted;
+
+      fnotice (stderr, "%s:source file is newer than graph file '%s'\n",
+	       src->name, bbg_file_name);
+      if (!info_emitted)
+	{
+	  fnotice (stderr,
+		   "(the message is only displayed one per source file)\n");
+	  info_emitted = 1;
+	}
+      src->file_time = 0;
+    }
+
+  return src;
+}
+
+/* Read the graph file. Return nonzero on fatal error.  */
+
+static int
+read_graph_file (void)
+{
+  unsigned version;
+  unsigned current_tag = 0;
+  struct function_info *fn = NULL;
+  function_t *old_functions_head = functions;
+  source_t *src = NULL;
+  unsigned ix;
+  unsigned tag;
+
+  if (!gcov_open (bbg_file_name, 1))
+    {
+      fnotice (stderr, "%s:cannot open graph file\n", bbg_file_name);
+      return 1;
+    }
+  bbg_file_time = gcov_time ();
+  if (!gcov_magic (gcov_read_unsigned (), GCOV_NOTE_MAGIC))
+    {
+      fnotice (stderr, "%s:not a gcov graph file\n", bbg_file_name);
+      gcov_close ();
+      return 1;
+    }
+
+  version = gcov_read_unsigned ();
+  if (version != GCOV_VERSION)
+    {
+      char v[4], e[4];
+
+      GCOV_UNSIGNED2STRING (v, version);
+      GCOV_UNSIGNED2STRING (e, GCOV_VERSION);
+
+      fnotice (stderr, "%s:version '%.4s', prefer '%.4s'\n",
+	       bbg_file_name, v, e);
+    }
+  bbg_stamp = gcov_read_unsigned ();
+
+  while ((tag = gcov_read_unsigned ()))
+    {
+      unsigned length = gcov_read_unsigned ();
+      gcov_position_t base = gcov_position ();
+
+      if (tag == GCOV_TAG_FUNCTION)
+	{
+	  char *function_name;
+	  unsigned ident, checksum, lineno;
+	  source_t *src;
+	  function_t *probe, *prev;
+
+	  ident = gcov_read_unsigned ();
+	  checksum = gcov_read_unsigned ();
+	  function_name = xstrdup (gcov_read_string ());
+	  src = find_source (gcov_read_string ());
+	  lineno = gcov_read_unsigned ();
+
+	  fn = XCNEW (function_t);
+	  fn->name = function_name;
+	  fn->ident = ident;
+	  fn->checksum = checksum;
+	  fn->src = src;
+	  fn->line = lineno;
+
+	  fn->next = functions;
+	  functions = fn;
+	  current_tag = tag;
+
+	  if (lineno >= src->num_lines)
+	    src->num_lines = lineno + 1;
+	  /* Now insert it into the source file's list of
+	     functions. Normally functions will be encountered in
+	     ascending order, so a simple scan is quick.  */
+	  for (probe = src->functions, prev = NULL;
+	       probe && probe->line > lineno;
+	       prev = probe, probe = probe->line_next)
+	    continue;
+	  fn->line_next = probe;
+	  if (prev)
+	    prev->line_next = fn;
+	  else
+	    src->functions = fn;
+	}
+      else if (fn && tag == GCOV_TAG_BLOCKS)
+	{
+	  if (fn->blocks)
+	    fnotice (stderr, "%s:already seen blocks for '%s'\n",
+		     bbg_file_name, fn->name);
+	  else
+	    {
+	      unsigned ix, num_blocks = GCOV_TAG_BLOCKS_NUM (length);
+	      fn->num_blocks = num_blocks;
+
+	      fn->blocks = XCNEWVEC (block_t, fn->num_blocks);
+	      for (ix = 0; ix != num_blocks; ix++)
+		fn->blocks[ix].flags = gcov_read_unsigned ();
+	    }
+	}
+      else if (fn && tag == GCOV_TAG_ARCS)
+	{
+	  unsigned src = gcov_read_unsigned ();
+	  unsigned num_dests = GCOV_TAG_ARCS_NUM (length);
+
+	  if (src >= fn->num_blocks || fn->blocks[src].succ)
+	    goto corrupt;
+
+	  while (num_dests--)
+	    {
+	      struct arc_info *arc;
+	      unsigned dest = gcov_read_unsigned ();
+	      unsigned flags = gcov_read_unsigned ();
+
+	      if (dest >= fn->num_blocks)
+		goto corrupt;
+	      arc = XCNEW (arc_t);
+
+	      arc->dst = &fn->blocks[dest];
+	      arc->src = &fn->blocks[src];
+
+	      arc->count = 0;
+	      arc->count_valid = 0;
+	      arc->on_tree = !!(flags & GCOV_ARC_ON_TREE);
+	      arc->fake = !!(flags & GCOV_ARC_FAKE);
+	      arc->fall_through = !!(flags & GCOV_ARC_FALLTHROUGH);
+
+	      arc->succ_next = fn->blocks[src].succ;
+	      fn->blocks[src].succ = arc;
+	      fn->blocks[src].num_succ++;
+
+	      arc->pred_next = fn->blocks[dest].pred;
+	      fn->blocks[dest].pred = arc;
+	      fn->blocks[dest].num_pred++;
+
+	      if (arc->fake)
+		{
+		  if (src)
+		    {
+		      /* Exceptional exit from this function, the
+			 source block must be a call.  */
+		      fn->blocks[src].is_call_site = 1;
+		      arc->is_call_non_return = 1;
+		    }
+		  else
+		    {
+		      /* Non-local return from a callee of this
+		         function. The destination block is a catch or
+		         setjmp.  */
+		      arc->is_nonlocal_return = 1;
+		      fn->blocks[dest].is_nonlocal_return = 1;
+		    }
+		}
+
+	      if (!arc->on_tree)
+		fn->num_counts++;
+	    }
+	}
+      else if (fn && tag == GCOV_TAG_LINES)
+	{
+	  unsigned blockno = gcov_read_unsigned ();
+	  unsigned *line_nos = XCNEWVEC (unsigned, length - 1);
+
+	  if (blockno >= fn->num_blocks || fn->blocks[blockno].u.line.encoding)
+	    goto corrupt;
+
+	  for (ix = 0; ;  )
+	    {
+	      unsigned lineno = gcov_read_unsigned ();
+
+	      if (lineno)
+		{
+		  if (!ix)
+		    {
+		      line_nos[ix++] = 0;
+		      line_nos[ix++] = src->index;
+		    }
+		  line_nos[ix++] = lineno;
+		  if (lineno >= src->num_lines)
+		    src->num_lines = lineno + 1;
+		}
+	      else
+		{
+		  const char *file_name = gcov_read_string ();
+
+		  if (!file_name)
+		    break;
+		  src = find_source (file_name);
+
+		  line_nos[ix++] = 0;
+		  line_nos[ix++] = src->index;
+		}
+	    }
+
+	  fn->blocks[blockno].u.line.encoding = line_nos;
+	  fn->blocks[blockno].u.line.num = ix;
+	}
+      else if (current_tag && !GCOV_TAG_IS_SUBTAG (current_tag, tag))
+	{
+	  fn = NULL;
+	  current_tag = 0;
+	}
+      gcov_sync (base, length);
+      if (gcov_is_error ())
+	{
+	corrupt:;
+	  fnotice (stderr, "%s:corrupted\n", bbg_file_name);
+	  gcov_close ();
+	  return 1;
+	}
+    }
+  gcov_close ();
+
+  /* We built everything backwards, so nreverse them all.  */
+
+  /* Reverse sources. Not strictly necessary, but we'll then process
+     them in the 'expected' order.  */
+  {
+    source_t *src, *src_p, *src_n;
+
+    for (src_p = NULL, src = sources; src; src_p = src, src = src_n)
+      {
+	src_n = src->next;
+	src->next = src_p;
+      }
+    sources =  src_p;
+  }
+
+  /* Reverse functions.  */
+  {
+    function_t *fn, *fn_p, *fn_n;
+
+    for (fn_p = old_functions_head, fn = functions;
+	 fn != old_functions_head;
+	 fn_p = fn, fn = fn_n)
+      {
+	unsigned ix;
+
+	fn_n = fn->next;
+	fn->next = fn_p;
+
+	/* Reverse the arcs.  */
+	for (ix = fn->num_blocks; ix--;)
+	  {
+	    arc_t *arc, *arc_p, *arc_n;
+
+	    for (arc_p = NULL, arc = fn->blocks[ix].succ; arc;
+		 arc_p = arc, arc = arc_n)
+	      {
+		arc_n = arc->succ_next;
+		arc->succ_next = arc_p;
+	      }
+	    fn->blocks[ix].succ = arc_p;
+
+	    for (arc_p = NULL, arc = fn->blocks[ix].pred; arc;
+		 arc_p = arc, arc = arc_n)
+	      {
+		arc_n = arc->pred_next;
+		arc->pred_next = arc_p;
+	      }
+	    fn->blocks[ix].pred = arc_p;
+	  }
+      }
+    functions = fn_p;
+  }
+  return 0;
+}
+
+/* Reads profiles from the count file and attach to each
+   function. Return nonzero if fatal error.  */
+
+static int
+read_count_file (void)
+{
+  unsigned ix;
+  unsigned version;
+  unsigned tag;
+  function_t *fn = NULL;
+  int error = 0;
+
+  if (!gcov_open (da_file_name, 1))
+    {
+      fnotice (stderr, "%s:cannot open data file, assuming not executed\n",
+	       da_file_name);
+      no_data_file = 1;
+      return 0;
+    }
+  if (!gcov_magic (gcov_read_unsigned (), GCOV_DATA_MAGIC))
+    {
+      fnotice (stderr, "%s:not a gcov data file\n", da_file_name);
+    cleanup:;
+      gcov_close ();
+      return 1;
+    }
+  version = gcov_read_unsigned ();
+  if (version != GCOV_VERSION)
+    {
+      char v[4], e[4];
+
+      GCOV_UNSIGNED2STRING (v, version);
+      GCOV_UNSIGNED2STRING (e, GCOV_VERSION);
+      
+      fnotice (stderr, "%s:version '%.4s', prefer version '%.4s'\n",
+	       da_file_name, v, e);
+    }
+  tag = gcov_read_unsigned ();
+  if (tag != bbg_stamp)
+    {
+      fnotice (stderr, "%s:stamp mismatch with graph file\n", da_file_name);
+      goto cleanup;
+    }
+
+  while ((tag = gcov_read_unsigned ()))
+    {
+      unsigned length = gcov_read_unsigned ();
+      unsigned long base = gcov_position ();
+
+      if (tag == GCOV_TAG_OBJECT_SUMMARY)
+	gcov_read_summary (&object_summary);
+      else if (tag == GCOV_TAG_PROGRAM_SUMMARY)
+	program_count++;
+      else if (tag == GCOV_TAG_FUNCTION)
+	{
+	  unsigned ident = gcov_read_unsigned ();
+	  struct function_info *fn_n = functions;
+
+	  /* Try to find the function in the list.
+	     To speed up the search, first start from the last function
+	     found.   */
+	  for (fn = fn ? fn->next : NULL; ; fn = fn->next)
+	    {
+	      if (fn)
+		;
+	      else if ((fn = fn_n))
+		fn_n = NULL;
+	      else
+		{
+		  fnotice (stderr, "%s:unknown function '%u'\n",
+			   da_file_name, ident);
+		  break;
+		}
+	      if (fn->ident == ident)
+		break;
+	    }
+
+	  if (!fn)
+	    ;
+	  else if (gcov_read_unsigned () != fn->checksum)
+	    {
+	    mismatch:;
+	      fnotice (stderr, "%s:profile mismatch for '%s'\n",
+		       da_file_name, fn->name);
+	      goto cleanup;
+	    }
+	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+	{
+	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->num_counts))
+	    goto mismatch;
+
+	  if (!fn->counts)
+	    fn->counts = XCNEWVEC (gcov_type, fn->num_counts);
+
+	  for (ix = 0; ix != fn->num_counts; ix++)
+	    fn->counts[ix] += gcov_read_counter ();
+	}
+      gcov_sync (base, length);
+      if ((error = gcov_is_error ()))
+	{
+	  fnotice (stderr, error < 0 ? "%s:overflowed\n" : "%s:corrupted\n",
+		   da_file_name);
+	  goto cleanup;
+	}
+    }
+
+  gcov_close ();
+  return 0;
+}
+
+/* Solve the flow graph. Propagate counts from the instrumented arcs
+   to the blocks and the uninstrumented arcs.  */
+
+static void
+solve_flow_graph (function_t *fn)
+{
+  unsigned ix;
+  arc_t *arc;
+  gcov_type *count_ptr = fn->counts;
+  block_t *blk;
+  block_t *valid_blocks = NULL;    /* valid, but unpropagated blocks.  */
+  block_t *invalid_blocks = NULL;  /* invalid, but inferable blocks.  */
+
+  if (fn->num_blocks < 2)
+    fnotice (stderr, "%s:'%s' lacks entry and/or exit blocks\n",
+	     bbg_file_name, fn->name);
+  else
+    {
+      if (fn->blocks[0].num_pred)
+	fnotice (stderr, "%s:'%s' has arcs to entry block\n",
+		 bbg_file_name, fn->name);
+      else
+	/* We can't deduce the entry block counts from the lack of
+	   predecessors.  */
+	fn->blocks[0].num_pred = ~(unsigned)0;
+
+      if (fn->blocks[fn->num_blocks - 1].num_succ)
+	fnotice (stderr, "%s:'%s' has arcs from exit block\n",
+		 bbg_file_name, fn->name);
+      else
+	/* Likewise, we can't deduce exit block counts from the lack
+	   of its successors.  */
+	fn->blocks[fn->num_blocks - 1].num_succ = ~(unsigned)0;
+    }
+
+  /* Propagate the measured counts, this must be done in the same
+     order as the code in profile.c  */
+  for (ix = 0, blk = fn->blocks; ix != fn->num_blocks; ix++, blk++)
+    {
+      block_t const *prev_dst = NULL;
+      int out_of_order = 0;
+      int non_fake_succ = 0;
+
+      for (arc = blk->succ; arc; arc = arc->succ_next)
+	{
+	  if (!arc->fake)
+	    non_fake_succ++;
+
+	  if (!arc->on_tree)
+	    {
+	      if (count_ptr)
+		arc->count = *count_ptr++;
+	      arc->count_valid = 1;
+	      blk->num_succ--;
+	      arc->dst->num_pred--;
+	    }
+	  if (prev_dst && prev_dst > arc->dst)
+	    out_of_order = 1;
+	  prev_dst = arc->dst;
+	}
+      if (non_fake_succ == 1)
+	{
+	  /* If there is only one non-fake exit, it is an
+	     unconditional branch.  */
+	  for (arc = blk->succ; arc; arc = arc->succ_next)
+	    if (!arc->fake)
+	      {
+		arc->is_unconditional = 1;
+		/* If this block is instrumenting a call, it might be
+		   an artificial block. It is not artificial if it has
+		   a non-fallthrough exit, or the destination of this
+		   arc has more than one entry.  Mark the destination
+		   block as a return site, if none of those conditions
+		   hold.  */
+		if (blk->is_call_site && arc->fall_through
+		    && arc->dst->pred == arc && !arc->pred_next)
+		  arc->dst->is_call_return = 1;
+	      }
+	}
+
+      /* Sort the successor arcs into ascending dst order. profile.c
+	 normally produces arcs in the right order, but sometimes with
+	 one or two out of order.  We're not using a particularly
+	 smart sort.  */
+      if (out_of_order)
+	{
+	  arc_t *start = blk->succ;
+	  unsigned changes = 1;
+
+	  while (changes)
+	    {
+	      arc_t *arc, *arc_p, *arc_n;
+
+	      changes = 0;
+	      for (arc_p = NULL, arc = start; (arc_n = arc->succ_next);)
+		{
+		  if (arc->dst > arc_n->dst)
+		    {
+		      changes = 1;
+		      if (arc_p)
+			arc_p->succ_next = arc_n;
+		      else
+			start = arc_n;
+		      arc->succ_next = arc_n->succ_next;
+		      arc_n->succ_next = arc;
+		      arc_p = arc_n;
+		    }
+		  else
+		    {
+		      arc_p = arc;
+		      arc = arc_n;
+		    }
+		}
+	    }
+	  blk->succ = start;
+	}
+
+      /* Place it on the invalid chain, it will be ignored if that's
+	 wrong.  */
+      blk->invalid_chain = 1;
+      blk->chain = invalid_blocks;
+      invalid_blocks = blk;
+    }
+
+  while (invalid_blocks || valid_blocks)
+    {
+      while ((blk = invalid_blocks))
+	{
+	  gcov_type total = 0;
+	  const arc_t *arc;
+
+	  invalid_blocks = blk->chain;
+	  blk->invalid_chain = 0;
+	  if (!blk->num_succ)
+	    for (arc = blk->succ; arc; arc = arc->succ_next)
+	      total += arc->count;
+	  else if (!blk->num_pred)
+	    for (arc = blk->pred; arc; arc = arc->pred_next)
+	      total += arc->count;
+	  else
+	    continue;
+
+	  blk->count = total;
+	  blk->count_valid = 1;
+	  blk->chain = valid_blocks;
+	  blk->valid_chain = 1;
+	  valid_blocks = blk;
+	}
+      while ((blk = valid_blocks))
+	{
+	  gcov_type total;
+	  arc_t *arc, *inv_arc;
+
+	  valid_blocks = blk->chain;
+	  blk->valid_chain = 0;
+	  if (blk->num_succ == 1)
+	    {
+	      block_t *dst;
+
+	      total = blk->count;
+	      inv_arc = NULL;
+	      for (arc = blk->succ; arc; arc = arc->succ_next)
+		{
+		  total -= arc->count;
+		  if (!arc->count_valid)
+		    inv_arc = arc;
+		}
+	      dst = inv_arc->dst;
+	      inv_arc->count_valid = 1;
+	      inv_arc->count = total;
+	      blk->num_succ--;
+	      dst->num_pred--;
+	      if (dst->count_valid)
+		{
+		  if (dst->num_pred == 1 && !dst->valid_chain)
+		    {
+		      dst->chain = valid_blocks;
+		      dst->valid_chain = 1;
+		      valid_blocks = dst;
+		    }
+		}
+	      else
+		{
+		  if (!dst->num_pred && !dst->invalid_chain)
+		    {
+		      dst->chain = invalid_blocks;
+		      dst->invalid_chain = 1;
+		      invalid_blocks = dst;
+		    }
+		}
+	    }
+	  if (blk->num_pred == 1)
+	    {
+	      block_t *src;
+
+	      total = blk->count;
+	      inv_arc = NULL;
+	      for (arc = blk->pred; arc; arc = arc->pred_next)
+		{
+		  total -= arc->count;
+		  if (!arc->count_valid)
+		    inv_arc = arc;
+		}
+	      src = inv_arc->src;
+	      inv_arc->count_valid = 1;
+	      inv_arc->count = total;
+	      blk->num_pred--;
+	      src->num_succ--;
+	      if (src->count_valid)
+		{
+		  if (src->num_succ == 1 && !src->valid_chain)
+		    {
+		      src->chain = valid_blocks;
+		      src->valid_chain = 1;
+		      valid_blocks = src;
+		    }
+		}
+	      else
+		{
+		  if (!src->num_succ && !src->invalid_chain)
+		    {
+		      src->chain = invalid_blocks;
+		      src->invalid_chain = 1;
+		      invalid_blocks = src;
+		    }
+		}
+	    }
+	}
+    }
+
+  /* If the graph has been correctly solved, every block will have a
+     valid count.  */
+  for (ix = 0; ix < fn->num_blocks; ix++)
+    if (!fn->blocks[ix].count_valid)
+      {
+	fnotice (stderr, "%s:graph is unsolvable for '%s'\n",
+		 bbg_file_name, fn->name);
+	break;
+      }
+}
+
+
+
+/* Increment totals in COVERAGE according to arc ARC.  */
+
+static void
+add_branch_counts (coverage_t *coverage, const arc_t *arc)
+{
+  if (arc->is_call_non_return)
+    {
+      coverage->calls++;
+      if (arc->src->count)
+	coverage->calls_executed++;
+    }
+  else if (!arc->is_unconditional)
+    {
+      coverage->branches++;
+      if (arc->src->count)
+	coverage->branches_executed++;
+      if (arc->count)
+	coverage->branches_taken++;
+    }
+}
+
+/* Format a HOST_WIDE_INT as either a percent ratio, or absolute
+   count.  If dp >= 0, format TOP/BOTTOM * 100 to DP decimal places.
+   If DP is zero, no decimal point is printed. Only print 100% when
+   TOP==BOTTOM and only print 0% when TOP=0.  If dp < 0, then simply
+   format TOP.  Return pointer to a static string.  */
+
+static char const *
+format_gcov (gcov_type top, gcov_type bottom, int dp)
+{
+  static char buffer[20];
+
+  if (dp >= 0)
+    {
+      float ratio = bottom ? (float)top / bottom : 0;
+      int ix;
+      unsigned limit = 100;
+      unsigned percent;
+
+      for (ix = dp; ix--; )
+	limit *= 10;
+
+      percent = (unsigned) (ratio * limit + (float)0.5);
+      if (percent <= 0 && top)
+	percent = 1;
+      else if (percent >= limit && top != bottom)
+	percent = limit - 1;
+      ix = sprintf (buffer, "%.*u%%", dp + 1, percent);
+      if (dp)
+	{
+	  dp++;
+	  do
+	    {
+	      buffer[ix+1] = buffer[ix];
+	      ix--;
+	    }
+	  while (dp--);
+	  buffer[ix + 1] = '.';
+	}
+    }
+  else
+    sprintf (buffer, HOST_WIDEST_INT_PRINT_DEC, (HOST_WIDEST_INT)top);
+
+  return buffer;
+}
+
+
+/* Output summary info for a function.  */
+
+static void
+function_summary (const coverage_t *coverage, const char *title)
+{
+  fnotice (stdout, "%s '%s'\n", title, coverage->name);
+
+  if (coverage->lines)
+    fnotice (stdout, "Lines executed:%s of %d\n",
+	     format_gcov (coverage->lines_executed, coverage->lines, 2),
+	     coverage->lines);
+  else
+    fnotice (stdout, "No executable lines\n");
+
+  if (flag_branches)
+    {
+      if (coverage->branches)
+	{
+	  fnotice (stdout, "Branches executed:%s of %d\n",
+		   format_gcov (coverage->branches_executed,
+				coverage->branches, 2),
+		   coverage->branches);
+	  fnotice (stdout, "Taken at least once:%s of %d\n",
+		   format_gcov (coverage->branches_taken,
+				coverage->branches, 2),
+		   coverage->branches);
+	}
+      else
+	fnotice (stdout, "No branches\n");
+      if (coverage->calls)
+	fnotice (stdout, "Calls executed:%s of %d\n",
+		 format_gcov (coverage->calls_executed, coverage->calls, 2),
+		 coverage->calls);
+      else
+	fnotice (stdout, "No calls\n");
+    }
+}
+
+/* Generate an output file name. LONG_OUTPUT_NAMES and PRESERVE_PATHS
+   affect name generation. With preserve_paths we create a filename
+   from all path components of the source file, replacing '/' with
+   '#', without it we simply take the basename component. With
+   long_output_names we prepend the processed name of the input file
+   to each output name (except when the current source file is the
+   input file, so you don't get a double concatenation). The two
+   components are separated by '##'. Also '.' filename components are
+   removed and '..'  components are renamed to '^'.  */
+
+static char *
+make_gcov_file_name (const char *input_name, const char *src_name)
+{
+  const char *cptr;
+  char *name;
+
+  if (flag_long_names && input_name && strcmp (src_name, input_name))
+    {
+      name = XNEWVEC (char, strlen (src_name) + strlen (input_name) + 10);
+      name[0] = 0;
+      /* Generate the input filename part.  */
+      cptr = flag_preserve_paths ? NULL : lbasename (input_name);
+      strcat (name, cptr ? cptr : input_name);
+      strcat (name, "##");
+    }
+  else
+    {
+      name = XNEWVEC (char, strlen (src_name) + 10);
+      name[0] = 0;
+    }
+
+  /* Generate the source filename part.  */
+
+  cptr = flag_preserve_paths ? NULL : lbasename (src_name);
+  strcat (name, cptr ? cptr : src_name);
+
+  if (flag_preserve_paths)
+    {
+      /* Convert '/' and '\' to '#', remove '/./', convert '/../' to '/^/',
+	 convert ':' to '~' on DOS based file system.  */
+      char *pnew = name, *pold = name;
+
+      /* First check for leading drive separator.  */
+
+      while (*pold != '\0')
+	{
+	  if (*pold == '/' || *pold == '\\')
+	    {
+	      *pnew++ = '#';
+	      pold++;
+	    }
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+	  else if (*pold == ':')
+	    {
+	      *pnew++ = '~';
+	      pold++;
+	    }
+#endif
+	  else if ((*pold == '/' && strstr (pold, "/./") == pold)
+		   || (*pold == '\\' && strstr (pold, "\\.\\") == pold))
+	      pold += 3;
+	  else if (*pold == '/' && strstr (pold, "/../") == pold)
+	    {
+	      strcpy (pnew, "/^/");
+	      pnew += 3;
+	      pold += 4;
+	    }
+	  else if (*pold == '\\' && strstr (pold, "\\..\\") == pold)
+	    {
+	      strcpy (pnew, "\\^\\");
+	      pnew += 3;
+	      pold += 4;
+	    }
+	  else
+	    *pnew++ = *pold++;
+	}
+
+      *pnew = '\0';
+    }
+
+  strcat (name, ".gcov");
+  return name;
+}
+
+/* Scan through the bb_data for each line in the block, increment
+   the line number execution count indicated by the execution count of
+   the appropriate basic block.  */
+
+static void
+add_line_counts (coverage_t *coverage, function_t *fn)
+{
+  unsigned ix;
+  line_t *line = NULL; /* This is propagated from one iteration to the
+			  next.  */
+
+  /* Scan each basic block.  */
+  for (ix = 0; ix != fn->num_blocks; ix++)
+    {
+      block_t *block = &fn->blocks[ix];
+      unsigned *encoding;
+      const source_t *src = NULL;
+      unsigned jx;
+
+      if (block->count && ix && ix + 1 != fn->num_blocks)
+	fn->blocks_executed++;
+      for (jx = 0, encoding = block->u.line.encoding;
+	   jx != block->u.line.num; jx++, encoding++)
+	if (!*encoding)
+	  {
+	    unsigned src_n = *++encoding;
+
+	    for (src = sources; src->index != src_n; src = src->next)
+	      continue;
+	    jx++;
+	  }
+	else
+	  {
+	    line = &src->lines[*encoding];
+
+	    if (coverage)
+	      {
+		if (!line->exists)
+		  coverage->lines++;
+		if (!line->count && block->count)
+		  coverage->lines_executed++;
+	      }
+	    line->exists = 1;
+	    line->count += block->count;
+	  }
+      free (block->u.line.encoding);
+      block->u.cycle.arc = NULL;
+      block->u.cycle.ident = ~0U;
+
+      if (!ix || ix + 1 == fn->num_blocks)
+	/* Entry or exit block */;
+      else if (flag_all_blocks)
+	{
+	  line_t *block_line = line ? line : &fn->src->lines[fn->line];
+
+	  block->chain = block_line->u.blocks;
+	  block_line->u.blocks = block;
+	}
+      else if (flag_branches)
+	{
+	  arc_t *arc;
+
+	  for (arc = block->succ; arc; arc = arc->succ_next)
+	    {
+	      arc->line_next = line->u.branches;
+	      line->u.branches = arc;
+	      if (coverage && !arc->is_unconditional)
+		add_branch_counts (coverage, arc);
+	    }
+	}
+    }
+  if (!line)
+    fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name);
+}
+
+/* Accumulate the line counts of a file.  */
+
+static void
+accumulate_line_counts (source_t *src)
+{
+  line_t *line;
+  function_t *fn, *fn_p, *fn_n;
+  unsigned ix;
+
+  /* Reverse the function order.  */
+  for (fn = src->functions, fn_p = NULL; fn;
+       fn_p = fn, fn = fn_n)
+    {
+      fn_n = fn->line_next;
+      fn->line_next = fn_p;
+    }
+  src->functions = fn_p;
+
+  for (ix = src->num_lines, line = src->lines; ix--; line++)
+    {
+      if (!flag_all_blocks)
+	{
+	  arc_t *arc, *arc_p, *arc_n;
+
+	  /* Total and reverse the branch information.  */
+	  for (arc = line->u.branches, arc_p = NULL; arc;
+	       arc_p = arc, arc = arc_n)
+	    {
+	      arc_n = arc->line_next;
+	      arc->line_next = arc_p;
+
+	      add_branch_counts (&src->coverage, arc);
+	    }
+	  line->u.branches = arc_p;
+	}
+      else if (line->u.blocks)
+	{
+	  /* The user expects the line count to be the number of times
+	     a line has been executed. Simply summing the block count
+	     will give an artificially high number.  The Right Thing
+	     is to sum the entry counts to the graph of blocks on this
+	     line, then find the elementary cycles of the local graph
+	     and add the transition counts of those cycles.  */
+	  block_t *block, *block_p, *block_n;
+	  gcov_type count = 0;
+
+	  /* Reverse the block information.  */
+	  for (block = line->u.blocks, block_p = NULL; block;
+	       block_p = block, block = block_n)
+	    {
+	      block_n = block->chain;
+	      block->chain = block_p;
+	      block->u.cycle.ident = ix;
+	    }
+	  line->u.blocks = block_p;
+
+	  /* Sum the entry arcs.  */
+	  for (block = line->u.blocks; block; block = block->chain)
+	    {
+	      arc_t *arc;
+
+	      for (arc = block->pred; arc; arc = arc->pred_next)
+		{
+		  if (arc->src->u.cycle.ident != ix)
+		    count += arc->count;
+		  if (flag_branches)
+		    add_branch_counts (&src->coverage, arc);
+		}
+
+	      /* Initialize the cs_count.  */
+	      for (arc = block->succ; arc; arc = arc->succ_next)
+		arc->cs_count = arc->count;
+	    }
+
+	  /* Find the loops. This uses the algorithm described in
+	     Tiernan 'An Efficient Search Algorithm to Find the
+	     Elementary Circuits of a Graph', CACM Dec 1970. We hold
+	     the P array by having each block point to the arc that
+	     connects to the previous block. The H array is implicitly
+	     held because of the arc ordering, and the block's
+	     previous arc pointer.
+
+	     Although the algorithm is O(N^3) for highly connected
+	     graphs, at worst we'll have O(N^2), as most blocks have
+	     only one or two exits. Most graphs will be small.
+
+	     For each loop we find, locate the arc with the smallest
+	     transition count, and add that to the cumulative
+	     count.  Decrease flow over the cycle and remove the arc
+	     from consideration.  */
+	  for (block = line->u.blocks; block; block = block->chain)
+	    {
+	      block_t *head = block;
+	      arc_t *arc;
+
+	    next_vertex:;
+	      arc = head->succ;
+	    current_vertex:;
+	      while (arc)
+		{
+		  block_t *dst = arc->dst;
+		  if (/* Already used that arc.  */
+		      arc->cycle
+		      /* Not to same graph, or before first vertex.  */
+		      || dst->u.cycle.ident != ix
+		      /* Already in path.  */
+		      || dst->u.cycle.arc)
+		    {
+		      arc = arc->succ_next;
+		      continue;
+		    }
+
+		  if (dst == block)
+		    {
+		      /* Found a closing arc.  */
+		      gcov_type cycle_count = arc->cs_count;
+		      arc_t *cycle_arc = arc;
+		      arc_t *probe_arc;
+
+		      /* Locate the smallest arc count of the loop.  */
+		      for (dst = head; (probe_arc = dst->u.cycle.arc);
+			   dst = probe_arc->src)
+			if (cycle_count > probe_arc->cs_count)
+			  {
+			    cycle_count = probe_arc->cs_count;
+			    cycle_arc = probe_arc;
+			  }
+
+		      count += cycle_count;
+		      cycle_arc->cycle = 1;
+
+		      /* Remove the flow from the cycle.  */
+		      arc->cs_count -= cycle_count;
+		      for (dst = head; (probe_arc = dst->u.cycle.arc);
+			   dst = probe_arc->src)
+			probe_arc->cs_count -= cycle_count;
+
+		      /* Unwind to the cyclic arc.  */
+		      while (head != cycle_arc->src)
+			{
+			  arc = head->u.cycle.arc;
+			  head->u.cycle.arc = NULL;
+			  head = arc->src;
+			}
+		      /* Move on.  */
+		      arc = arc->succ_next;
+		      continue;
+		    }
+
+		  /* Add new block to chain.  */
+		  dst->u.cycle.arc = arc;
+		  head = dst;
+		  goto next_vertex;
+		}
+	      /* We could not add another vertex to the path. Remove
+		 the last vertex from the list.  */
+	      arc = head->u.cycle.arc;
+	      if (arc)
+		{
+		  /* It was not the first vertex. Move onto next arc.  */
+		  head->u.cycle.arc = NULL;
+		  head = arc->src;
+		  arc = arc->succ_next;
+		  goto current_vertex;
+		}
+	      /* Mark this block as unusable.  */
+	      block->u.cycle.ident = ~0U;
+	    }
+
+	  line->count = count;
+	}
+
+      if (line->exists)
+	{
+	  src->coverage.lines++;
+	  if (line->count)
+	    src->coverage.lines_executed++;
+	}
+    }
+}
+
+/* Output information about ARC number IX.  Returns nonzero if
+   anything is output.  */
+
+static int
+output_branch_count (FILE *gcov_file, int ix, const arc_t *arc)
+{
+
+  if (arc->is_call_non_return)
+    {
+      if (arc->src->count)
+	{
+	  fnotice (gcov_file, "call   %2d returned %s\n", ix,
+		   format_gcov (arc->src->count - arc->count,
+				arc->src->count, -flag_counts));
+	}
+      else
+	fnotice (gcov_file, "call   %2d never executed\n", ix);
+    }
+  else if (!arc->is_unconditional)
+    {
+      if (arc->src->count)
+	fnotice (gcov_file, "branch %2d taken %s%s\n", ix,
+		 format_gcov (arc->count, arc->src->count, -flag_counts),
+		 arc->fall_through ? " (fallthrough)" : "");
+      else
+	fnotice (gcov_file, "branch %2d never executed\n", ix);
+    }
+  else if (flag_unconditional && !arc->dst->is_call_return)
+    {
+      if (arc->src->count)
+	fnotice (gcov_file, "unconditional %2d taken %s\n", ix,
+		 format_gcov (arc->count, arc->src->count, -flag_counts));
+      else
+	fnotice (gcov_file, "unconditional %2d never executed\n", ix);
+    }
+  else
+    return 0;
+  return 1;
+
+}
+
+/* Read in the source file one line at a time, and output that line to
+   the gcov file preceded by its execution count and other
+   information.  */
+
+static void
+output_lines (FILE *gcov_file, const source_t *src)
+{
+  FILE *source_file;
+  unsigned line_num;	/* current line number.  */
+  const line_t *line;           /* current line info ptr.  */
+  char string[STRING_SIZE];     /* line buffer.  */
+  char const *retval = "";	/* status of source file reading.  */
+  function_t *fn = NULL;
+
+  fprintf (gcov_file, "%9s:%5d:Source:%s\n", "-", 0, src->name);
+  if (!multiple_files)
+    {
+      fprintf (gcov_file, "%9s:%5d:Graph:%s\n", "-", 0, bbg_file_name);
+      fprintf (gcov_file, "%9s:%5d:Data:%s\n", "-", 0,
+	       no_data_file ? "-" : da_file_name);
+      fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0,
+	       object_summary.ctrs[GCOV_COUNTER_ARCS].runs);
+    }
+  fprintf (gcov_file, "%9s:%5d:Programs:%u\n", "-", 0, program_count);
+
+  source_file = fopen (src->name, "r");
+  if (!source_file)
+    {
+      fnotice (stderr, "%s:cannot open source file\n", src->name);
+      retval = NULL;
+    }
+  else if (src->file_time == 0)
+    fprintf (gcov_file, "%9s:%5d:Source is newer than graph\n", "-", 0);
+
+  if (flag_branches)
+    fn = src->functions;
+
+  for (line_num = 1, line = &src->lines[line_num];
+       line_num < src->num_lines; line_num++, line++)
+    {
+      for (; fn && fn->line == line_num; fn = fn->line_next)
+	{
+	  arc_t *arc = fn->blocks[fn->num_blocks - 1].pred;
+	  gcov_type return_count = fn->blocks[fn->num_blocks - 1].count;
+	  
+	  for (; arc; arc = arc->pred_next)
+	    if (arc->fake)
+	      return_count -= arc->count;
+	  
+	  fprintf (gcov_file, "function %s", fn->name);
+	  fprintf (gcov_file, " called %s",
+		   format_gcov (fn->blocks[0].count, 0, -1));
+	  fprintf (gcov_file, " returned %s",
+		   format_gcov (return_count, fn->blocks[0].count, 0));
+	  fprintf (gcov_file, " blocks executed %s",
+		   format_gcov (fn->blocks_executed, fn->num_blocks - 2, 0));
+	  fprintf (gcov_file, "\n");
+	}
+
+      /* For lines which don't exist in the .bb file, print '-' 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.  */
+      fprintf (gcov_file, "%9s:%5u:",
+	       !line->exists ? "-" : !line->count ? "#####"
+	       : format_gcov (line->count, 0, -1), line_num);
+
+      if (retval)
+	{
+	  /* Copy source line.  */
+	  do
+	    {
+	      retval = fgets (string, STRING_SIZE, source_file);
+	      if (!retval)
+		break;
+	      fputs (retval, gcov_file);
+	    }
+	  while (!retval[0] || retval[strlen (retval) - 1] != '\n');
+	}
+      if (!retval)
+	fputs ("/*EOF*/\n", gcov_file);
+
+      if (flag_all_blocks)
+	{
+	  block_t *block;
+	  arc_t *arc;
+	  int ix, jx;
+
+	  for (ix = jx = 0, block = line->u.blocks; block;
+	       block = block->chain)
+	    {
+	      if (!block->is_call_return)
+		fprintf (gcov_file, "%9s:%5u-block %2d\n",
+			 !line->exists ? "-" : !block->count ? "$$$$$"
+			 : format_gcov (block->count, 0, -1),
+			 line_num, ix++);
+	      if (flag_branches)
+		for (arc = block->succ; arc; arc = arc->succ_next)
+		  jx += output_branch_count (gcov_file, jx, arc);
+	    }
+	}
+      else if (flag_branches)
+	{
+	  int ix;
+	  arc_t *arc;
+
+	  for (ix = 0, arc = line->u.branches; arc; arc = arc->line_next)
+	    ix += output_branch_count (gcov_file, ix, arc);
+	}
+    }
+
+  /* Handle all remaining source lines.  There may be lines after the
+     last line of code.  */
+  if (retval)
+    {
+      for (; (retval = fgets (string, STRING_SIZE, source_file)); line_num++)
+	{
+	  fprintf (gcov_file, "%9s:%5u:%s", "-", line_num, retval);
+
+	  while (!retval[0] || retval[strlen (retval) - 1] != '\n')
+	    {
+	      retval = fgets (string, STRING_SIZE, source_file);
+	      if (!retval)
+		break;
+	      fputs (retval, gcov_file);
+	    }
+	}
+    }
+
+  if (source_file)
+    fclose (source_file);
+}