diff mbox series

[8/N,RFC] v2 GCOV: support multiple functions per a line

Message ID ad931bcd-afc5-e68c-efd9-f8fef69fabf6@suse.cz
State New
Headers show
Series [8/N,RFC] v2 GCOV: support multiple functions per a line | expand

Commit Message

Martin Liška Nov. 1, 2017, 8 a.m. UTC
Hi.

As shown from examples with -O2 optimization level, previous approach was not viable.
A more complex approach is necessary and can be shortly described as follows:

1) each function has assigned start and end line in a source file
2) group functions are these functions that begin on a line and there's another function
that begins on the same line (template initializations, deleting dtors, ...)
3) both source_file and function have 'vector<line_info> lines':
   a) source_file::lines represent all lines of non-group functions
   b) function_info::lines exists if a function is a group function and represent lines that belong to the function
4) function_info::lines for a group of functions are summed to source_file::lines; that makes function
   output_lines easier
5) branches, calls and blocks are printed per function in a group, not to 'global scope' lines
6) DECL_ARTIFICIAL functions are ignored

There are 2 examples that illustrate the approach:

        1:    7:class ClassB
        -:    8:{
        -:    9:public:
        -:   10:  virtual ~ClassB ();
        -:   11:};
        -:   12:
        3:   13:ClassB::~ClassB ()
        -:   14:{
        1:   15:  int r = inline_me (glob);
        1:   16:  std::cout << "Bey, ClassB!" << r << std::endl;
        2:   17:}
------------------
_ZN6ClassBD0Ev:
        1:   13:ClassB::~ClassB ()
        -:   14:{
        -:   15:  int r = inline_me (glob);
        -:   16:  std::cout << "Bey, ClassB!" << r << std::endl;
        1:   17:}
------------------
_ZN6ClassBD2Ev:
        2:   13:ClassB::~ClassB ()
        -:   14:{
        1:   15:  int r = inline_me (glob);
        1:   16:  std::cout << "Bey, ClassB!" << r << std::endl;
        1:   17:}
------------------
        -:   18:
        -:   19:int
        1:   20:main (int argc, char **argv)
        -:   21:{
        1:   22:  ClassB *pObjB = new ClassB ();
        1:   23:  delete pObjB;
        -:   24:
        1:   25:  if (argc == 123)
    #####:   26:    return 1;
        -:   27:
        -:   28:  return 0;
        -:   29:}
        -:   30:
        -:   31:
        -:   32:static int
    #####:   33:inline_me(int a)
        -:   34:{
       1*:   35:  return a * a;
        -:   36:}

It's compiled with -O2 and it shows that ZN6ClassBD2Ev function executes also line 35, but the line is not counted
to function_info::lines as it does not live in function scope. Note that line 35 is marked with '*'. That's because
I used -fkeep-inline-functions.

        -:    0:Source:test.cpp
        -:    0:Graph:test.gcno
        -:    0:Data:test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:template<class T>
        -:    2:class Foo
        -:    3:{
        -:    4:  public:
        3:    5:  Foo()
        -:    6:  {
        3:    7:    b = 123;
        3:    8:  }
------------------
Foo<char>::Foo():
    #####:    5:  Foo()
        -:    6:  {
    #####:    7:    b = 123;
    #####:    8:  }
------------------
Foo<int>::Foo():
        1:    5:  Foo()
        -:    6:  {
        1:    7:    b = 123;
        1:    8:  }
------------------
Foo<float>::Foo():
        2:    5:  Foo()
        -:    6:  {
        2:    7:    b = 123;
        2:    8:  }
------------------
        -:    9:
        1:   10:  void test() { if (!b) __builtin_abort (); b = 111; }
------------------
Foo<char>::test():
    #####:   10:  void test() { if (!b) __builtin_abort (); b = 111; }
    %%%%%:   10-block  0
    %%%%%:   10-block  1
------------------
Foo<int>::test():
       1*:   10:  void test() { if (!b) __builtin_abort (); b = 111; }
        1:   10-block  0
    %%%%%:   10-block  1
------------------
Foo<float>::test():
    #####:   10:  void test() { if (!b) __builtin_abort (); b = 111; }
    %%%%%:   10-block  0
    %%%%%:   10-block  1
------------------
        -:   11:
        -:   12:  private:
        -:   13:  int b;
        -:   14:};
        -:   15:
        -:   16:template class Foo<float>;
        -:   17:template class Foo<int>;
        -:   18:template class Foo<char>;
        -:   19:
        1:   20:int main()
        -:   21:{
        1:   22:  Foo<int> xx;
        1:   22-block  0
        1:   23:  Foo<float> yy;
        1:   23-block  0
        1:   24:  Foo<float> zz;
        1:   24-block  0
        1:   25:  xx.test();
        1:   25-block  0
        -:   26:
        1:   27:  return 0;
        1:   27-block  0
        -:   28:}

This sample is illustration of how we aggregate statistics.

I've added changelog entry and I'm still planning to enhance documentation, add
some tests (which would be tricky) and small follow-up clean-up is planned.
In meantime I would appreciate a feedback.

Thanks,
Martin

Comments

Martin Liška Nov. 7, 2017, 10:53 a.m. UTC | #1
Hello.

This is slightly updated version from the previous. Various small issues were fixed
and I update documentation in order to reflect the changes.

Ready to be installed?
Thanks,
Martin
From 5af7859e0d58d460f37c0b607adbf3c9ca927025 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 26 Oct 2017 10:39:40 +0200
Subject: [PATCH 1/8] GCOV: support multiple functions per a line

gcc/ChangeLog:

2017-11-01  Martin Liska  <mliska@suse.cz>

	* coverage.c (coverage_begin_function): Output also end locus
	of a function and information whether the function is
	artificial.
	* gcov-dump.c (tag_function): Parse and print the information.
	* gcov.c (INCLUDE_MAP): Add include.
	(INCLUDE_SET): Likewise.
	(struct line_info): Move earlier in the source file because
	of vector<line_info> in function_info structure.
	(line_info::line_info): Likewise.
	(line_info::has_block): Likewise.
	(struct source_info): Add new member index.
	(source_info::get_functions_at_location): New function.
	(function_info::group_line_p): New function.
	(output_intermediate_line): New function.
	(output_intermediate_file): Use the mentioned function.
	(struct function_start): New.
	(struct function_start_pair_hash): Likewise.
	(process_file): Add code that identifies group functions.
	Assign lines either to global or function scope.
	(generate_results): Skip artificial functions.
	(find_source): Assign index for each source file.
	(read_graph_file): Read new flag artificial and end_line.
	(add_line_counts): Assign it either to global of function scope.
	(accumulate_line_counts): Isolate core of the function to
	accumulate_line_info and call it for both function and global
	scope lines.
	(accumulate_line_info): New function.
	(output_line_beginning): Fix GNU coding style.
	(print_source_line): New function.
	(output_line_details): Likewise.
	(output_function_details): Likewise.
	(output_lines): Iterate both source (global) scope and function
	scope.
	(struct function_line_start_cmp): New class.
	* doc/gcov.texi: Reflect changes in documentation.
---
 gcc/coverage.c    |   2 +
 gcc/doc/gcov.texi | 329 +++++++++++++++------
 gcc/gcov-dump.c   |   7 +-
 gcc/gcov.c        | 847 +++++++++++++++++++++++++++++++++++++-----------------
 4 files changed, 829 insertions(+), 356 deletions(-)

diff --git a/gcc/coverage.c b/gcc/coverage.c
index 8a56a677f15..8ac593920b6 100644
--- a/gcc/coverage.c
+++ b/gcc/coverage.c
@@ -663,8 +663,10 @@ coverage_begin_function (unsigned lineno_checksum, unsigned cfg_checksum)
   gcov_write_unsigned (cfg_checksum);
   gcov_write_string (IDENTIFIER_POINTER
 		     (DECL_ASSEMBLER_NAME (current_function_decl)));
+  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
   gcov_write_filename (xloc.file);
   gcov_write_unsigned (xloc.line);
+  gcov_write_unsigned (expand_location (cfun->function_end_locus).line);
   gcov_write_length (offset);
 
   return !gcov_is_error ();
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index 5c4ba8a51a7..6f6a92c131a 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -193,7 +193,7 @@ Write counts in human readable format (like 24k).
 
 @smallexample
 file:@var{source_file_name}
-function:@var{line_number},@var{execution_count},@var{function_name}
+function:@var{start_line_number},@var{end_line_number},@var{execution_count},@var{function_name}
 lcount:@var{line number},@var{execution_count},@var{has_unexecuted_block}
 branch:@var{line_number},@var{branch_coverage_type}
 
@@ -201,24 +201,55 @@ Where the @var{branch_coverage_type} is
    notexec (Branch not executed)
    taken (Branch executed and taken)
    nottaken (Branch executed, but not taken)
+@end smallexample
 
 There can be multiple @var{file} entries in an intermediate gcov
 file. All entries following a @var{file} pertain to that source file
-until the next @var{file} entry.
-@end smallexample
+until the next @var{file} entry.  If there are multiple functions that
+start on a single line, then corresponding lcount is repeated multiple
+times.
 
 Here is a sample when @option{-i} is used in conjunction with @option{-b} option:
 
 @smallexample
-file:array.cc
-function:11,1,_Z3sumRKSt6vectorIPiSaIS0_EE
-function:22,1,main
-lcount:11,1,0
-lcount:12,1,0
-lcount:14,1,0
-branch:14,taken
-lcount:26,1,0
-branch:28,nottaken
+file:tmp.cpp
+function:7,7,0,_ZN3FooIcEC2Ev
+function:7,7,1,_ZN3FooIiEC2Ev
+function:8,8,0,_ZN3FooIcE3incEv
+function:8,8,2,_ZN3FooIiE3incEv
+function:18,37,1,main
+lcount:7,0,1
+lcount:7,1,0
+lcount:8,0,1
+lcount:8,2,0
+lcount:18,1,0
+lcount:21,1,0
+branch:21,taken
+branch:21,nottaken
+lcount:23,1,0
+branch:23,taken
+branch:23,nottaken
+lcount:24,1,0
+branch:24,taken
+branch:24,nottaken
+lcount:25,1,0
+lcount:27,11,0
+branch:27,taken
+branch:27,taken
+lcount:28,10,0
+lcount:30,1,1
+branch:30,nottaken
+branch:30,taken
+lcount:32,1,0
+branch:32,nottaken
+branch:32,taken
+lcount:33,0,1
+branch:33,notexec
+branch:33,notexec
+lcount:35,1,0
+branch:35,taken
+branch:35,nottaken
+lcount:36,1,0
 @end smallexample
 
 @item -k
@@ -391,79 +422,158 @@ source file compiled with @option{-fprofile-arcs}, an accompanying
 
 Running @command{gcov} with your program's source file names as arguments
 will now produce a listing of the code along with frequency of execution
-for each line.  For example, if your program is called @file{tmp.c}, this
+for each line.  For example, if your program is called @file{tmp.cpp}, this
 is what you see when you use the basic @command{gcov} facility:
 
 @smallexample
-$ gcc -fprofile-arcs -ftest-coverage tmp.c
+$ g++ -fprofile-arcs -ftest-coverage tmp.cpp
 $ a.out
-$ gcov tmp.c
-File 'tmp.c'
-Lines executed:90.00% of 10
-Creating 'tmp.c.gcov'
+$ gcov tmp.cpp -m
+File 'tmp.cpp'
+Lines executed:92.86% of 14
+Creating 'tmp.cpp.gcov'
 @end smallexample
 
-The file @file{tmp.c.gcov} contains output from @command{gcov}.
+The file @file{tmp.cpp.gcov} contains output from @command{gcov}.
 Here is a sample:
 
 @smallexample
-        -:    0:Source:tmp.c
+        -:    0:Source:tmp.cpp
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
-        -:    3:int main (void)
-        1:    4:@{
-        1:    5:  int i, total;
-        -:    6:
-        1:    7:  total = 0;
-        -:    8:
-       11:    9:  for (i = 0; i < 10; i++)
-       10:   10:    total += i;
-        -:   11:
-        1:   12:  if (total != 45)
-    #####:   13:    printf ("Failure\n");
-        -:   14:  else
-        1:   15:    printf ("Success\n");
-        1:   16:  return 0;
-        -:   17:@}
+        -:    3:template<class T>
+        -:    4:class Foo
+        -:    5:@{
+        -:    6:  public:
+       1*:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<char>::Foo():
+    #####:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<int>::Foo():
+        1:    7:  Foo(): b (1000) @{@}
+------------------
+       2*:    8:  void inc () @{ b++; @}
+------------------
+Foo<char>::inc():
+    #####:    8:  void inc () @{ b++; @}
+------------------
+Foo<int>::inc():
+        2:    8:  void inc () @{ b++; @}
+------------------
+        -:    9:
+        -:   10:  private:
+        -:   11:  int b;
+        -:   12:@};
+        -:   13:
+        -:   14:template class Foo<int>;
+        -:   15:template class Foo<char>;
+        -:   16:
+        -:   17:int
+        1:   18:main (void)
+        -:   19:@{
+        -:   20:  int i, total;
+        1:   21:  Foo<int> counter;
+        -:   22:
+        1:   23:  counter.inc();
+        1:   24:  counter.inc();
+        1:   25:  total = 0;
+        -:   26:
+       11:   27:  for (i = 0; i < 10; i++)
+       10:   28:    total += i;
+        -:   29:
+       1*:   30:  int v = total > 100 ? 1 : 2;
+        -:   31:
+        1:   32:  if (total != 45)
+    #####:   33:    printf ("Failure\n");
+        -:   34:  else
+        1:   35:    printf ("Success\n");
+        1:   36:  return 0;
+        -:   37:@}
 @end smallexample
 
+Note that line 7 is shown in the report multiple times.  First occurrence
+presents total number of execution of the line and the next two belong
+to instances of class Foo constructors.  As you can also see, line 30 contains
+some unexecuted basic blocks and thus execution count has asterisk symbol.
+
 When you use the @option{-a} option, you will get individual block
 counts, and the output looks like this:
 
 @smallexample
-        -:    0:Source:tmp.c
+        -:    0:Source:tmp.cpp
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
-        -:    3:int main (void)
-        1:    4:@{
-        1:    4-block  0
-        1:    5:  int i, total;
-        -:    6:
-        1:    7:  total = 0;
-        -:    8:
-       11:    9:  for (i = 0; i < 10; i++)
-       11:    9-block  0
-       10:   10:    total += i;
-       10:   10-block  0
-        -:   11:
-        1:   12:  if (total != 45)
-        1:   12-block  0
-    #####:   13:    printf ("Failure\n");
-    $$$$$:   13-block  0
-        -:   14:  else
-        1:   15:    printf ("Success\n");
-        1:   15-block  0
-        1:   16:  return 0;
-        1:   16-block  0
-        -:   17:@}
+        -:    3:template<class T>
+        -:    4:class Foo
+        -:    5:@{
+        -:    6:  public:
+       1*:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<char>::Foo():
+    #####:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<int>::Foo():
+        1:    7:  Foo(): b (1000) @{@}
+------------------
+       2*:    8:  void inc () @{ b++; @}
+------------------
+Foo<char>::inc():
+    #####:    8:  void inc () @{ b++; @}
+------------------
+Foo<int>::inc():
+        2:    8:  void inc () @{ b++; @}
+------------------
+        -:    9:
+        -:   10:  private:
+        -:   11:  int b;
+        -:   12:@};
+        -:   13:
+        -:   14:template class Foo<int>;
+        -:   15:template class Foo<char>;
+        -:   16:
+        -:   17:int
+        1:   18:main (void)
+        -:   19:@{
+        -:   20:  int i, total;
+        1:   21:  Foo<int> counter;
+        1:   21-block  0
+        -:   22:
+        1:   23:  counter.inc();
+        1:   23-block  0
+        1:   24:  counter.inc();
+        1:   24-block  0
+        1:   25:  total = 0;
+        -:   26:
+       11:   27:  for (i = 0; i < 10; i++)
+        1:   27-block  0
+       11:   27-block  1
+       10:   28:    total += i;
+       10:   28-block  0
+        -:   29:
+       1*:   30:  int v = total > 100 ? 1 : 2;
+        1:   30-block  0
+    %%%%%:   30-block  1
+        1:   30-block  2
+        -:   31:
+        1:   32:  if (total != 45)
+        1:   32-block  0
+    #####:   33:    printf ("Failure\n");
+    %%%%%:   33-block  0
+        -:   34:  else
+        1:   35:    printf ("Success\n");
+        1:   35-block  0
+        1:   36:  return 0;
+        1:   36-block  0
+        -:   37:@}
 @end smallexample
 
 In this mode, each basic block is only shown on one line -- the last
@@ -477,53 +587,94 @@ block, the branch and call counts of the block will be shown, if the
 
 Because of the way GCC instruments calls, a call count can be shown
 after a line with no individual blocks.
-As you can see, line 13 contains a basic block that was not executed.
+As you can see, line 33 contains a basic block that was not executed.
 
 @need 450
 When you use the @option{-b} option, your output looks like this:
 
 @smallexample
-$ gcov -b tmp.c
-File 'tmp.c'
-Lines executed:90.00% of 10
-Branches executed:80.00% of 5
-Taken at least once:80.00% of 5
-Calls executed:50.00% of 2
-Creating 'tmp.c.gcov'
-@end smallexample
-
-Here is a sample of a resulting @file{tmp.c.gcov} file:
-
-@smallexample
-        -:    0:Source:tmp.c
+        -:    0:Source:tmp.cpp
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
-        -:    3:int main (void)
-function main called 1 returned 1 blocks executed 75%
-        1:    4:@{
-        1:    5:  int i, total;
-        -:    6:
-        1:    7:  total = 0;
-        -:    8:
-       11:    9:  for (i = 0; i < 10; i++)
+        -:    3:template<class T>
+        -:    4:class Foo
+        -:    5:@{
+        -:    6:  public:
+       1*:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<char>::Foo():
+function Foo<char>::Foo() called 0 returned 0% blocks executed 0%
+    #####:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<int>::Foo():
+function Foo<int>::Foo() called 1 returned 100% blocks executed 100%
+        1:    7:  Foo(): b (1000) @{@}
+------------------
+       2*:    8:  void inc () @{ b++; @}
+------------------
+Foo<char>::inc():
+function Foo<char>::inc() called 0 returned 0% blocks executed 0%
+    #####:    8:  void inc () @{ b++; @}
+------------------
+Foo<int>::inc():
+function Foo<int>::inc() called 2 returned 100% blocks executed 100%
+        2:    8:  void inc () @{ b++; @}
+------------------
+        -:    9:
+        -:   10:  private:
+        -:   11:  int b;
+        -:   12:@};
+        -:   13:
+        -:   14:template class Foo<int>;
+        -:   15:template class Foo<char>;
+        -:   16:
+        -:   17:int
+function main called 1 returned 100% blocks executed 81%
+        1:   18:main (void)
+        -:   19:@{
+        -:   20:  int i, total;
+        1:   21:  Foo<int> counter;
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        -:   22:
+        1:   23:  counter.inc();
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        1:   24:  counter.inc();
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        1:   25:  total = 0;
+        -:   26:
+       11:   27:  for (i = 0; i < 10; i++)
 branch  0 taken 91% (fallthrough)
 branch  1 taken 9%
-       10:   10:    total += i;
-        -:   11:
-        1:   12:  if (total != 45)
+       10:   28:    total += i;
+        -:   29:
+       1*:   30:  int v = total > 100 ? 1 : 2;
+branch  0 taken 0% (fallthrough)
+branch  1 taken 100%
+        -:   31:
+        1:   32:  if (total != 45)
 branch  0 taken 0% (fallthrough)
 branch  1 taken 100%
-    #####:   13:    printf ("Failure\n");
+    #####:   33:    printf ("Failure\n");
 call    0 never executed
-        -:   14:  else
-        1:   15:    printf ("Success\n");
-call    0 called 1 returned 100%
-        1:   16:  return 0;
-        -:   17:@}
+branch  1 never executed
+branch  2 never executed
+        -:   34:  else
+        1:   35:    printf ("Success\n");
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        1:   36:  return 0;
+        -:   37:@}
 @end smallexample
 
 For each function, a line is printed showing how many times the function
diff --git a/gcc/gcov-dump.c b/gcc/gcov-dump.c
index d24e72ac4a1..22d08c04c1e 100644
--- a/gcc/gcov-dump.c
+++ b/gcc/gcov-dump.c
@@ -308,9 +308,14 @@ tag_function (const char *filename ATTRIBUTE_UNUSED,
 	  
 	  name = gcov_read_string ();
 	  printf (", `%s'", name ? name : "NULL");
+	  unsigned artificial = gcov_read_unsigned ();
 	  name = gcov_read_string ();
 	  printf (" %s", name ? name : "NULL");
-	  printf (":%u", gcov_read_unsigned ());
+	  unsigned line_start = gcov_read_unsigned ();
+	  unsigned line_end = gcov_read_unsigned ();
+	  printf (":%u:%u", line_start, line_end);
+	  if (artificial)
+	    printf (", artificial");
 	}
     }
 }
diff --git a/gcc/gcov.c b/gcc/gcov.c
index 48bcdc0d4c3..16fc456c632 100644
--- a/gcc/gcov.c
+++ b/gcc/gcov.c
@@ -34,6 +34,8 @@ along with Gcov; see the file COPYING3.  If not see
 #define INCLUDE_ALGORITHM
 #define INCLUDE_VECTOR
 #define INCLUDE_STRING
+#define INCLUDE_MAP
+#define INCLUDE_SET
 #include "system.h"
 #include "coretypes.h"
 #include "tm.h"
@@ -183,6 +185,42 @@ block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0),
   cycle.arc = NULL;
 }
 
+/* Describes a single line of source.  Contains a chain of basic blocks
+   with code on it.  */
+
+struct line_info
+{
+  /* Default constructor.  */
+  line_info ();
+
+  /* Return true when NEEDLE is one of basic blocks the line belongs to.  */
+  bool has_block (block_t *needle);
+
+  /* Execution count.  */
+  gcov_type count;
+
+  /* Branches from blocks that end on this line.  */
+  vector<arc_t *> branches;
+
+  /* blocks which start on this line.  Used in all-blocks mode.  */
+  vector<block_t *> blocks;
+
+  unsigned exists : 1;
+  unsigned unexceptional : 1;
+  unsigned has_unexecuted_block : 1;
+};
+
+line_info::line_info (): count (0), branches (), blocks (), exists (false),
+  unexceptional (0), has_unexecuted_block (0)
+{
+}
+
+bool
+line_info::has_block (block_t *needle)
+{
+  return std::find (blocks.begin (), blocks.end (), needle) != blocks.end ();
+}
+
 /* Describes a single function. Contains an array of basic blocks.  */
 
 typedef struct function_info
@@ -190,6 +228,10 @@ typedef struct function_info
   function_info ();
   ~function_info ();
 
+  /* Return true when line N belongs to the function in source file SRC_IDX.
+     The line must be defined in body of the function, can't be inlined.  */
+  bool group_line_p (unsigned n, unsigned src_idx);
+
   /* Name of function.  */
   char *name;
   char *demangled_name;
@@ -200,6 +242,13 @@ typedef struct function_info
   /* The graph contains at least one fake incoming edge.  */
   unsigned has_catch : 1;
 
+  /* True when the function is artificial and does not exist
+     in a source file.  */
+  unsigned artificial : 1;
+
+  /* True when multiple functions start at a line in a source file.  */
+  unsigned is_group : 1;
+
   /* Array of basic blocks.  Like in GCC, the entry block is
      at blocks[0] and the exit block is at blocks[1].  */
 #define ENTRY_BLOCK (0)
@@ -211,17 +260,34 @@ typedef struct function_info
   gcov_type *counts;
   unsigned num_counts;
 
-  /* First line number & file.  */
-  unsigned line;
+  /* First line number.  */
+  unsigned start_line;
+
+  /* Last line number.  */
+  unsigned end_line;
+
+  /* Index of source file where the function is defined.  */
   unsigned src;
 
-  /* Next function in same source file.  */
-  struct function_info *next_file_fn;
+  /* Vector of line information.  */
+  vector<line_info> lines;
 
   /* Next function.  */
   struct function_info *next;
 } function_t;
 
+/* Function info comparer that will sort functions according to starting
+   line.  */
+
+struct function_line_start_cmp
+{
+  inline bool operator() (const function_info *lhs,
+			  const function_info *rhs)
+    {
+      return lhs->end_line < rhs->start_line;
+    }
+};
+
 /* Describes coverage of a file or function.  */
 
 typedef struct coverage_info
@@ -239,42 +305,6 @@ typedef struct coverage_info
   char *name;
 } coverage_t;
 
-/* Describes a single line of source. Contains a chain of basic blocks
-   with code on it.  */
-
-struct line_info
-{
-  /* Default constructor.  */
-  line_info ();
-
-  /* Return true when NEEDLE is one of basic blocks the line belongs to.  */
-  bool has_block (block_t *needle);
-
-  /* Execution count.  */
-  gcov_type count;
-
-  /* Branches from blocks that end on this line.  */
-  vector<arc_t *> branches;
-
-  /* blocks which start on this line.  Used in all-blocks mode.  */
-  vector<block_t *> blocks;
-
-  unsigned exists : 1;
-  unsigned unexceptional : 1;
-  unsigned has_unexecuted_block : 1;
-};
-
-line_info::line_info (): count (0), branches (), blocks (), exists (false),
-  unexceptional (0), has_unexecuted_block (0)
-{
-}
-
-bool
-line_info::has_block (block_t *needle)
-{
-  return std::find (blocks.begin (), blocks.end (), needle) != blocks.end ();
-}
-
 /* Describes a file mentioned in the block graph.  Contains an array
    of line info.  */
 
@@ -283,6 +313,11 @@ struct source_info
   /* Default constructor.  */
   source_info ();
 
+  vector<function_t *> get_functions_at_location (unsigned line_num) const;
+
+  /* Index of the source_info in sources vector.  */
+  unsigned index;
+
   /* Canonical name of source file.  */
   char *name;
   time_t file_time;
@@ -294,14 +329,29 @@ struct source_info
 
   /* Functions in this source file.  These are in ascending line
      number order.  */
-  function_t *functions;
+  vector <function_t *> functions;
 };
 
-source_info::source_info (): name (NULL), file_time (), lines (),
-  coverage (), functions (NULL)
+source_info::source_info (): name (NULL), file_time (),
+  lines (), coverage (), functions ()
 {
 }
 
+vector<function_t *>
+source_info::get_functions_at_location (unsigned line_num) const
+{
+  vector<function_t *> r;
+
+  for (vector<function_t *>::const_iterator it = functions.begin ();
+       it != functions.end (); it++)
+    {
+      if ((*it)->start_line == line_num && (*it)->src == index)
+	r.push_back (*it);
+    }
+
+  return r;
+}
+
 class name_map
 {
 public:
@@ -495,8 +545,9 @@ extern int main (int, char **);
 
 function_info::function_info (): name (NULL), demangled_name (NULL),
   ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0),
+  artificial (0), is_group (0),
   blocks (), blocks_executed (0), counts (NULL), num_counts (0),
-  line (0), src (0), next_file_fn (NULL), next (NULL)
+  start_line (0), end_line (0), src (0), lines (), next (NULL)
 {
 }
 
@@ -518,6 +569,11 @@ function_info::~function_info ()
   free (name);
 }
 
+bool function_info::group_line_p (unsigned n, unsigned src_idx)
+{
+  return is_group && src == src_idx && start_line <= n && n <= end_line;
+}
+
 /* Cycle detection!
    There are a bajillion algorithms that do this.  Boost's function is named
    hawick_cycles, so I used the algorithm by K. A. Hawick and H. A. James in
@@ -889,6 +945,42 @@ process_args (int argc, char **argv)
   return optind;
 }
 
+/* Output intermediate LINE sitting on LINE_NUM to output file F.  */
+
+static void
+output_intermediate_line (FILE *f, line_info *line, unsigned line_num)
+{
+  if (!line->exists)
+    return;
+
+  fprintf (f, "lcount:%u,%s,%d\n", line_num,
+	   format_gcov (line->count, 0, -1),
+	   line->has_unexecuted_block);
+
+  vector<arc_t *>::const_iterator it;
+  if (flag_branches)
+    for (it = line->branches.begin (); it != line->branches.end ();
+	 it++)
+      {
+	if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
+	  {
+	    const char *branch_type;
+	    /* branch:<line_num>,<branch_coverage_type>
+	       branch_coverage_type
+	       : notexec (Branch not executed)
+	       : taken (Branch executed and taken)
+	       : nottaken (Branch executed, but not taken)
+	       */
+	    if ((*it)->src->count)
+		 branch_type
+			= ((*it)->count > 0) ? "taken" : "nottaken";
+	    else
+	      branch_type = "notexec";
+	    fprintf (f, "branch:%d,%s\n", line_num, branch_type);
+	  }
+      }
+}
+
 /* Output the result in intermediate format used by 'lcov'.
 
 The intermediate format contains a single file named 'foo.cc.gcov',
@@ -902,50 +994,96 @@ file 'foo.cc.gcov' similar to the above example. */
 static void
 output_intermediate_file (FILE *gcov_file, source_info *src)
 {
-  unsigned line_num;    /* current line number.  */
-  const line_info *line;   /* current line info ptr.  */
-  function_t *fn;       /* current function info ptr. */
-
   fprintf (gcov_file, "file:%s\n", src->name);    /* source file name */
 
-  for (fn = src->functions; fn; fn = fn->next_file_fn)
+  std::sort (src->functions.begin (), src->functions.end (),
+	     function_line_start_cmp ());
+  for (vector<function_t *>::iterator it = src->functions.begin ();
+       it != src->functions.end (); it++)
     {
       /* function:<name>,<line_number>,<execution_count> */
-      fprintf (gcov_file, "function:%d,%s,%s\n", fn->line,
-	       format_gcov (fn->blocks[0].count, 0, -1),
-	       flag_demangled_names ? fn->demangled_name : fn->name);
+      fprintf (gcov_file, "function:%d,%d,%s,%s\n", (*it)->start_line,
+	       (*it)->end_line, format_gcov ((*it)->blocks[0].count, 0, -1),
+	       flag_demangled_names ? (*it)->demangled_name : (*it)->name);
     }
 
-  for (line_num = 1, line = &src->lines[line_num];
-       line_num < src->lines.size ();
-       line_num++, line++)
+  for (unsigned line_num = 0; line_num <= src->lines.size (); line_num++)
     {
-      if (line->exists)
-	fprintf (gcov_file, "lcount:%u,%s,%d\n", line_num,
-		 format_gcov (line->count, 0, -1), line->has_unexecuted_block);
-      if (flag_branches)
-	for (vector<arc_t *>::const_iterator it = line->branches.begin ();
-	     it != line->branches.end (); it++)
-	  {
-	    if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
-	      {
-		const char *branch_type;
-		/* branch:<line_num>,<branch_coverage_type>
-		   branch_coverage_type
-		     : notexec (Branch not executed)
-		     : taken (Branch executed and taken)
-		     : nottaken (Branch executed, but not taken)
-		*/
-		if ((*it)->src->count)
-		  branch_type = ((*it)->count > 0) ? "taken" : "nottaken";
-		else
-		  branch_type = "notexec";
-		fprintf (gcov_file, "branch:%d,%s\n", line_num, branch_type);
-	      }
-	  }
+      vector<function_t *> fns = src->get_functions_at_location (line_num);
+
+      /* Print first group functions that begin on the line.  */
+      for (vector<function_t *>::iterator it2 = fns.begin ();
+	   it2 != fns.end (); it2++)
+	{
+	  vector<line_info> &lines = (*it2)->lines;
+	  /* Print all lines covered by the function.  */
+	  for (unsigned i = 0; i < lines.size (); i++)
+	    {
+	      line_info *line = &lines[i];
+	      output_intermediate_line (gcov_file, line, line_num + i);
+	    }
+	}
+
+      /* Follow with lines associated with the source file.  */
+      output_intermediate_line (gcov_file, &src->lines[line_num], line_num);
     }
 }
 
+/* Function start pair.  */
+struct function_start
+{
+  unsigned source_file_idx;
+  unsigned start_line;
+};
+
+/* Traits class for function start hash maps below.  */
+
+struct function_start_pair_hash : typed_noop_remove <function_start>
+{
+  typedef function_start value_type;
+  typedef function_start compare_type;
+
+  static hashval_t
+  hash (const function_start &ref)
+  {
+    inchash::hash hstate (0);
+    hstate.add_int (ref.source_file_idx);
+    hstate.add_int (ref.start_line);
+    return hstate.end ();
+  }
+
+  static bool
+  equal (const function_start &ref1, const function_start &ref2)
+  {
+    return (ref1.source_file_idx == ref2.source_file_idx
+	    && ref1.start_line == ref2.start_line);
+  }
+
+  static void
+  mark_deleted (function_start &ref)
+  {
+    ref.start_line = ~1U;
+  }
+
+  static void
+  mark_empty (function_start &ref)
+  {
+    ref.start_line = ~2U;
+  }
+
+  static bool
+  is_deleted (const function_start &ref)
+  {
+    return ref.start_line == ~1U;
+  }
+
+  static bool
+  is_empty (const function_start &ref)
+  {
+    return ref.start_line == ~2U;
+  }
+};
+
 /* Process a single input file.  */
 
 static void
@@ -959,6 +1097,28 @@ process_file (const char *file_name)
     return;
 
   read_count_file (fns);
+
+  hash_map<function_start_pair_hash, function_t *> fn_map;
+
+  /* Identify group functions.  */
+  for (function_t *f = fns; f; f = f->next)
+    if (!f->artificial)
+      {
+	function_start needle;
+	needle.source_file_idx = f->src;
+	needle.start_line = f->start_line;
+
+	function_t **slot = fn_map.get (needle);
+	if (slot)
+	  {
+	    gcc_assert ((*slot)->end_line == f->end_line);
+	    (*slot)->is_group = 1;
+	    f->is_group = 1;
+	  }
+	else
+	  fn_map.put (needle, f);
+      }
+
   while (fns)
     {
       function_t *fn = fns;
@@ -968,46 +1128,50 @@ process_file (const char *file_name)
       if (fn->counts || no_data_file)
 	{
 	  unsigned src = fn->src;
-	  unsigned line = fn->line;
 	  unsigned block_no;
-	  function_t *probe, **prev;
-
-	  /* 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.  Note we're
-	     building this list in reverse order.  */
-	  for (prev = &sources[src].functions;
-	       (probe = *prev); prev = &probe->next_file_fn)
-	    if (probe->line <= line)
-	      break;
-	  fn->next_file_fn = probe;
-	  *prev = fn;
 
-	  /* Mark last line in files touched by function.  */
-	  for (block_no = 0; block_no != fn->blocks.size (); block_no++)
+	  /* Process only non-artificial functions.  */
+	  if (!fn->artificial)
 	    {
-	      block_t *block = &fn->blocks[block_no];
-	      for (unsigned i = 0; i < block->locations.size (); i++)
-		{
-		  unsigned s = block->locations[i].source_file_idx;
-
-		  /* Sort lines of locations.  */
-		  sort (block->locations[i].lines.begin (),
-			block->locations[i].lines.end ());
+	      source_info *s = &sources[src];
+	      s->functions.push_back (fn);
 
-		  if (!block->locations[i].lines.empty ())
+	      /* Mark last line in files touched by function.  */
+	      for (block_no = 0; block_no != fn->blocks.size (); block_no++)
+		{
+		  block_t *block = &fn->blocks[block_no];
+		  for (unsigned i = 0; i < block->locations.size (); i++)
 		    {
-		      unsigned last_line
-			= block->locations[i].lines.back () + 1;
-		      if (last_line > sources[s].lines.size ())
-			sources[s].lines.resize (last_line);
+		      /* Sort lines of locations.  */
+		      sort (block->locations[i].lines.begin (),
+			    block->locations[i].lines.end ());
+
+		      if (!block->locations[i].lines.empty ())
+			{
+			  s = &sources[block->locations[i].source_file_idx];
+			  unsigned last_line
+			    = block->locations[i].lines.back ();
+
+			  /* Record new lines for the function.  */
+			  if (last_line >= s->lines.size ())
+			    {
+			      /* Record new lines for a source file.  */
+			      s->lines.resize (last_line + 1);
+			    }
+			}
 		    }
+
+		  /* Allocate lines for group function, following start_line
+		     and end_line information of the function.  */
+		  if (fn->is_group)
+		    fn->lines.resize (fn->end_line - fn->start_line + 1);
 		}
+
+	      solve_flow_graph (fn);
+	      if (fn->has_catch)
+		find_exception_blocks (fn);
 	    }
 
-	  solve_flow_graph (fn);
-	  if (fn->has_catch)
-	    find_exception_blocks (fn);
 	  *fn_end = fn;
 	  fn_end = &fn->next;
 	}
@@ -1057,6 +1221,8 @@ generate_results (const char *file_name)
   for (fn = functions; fn; fn = fn->next)
     {
       coverage_t coverage;
+      if (fn->artificial)
+	continue;
 
       memset (&coverage, 0, sizeof (coverage));
       coverage.name = flag_demangled_names ? fn->demangled_name : fn->name;
@@ -1237,6 +1403,7 @@ find_source (const char *file_name)
       src = &sources.back ();
       src->name = canon;
       src->coverage.name = src->name;
+      src->index = idx;
       if (source_length
 #if HAVE_DOS_BASED_FILE_SYSTEM
 	  /* You lose if separators don't match exactly in the
@@ -1328,15 +1495,17 @@ read_graph_file (void)
       if (tag == GCOV_TAG_FUNCTION)
 	{
 	  char *function_name;
-	  unsigned ident, lineno;
+	  unsigned ident;
 	  unsigned lineno_checksum, cfg_checksum;
 
 	  ident = gcov_read_unsigned ();
 	  lineno_checksum = gcov_read_unsigned ();
 	  cfg_checksum = gcov_read_unsigned ();
 	  function_name = xstrdup (gcov_read_string ());
+	  unsigned artificial = gcov_read_unsigned ();
 	  unsigned src_idx = find_source (gcov_read_string ());
-	  lineno = gcov_read_unsigned ();
+	  unsigned start_line = gcov_read_unsigned ();
+	  unsigned end_line = gcov_read_unsigned ();
 
 	  fn = new function_t;
 	  fn->name = function_name;
@@ -1350,9 +1519,10 @@ read_graph_file (void)
 	  fn->lineno_checksum = lineno_checksum;
 	  fn->cfg_checksum = cfg_checksum;
 	  fn->src = src_idx;
-	  fn->line = lineno;
+	  fn->start_line = start_line;
+	  fn->end_line = end_line;
+	  fn->artificial = artificial;
 
-	  fn->next_file_fn = NULL;
 	  fn->next = NULL;
 	  *fns_end = fn;
 	  fns_end = &fn->next;
@@ -2266,48 +2436,66 @@ add_line_counts (coverage_t *coverage, function_t *fn)
 	fn->blocks_executed++;
       for (unsigned i = 0; i < block->locations.size (); i++)
 	{
-	  source_info *src = &sources[block->locations[i].source_file_idx];
-
+	  unsigned src_idx = block->locations[i].source_file_idx;
 	  vector<unsigned> &lines = block->locations[i].lines;
+
+	  block->cycle.arc = NULL;
+	  block->cycle.ident = ~0U;
+
 	  for (unsigned j = 0; j < lines.size (); j++)
 	    {
-	      line = &src->lines[lines[j]];
-	      if (coverage)
+	      unsigned ln = lines[j];
+
+	      /* Line belongs to a function that is in a group.  */
+	      if (fn->group_line_p (ln, src_idx))
 		{
-		  if (!line->exists)
-		    coverage->lines++;
-		  if (!line->count && block->count)
-		    coverage->lines_executed++;
+		  gcc_assert (lines[j] - fn->start_line < fn->lines.size ());
+		  line = &(fn->lines[lines[j] - fn->start_line]);
+		  line->exists = 1;
+		  if (!block->exceptional)
+		    {
+		      line->unexceptional = 1;
+		      if (block->count == 0)
+			line->has_unexecuted_block = 1;
+		    }
+		  line->count += block->count;
 		}
-	      line->exists = 1;
-	      if (!block->exceptional)
+	      else
 		{
-		  line->unexceptional = 1;
-		  if (block->count == 0)
-		    line->has_unexecuted_block = 1;
+		  gcc_assert (ln < sources[src_idx].lines.size ());
+		  line = &(sources[src_idx].lines[ln]);
+		  if (coverage)
+		    {
+		      if (!line->exists)
+			coverage->lines++;
+		      if (!line->count && block->count)
+			coverage->lines_executed++;
+		    }
+		  line->exists = 1;
+		  if (!block->exceptional)
+		    {
+		      line->unexceptional = 1;
+		      if (block->count == 0)
+			line->has_unexecuted_block = 1;
+		    }
+		  line->count += block->count;
 		}
-	      line->count += block->count;
 	    }
-	}
-      block->cycle.arc = NULL;
-      block->cycle.ident = ~0U;
-      has_any_line = true;
 
-      if (!ix || ix + 1 == fn->blocks.size ())
-	/* Entry or exit block */;
-      else if (line != NULL)
-	{
-	  line->blocks.push_back (block);
+	  has_any_line = true;
 
-	  if (flag_branches)
+	  if (!ix || ix + 1 == fn->blocks.size ())
+	    /* Entry or exit block.  */;
+	  else if (line != NULL)
 	    {
-	      arc_t *arc;
+	      line->blocks.push_back (block);
 
-	      for (arc = block->succ; arc; arc = arc->succ_next)
+	      if (flag_branches)
 		{
-		  line->branches.push_back (arc);
-		  if (coverage && !arc->is_unconditional)
-		    add_branch_counts (coverage, arc);
+		  arc_t *arc;
+
+		  for (arc = block->succ; arc; arc = arc->succ_next)
+		    line->branches.push_back (arc);
 		}
 	    }
 	}
@@ -2317,72 +2505,113 @@ add_line_counts (coverage_t *coverage, function_t *fn)
     fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name);
 }
 
+/* Accumulate info for LINE that belongs to SRC source file.  If ADD_COVERAGE
+   is set to true, update source file summary.  */
+
+static void accumulate_line_info (line_info *line, source_info *src,
+				  bool add_coverage)
+{
+  if (add_coverage)
+    for (vector<arc_info *>::iterator it = line->branches.begin ();
+	 it != line->branches.end (); it++)
+      add_branch_counts (&src->coverage, *it);
+
+  if (!line->blocks.empty ())
+    {
+      /* 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.  */
+      gcov_type count = 0;
+
+      /* Cycle detection.  */
+      for (vector<block_t *>::iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next)
+	    if (!line->has_block (arc->src))
+	      count += arc->count;
+	  for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next)
+	    arc->cs_count = arc->count;
+	}
+
+      /* Now, add the count of loops entirely on this line.  */
+      count += get_cycles_count (*line);
+      line->count = count;
+    }
+
+  if (line->exists && add_coverage)
+    {
+      src->coverage.lines++;
+      if (line->count)
+	src->coverage.lines_executed++;
+    }
+}
+
 /* Accumulate the line counts of a file.  */
 
 static void
 accumulate_line_counts (source_info *src)
 {
-  function_t *fn, *fn_p, *fn_n;
-  unsigned ix = 0;
-
-  /* Reverse the function order.  */
-  for (fn = src->functions, fn_p = NULL; fn; fn_p = fn, fn = fn_n)
+  /* First work on group functions.  */
+  for (vector<function_t *>::iterator it = src->functions.begin ();
+       it != src->functions.end (); it++)
     {
-      fn_n = fn->next_file_fn;
-      fn->next_file_fn = fn_p;
+      function_info *fn = *it;
+
+      if (fn->src != src->index || !fn->is_group)
+	continue;
+
+      for (vector<line_info>::iterator it2 = fn->lines.begin ();
+	   it2 != fn->lines.end (); it2++)
+	  {
+	    line_info *line = &(*it2);
+	    accumulate_line_info (line, src, false);
+	  }
     }
-  src->functions = fn_p;
 
-  for (vector<line_info>::reverse_iterator it = src->lines.rbegin ();
-       it != src->lines.rend (); it++)
-    {
-      line_info *line = &(*it);
-      if (!line->blocks.empty ())
-	{
-	  /* 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.  */
-	  gcov_type count = 0;
-
-	  /* Sum the entry arcs.  */
-	  for (vector<block_t *>::iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
-	    {
-	      arc_t *arc;
+  /* Work on global lines that line in source file SRC.  */
+  for (vector<line_info>::iterator it = src->lines.begin ();
+       it != src->lines.end (); it++)
+    accumulate_line_info (&(*it), src, true);
 
-	      for (arc = (*it)->pred; arc; arc = arc->pred_next)
-		if (flag_branches)
-		  add_branch_counts (&src->coverage, arc);
-	    }
+  /* If not using intermediate mode, sum lines of group functions and
+     add them to lines that live in a source file.  */
+  if (!flag_intermediate_format)
+    for (vector<function_t *>::iterator it = src->functions.begin ();
+	 it != src->functions.end (); it++)
+      {
+	function_info *fn = *it;
 
-	  /* Cycle detection.  */
-	  for (vector<block_t *>::iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
-	    {
-	      for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next)
-		if (!line->has_block (arc->src))
-		  count += arc->count;
-	      for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next)
-		arc->cs_count = arc->count;
-	    }
+	if (fn->src != src->index || !fn->is_group)
+	  continue;
 
-	  /* Now, add the count of loops entirely on this line.  */
-	  count += get_cycles_count (*line);
-	  line->count = count;
-	}
+	for (unsigned i = 0; i < fn->lines.size (); i++)
+	  {
+	    line_info *fn_line = &fn->lines[i];
+	    if (fn_line->exists)
+	      {
+		unsigned ln = fn->start_line + i;
+		line_info *src_line = &src->lines[ln];
 
-      if (line->exists)
-	{
-	  src->coverage.lines++;
-	  if (line->count)
-	    src->coverage.lines_executed++;
-	}
+		if (!src_line->exists)
+		  src->coverage.lines++;
+		if (!src_line->count && fn_line->count)
+		  src->coverage.lines_executed++;
 
-      ix++;
-    }
+		src_line->count += fn_line->count;
+		src_line->exists = 1;
+
+		if (fn_line->has_unexecuted_block)
+		  src_line->has_unexecuted_block = 1;
+
+		if (fn_line->unexceptional)
+		  src_line->unexceptional = 1;
+	      }
+	  }
+      }
 }
 
 /* Output information about ARC number IX.  Returns nonzero if
@@ -2500,7 +2729,8 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional,
 	      if (flag_use_colors)
 		{
 		  pad_count_string (s);
-		  s = SGR_SEQ (COLOR_BG_MAGENTA COLOR_SEPARATOR COLOR_FG_WHITE);
+		  s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA
+					COLOR_SEPARATOR COLOR_FG_WHITE));
 		  s += SGR_RESET;
 		}
 	      else
@@ -2538,6 +2768,86 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional,
   fprintf (f, "%s:%5u", s.c_str (), line_num);
 }
 
+static void
+print_source_line (FILE *f, const vector<const char *> &source_lines,
+		   unsigned line)
+{
+  gcc_assert (line >= 1);
+  gcc_assert (line <= source_lines.size ());
+
+  fprintf (f, ":%s\n", source_lines[line - 1]);
+}
+
+/* Output line details for LINE and print it to F file.  LINE lives on
+   LINE_NUM.  */
+
+static void
+output_line_details (FILE *f, const line_info *line, unsigned line_num)
+{
+  if (flag_all_blocks)
+    {
+      arc_t *arc;
+      int ix, jx;
+
+      ix = jx = 0;
+      for (vector<block_t *>::const_iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  if (!(*it)->is_call_return)
+	    {
+	      output_line_beginning (f, line->exists,
+				     (*it)->exceptional, false,
+				     (*it)->count, line_num,
+				     "%%%%%", "$$$$$");
+	      fprintf (f, "-block %2d", ix++);
+	      if (flag_verbose)
+		fprintf (f, " (BB %u)", (*it)->id);
+	      fprintf (f, "\n");
+	    }
+	  if (flag_branches)
+	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
+	      jx += output_branch_count (f, jx, arc);
+	}
+    }
+  else if (flag_branches)
+    {
+      int ix;
+
+      ix = 0;
+      for (vector<arc_t *>::const_iterator it = line->branches.begin ();
+	   it != line->branches.end (); it++)
+	ix += output_branch_count (f, ix, (*it));
+    }
+}
+
+/* Output detail statistics about function FN to file F.  */
+
+static void
+output_function_details (FILE *f, const function_info *fn)
+{
+  if (!flag_branches)
+    return;
+
+  arc_t *arc = fn->blocks[EXIT_BLOCK].pred;
+  gcov_type return_count = fn->blocks[EXIT_BLOCK].count;
+  gcov_type called_count = fn->blocks[ENTRY_BLOCK].count;
+
+  for (; arc; arc = arc->pred_next)
+    if (arc->fake)
+      return_count -= arc->count;
+
+  fprintf (f, "function %s",
+	   flag_demangled_names ? fn->demangled_name : fn->name);
+  fprintf (f, " called %s",
+	   format_gcov (called_count, 0, -1));
+  fprintf (f, " returned %s",
+	   format_gcov (return_count, called_count, 0));
+  fprintf (f, " blocks executed %s",
+	   format_gcov (fn->blocks_executed, fn->blocks.size () - 2,
+			0));
+  fprintf (f, "\n");
+}
+
 /* 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.  */
@@ -2546,12 +2856,10 @@ static void
 output_lines (FILE *gcov_file, const source_info *src)
 {
 #define  DEFAULT_LINE_START "        -:    0:"
+#define FN_SEPARATOR "------------------\n"
 
   FILE *source_file;
-  unsigned line_num;	/* current line number.  */
-  const line_info *line;  /* current line info ptr.  */
-  const char *retval = "";	/* status of source file reading.  */
-  function_t *fn = NULL;
+  const char *retval;
 
   fprintf (gcov_file, DEFAULT_LINE_START "Source:%s\n", src->coverage.name);
   if (!multiple_files)
@@ -2565,43 +2873,40 @@ output_lines (FILE *gcov_file, const source_info *src)
 
   source_file = fopen (src->name, "r");
   if (!source_file)
-    {
-      fnotice (stderr, "Cannot open source file %s\n", src->name);
-      retval = NULL;
-    }
+    fnotice (stderr, "Cannot open source file %s\n", src->name);
   else if (src->file_time == 0)
     fprintf (gcov_file, DEFAULT_LINE_START "Source is newer than graph\n");
 
-  if (flag_branches)
-    fn = src->functions;
+  vector<const char *> source_lines;
+  if (source_file)
+    while ((retval = read_line (source_file)) != NULL)
+      source_lines.push_back (xstrdup (retval));
 
-  for (line_num = 1, line = &src->lines[line_num];
-       line_num < src->lines.size (); line_num++, line++)
+  unsigned line_start_group = 0;
+  vector<function_t *> fns;
+
+  for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++)
     {
-      for (; fn && fn->line == line_num; fn = fn->next_file_fn)
+      if (line_num >= src->lines.size ())
 	{
-	  arc_t *arc = fn->blocks[EXIT_BLOCK].pred;
-	  gcov_type return_count = fn->blocks[EXIT_BLOCK].count;
-	  gcov_type called_count = fn->blocks[ENTRY_BLOCK].count;
-
-	  for (; arc; arc = arc->pred_next)
-	    if (arc->fake)
-	      return_count -= arc->count;
-
-	  fprintf (gcov_file, "function %s", flag_demangled_names ?
-                   fn->demangled_name : fn->name);
-	  fprintf (gcov_file, " called %s",
-		   format_gcov (called_count, 0, -1));
-	  fprintf (gcov_file, " returned %s",
-		   format_gcov (return_count, called_count, 0));
-	  fprintf (gcov_file, " blocks executed %s",
-		   format_gcov (fn->blocks_executed, fn->blocks.size () - 2,
-				0));
-	  fprintf (gcov_file, "\n");
+	  fprintf (gcov_file, "%9s:%5u", "-", line_num);
+	  print_source_line (gcov_file, source_lines, line_num);
+	  continue;
 	}
 
-      if (retval)
-	retval = read_line (source_file);
+      const line_info *line = &src->lines[line_num];
+
+      if (line_start_group == 0)
+	{
+	  fns = src->get_functions_at_location (line_num);
+	  if (fns.size () > 1)
+	    line_start_group = fns[0]->end_line;
+	  else if (fns.size () == 1)
+	    {
+	      function_t *fn = fns[0];
+	      output_function_details (gcov_file, fn);
+	    }
+	}
 
       /* For lines which don't exist in the .bb file, print '-' before
 	 the source line.  For lines which exist but were never
@@ -2610,54 +2915,64 @@ output_lines (FILE *gcov_file, const source_info *src)
 	 There are 16 spaces of indentation added before the source
 	 line so that tabs won't be messed up.  */
       output_line_beginning (gcov_file, line->exists, line->unexceptional,
-			     line->has_unexecuted_block, line->count, line_num,
-			     "=====", "#####");
-      fprintf (gcov_file, ":%s\n", retval ? retval : "/*EOF*/");
+			     line->has_unexecuted_block, line->count,
+			     line_num, "=====", "#####");
 
-      if (flag_all_blocks)
-	{
-	  arc_t *arc;
-	  int ix, jx;
+      print_source_line (gcov_file, source_lines, line_num);
+      output_line_details (gcov_file, line, line_num);
 
-	  ix = jx = 0;
-	  for (vector<block_t *>::const_iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
+      if (line_start_group == line_num)
+	{
+	  for (vector<function_t *>::iterator it = fns.begin ();
+	       it != fns.end (); it++)
 	    {
-	      if (!(*it)->is_call_return)
+	      function_info *fn = *it;
+	      vector<line_info> &lines = fn->lines;
+
+	      fprintf (gcov_file, FN_SEPARATOR);
+
+	      string fn_name
+		= flag_demangled_names ? fn->demangled_name : fn->name;
+
+	      if (flag_use_colors)
 		{
+		  fn_name.insert (0, SGR_SEQ (COLOR_FG_CYAN));
+		  fn_name += SGR_RESET;
+		}
+
+	      fprintf (gcov_file, "%s:\n", fn_name.c_str ());
+
+	      output_function_details (gcov_file, fn);
+
+	      /* Print all lines covered by the function.  */
+	      for (unsigned i = 0; i < lines.size (); i++)
+		{
+		  line_info *line = &lines[i];
+		  unsigned l = fn->start_line + i;
+
+		  /* For lines which don't exist in the .bb file, print '-'
+		     before the source line.  For lines which exist but
+		     were never executed, print '#####' or '=====' 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.  */
 		  output_line_beginning (gcov_file, line->exists,
-					 (*it)->exceptional, false,
-					 (*it)->count, line_num,
-					 "%%%%%", "$$$$$");
-		  fprintf (gcov_file, "-block %2d", ix++);
-		  if (flag_verbose)
-		    fprintf (gcov_file, " (BB %u)", (*it)->id);
-		  fprintf (gcov_file, "\n");
+					 line->unexceptional,
+					 line->has_unexecuted_block,
+					 line->count,
+					 l, "=====", "#####");
+
+		  print_source_line (gcov_file, source_lines, l);
+		  output_line_details (gcov_file, line, l);
 		}
-	      if (flag_branches)
-		for (arc = (*it)->succ; arc; arc = arc->succ_next)
-		  jx += output_branch_count (gcov_file, jx, arc);
 	    }
-	}
-      else if (flag_branches)
-	{
-	  int ix;
 
-	  ix = 0;
-	  for (vector<arc_t *>::const_iterator it = line->branches.begin ();
-	       it != line->branches.end (); it++)
-	    ix += output_branch_count (gcov_file, ix, (*it));
+	  fprintf (gcov_file, FN_SEPARATOR);
+	  line_start_group = 0;
 	}
     }
 
-  /* Handle all remaining source lines.  There may be lines after the
-     last line of code.  */
-  if (retval)
-    {
-      for (; (retval = read_line (source_file)); line_num++)
-	fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval);
-    }
-
   if (source_file)
     fclose (source_file);
 }
Nathan Sidwell Nov. 7, 2017, 2:49 p.m. UTC | #2
On 11/07/2017 05:53 AM, Martin Liška wrote:
> Hello.
> 
> This is slightly updated version from the previous. Various small issues were fixed
> and I update documentation in order to reflect the changes.

> +  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
>    gcov_write_filename (xloc.file);
>    gcov_write_unsigned (xloc.line);
> +  gcov_write_unsigned (expand_location (cfun->function_end_locus).line);
>    gcov_write_length (offset);

this is presuming the end line is in the same file as the start line.  A 
reasonable assumption, but users can have exciting ideas!  What is the 
failure mode if the function straddles a file boundary?

+  /* True when multiple functions start at a line in a source file.  */
+  unsigned is_group : 1;

Is this true for the non-template instantiation case of:

void a () {} void b () {}

What happens there?  (perhaps we cannot tell the difference?)

+  inline bool operator() (const function_info *lhs,
+			  const function_info *rhs)
+    {
+      return lhs->end_line < rhs->start_line;
+    }

This isn't stable if they start on the same line.  Will output order 
depend on the vaguaries of the sorting algorithm?

> +	  vector<line_info> &lines = (*it2)->lines;
> +	  /* Print all lines covered by the function.  */
> +	  for (unsigned i = 0; i < lines.size (); i++)

comment seems to apply to the whole block, not just the for loop? The 
lines init is merely local cache?  (this stood out because of lack of 
blank line before the comment, btw)

Otherwise looks good, and a nice improvement.  Please investigate the 
corner cases mentioned above though.

nathan
Martin Liška Nov. 8, 2017, 11:03 a.m. UTC | #3
On 11/07/2017 03:49 PM, Nathan Sidwell wrote:
> On 11/07/2017 05:53 AM, Martin Liška wrote:
>> Hello.
>>
>> This is slightly updated version from the previous. Various small issues were fixed
>> and I update documentation in order to reflect the changes.
> 
>> +  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
>>    gcov_write_filename (xloc.file);
>>    gcov_write_unsigned (xloc.line);
>> +  gcov_write_unsigned (expand_location (cfun->function_end_locus).line);
>>    gcov_write_length (offset);
> 
> this is presuming the end line is in the same file as the start line.  A reasonable assumption, but users can have exciting ideas!  What is the failure mode if the function straddles a file boundary?

Hi.

I decided to fix that with change that set line_end = line_start if line_end < line_start.
That survives reasonably well with cases like this:

$         -:    3:template<class T>
        -:    4:class Foo
        -:    5:{
        -:    6:  public:
        1:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
Foo<char>::Foo():
        0:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
Foo<int>::Foo():
        1:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
Foo<char>::Foo(int):
        0:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
Foo<int>::Foo(int):
        0:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
Foo<char>::xxx():
        0:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
Foo<int>::xxx():
        0:    7:  Foo(): b (1000) {}          Foo(int _b): b (_b) {}   static void xxx() {}
------------------
        2:    8:  void inc () {
------------------
Foo<char>::inc():
        0:    8:  void inc () {
------------------
Foo<int>::inc():
        2:    8:  void inc () {
------------------
        2:    9:    b++;
[another gcov file]
        2:    3:b++;
        2:    4:b++;
        2:    5:b += 1;
        2:    6:}

> 
> +  /* True when multiple functions start at a line in a source file.  */
> +  unsigned is_group : 1;
> 
> Is this true for the non-template instantiation case of:
> 
> void a () {} void b () {}
> 
> What happens there?  (perhaps we cannot tell the difference?)
> 
> +  inline bool operator() (const function_info *lhs,
> +              const function_info *rhs)
> +    {
> +      return lhs->end_line < rhs->start_line;

There's typo as s/end_line/start_line.

> +    }
> 
> This isn't stable if they start on the same line.  Will output order depend on the vaguaries of the sorting algorithm?
> 
>> +      vector<line_info> &lines = (*it2)->lines;
>> +      /* Print all lines covered by the function.  */
>> +      for (unsigned i = 0; i < lines.size (); i++)

So fixed by introduction of line column that is used for sorting as well.

Now we have:

        -:    2:class Foo
        -:    3:{
        -:    4:  public:
        -:    5:  int b;
        1:    6:  Foo(): b (1000) {}
       2*:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
Foo<int>::inc():
        1:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
test():
    #####:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
test2():
    #####:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
test3():
    #####:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
test4():
        1:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
test5():
    #####:    7:  void inc () { if (b) b++; } };     static void test() {}   static void test2() {} static void test3() {} static void test4() {} static void test5() {}
------------------
        -:    8:
        1:    9:int main()
        -:   10:{
        1:   11:  Foo<int> xx;
        1:   12:  xx.inc();
        -:   13:
        1:   14:  test4();
        -:   15:
        1:   16:  return 0;
        -:   17:}

Which hopefully also work fine.

> 
> comment seems to apply to the whole block, not just the for loop? The lines init is merely local cache?  (this stood out because of lack of blank line before the comment, btw)

Removed it :)

> 
> Otherwise looks good, and a nice improvement.  Please investigate the corner cases mentioned above though.

Thank you.
Btw. there are people in Mozilla that have been utilizing gcov significantly:
https://marco-c.github.io/2017/07/28/code-coverage-architecture.html

They would definitely benefit from the functionality because the code base is full of heavy C++ code.

May I understand the reply as ACK?
Martin

> 
> nathan
From 6de940856a2af8a9b1689951d45a9484e921a7ec Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 26 Oct 2017 10:39:40 +0200
Subject: [PATCH 1/8] GCOV: support multiple functions per a line

gcc/ChangeLog:

2017-11-01  Martin Liska  <mliska@suse.cz>

	* coverage.c (coverage_begin_function): Output also end locus
	of a function and information whether the function is
	artificial.
	* gcov-dump.c (tag_function): Parse and print the information.
	* gcov.c (INCLUDE_MAP): Add include.
	(INCLUDE_SET): Likewise.
	(struct line_info): Move earlier in the source file because
	of vector<line_info> in function_info structure.
	(line_info::line_info): Likewise.
	(line_info::has_block): Likewise.
	(struct source_info): Add new member index.
	(source_info::get_functions_at_location): New function.
	(function_info::group_line_p): New function.
	(output_intermediate_line): New function.
	(output_intermediate_file): Use the mentioned function.
	(struct function_start): New.
	(struct function_start_pair_hash): Likewise.
	(process_file): Add code that identifies group functions.
	Assign lines either to global or function scope.
	(generate_results): Skip artificial functions.
	(find_source): Assign index for each source file.
	(read_graph_file): Read new flag artificial and end_line.
	(add_line_counts): Assign it either to global of function scope.
	(accumulate_line_counts): Isolate core of the function to
	accumulate_line_info and call it for both function and global
	scope lines.
	(accumulate_line_info): New function.
	(output_line_beginning): Fix GNU coding style.
	(print_source_line): New function.
	(output_line_details): Likewise.
	(output_function_details): Likewise.
	(output_lines): Iterate both source (global) scope and function
	scope.
	(struct function_line_start_cmp): New class.
	* doc/gcov.texi: Reflect changes in documentation.
---
 gcc/coverage.c    |   6 +
 gcc/doc/gcov.texi | 329 +++++++++++++++------
 gcc/gcov-dump.c   |   8 +-
 gcc/gcov.c        | 855 +++++++++++++++++++++++++++++++++++++-----------------
 4 files changed, 842 insertions(+), 356 deletions(-)

diff --git a/gcc/coverage.c b/gcc/coverage.c
index 8a56a677f15..15d092f4a60 100644
--- a/gcc/coverage.c
+++ b/gcc/coverage.c
@@ -663,8 +663,14 @@ coverage_begin_function (unsigned lineno_checksum, unsigned cfg_checksum)
   gcov_write_unsigned (cfg_checksum);
   gcov_write_string (IDENTIFIER_POINTER
 		     (DECL_ASSEMBLER_NAME (current_function_decl)));
+  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
   gcov_write_filename (xloc.file);
   gcov_write_unsigned (xloc.line);
+  gcov_write_unsigned (xloc.column);
+
+  /* Function can start in a single file and end in another one.  */
+  int fn_end_line = expand_location (cfun->function_end_locus).line;
+  gcov_write_unsigned (fn_end_line > xloc.line ? fn_end_line : xloc.line);
   gcov_write_length (offset);
 
   return !gcov_is_error ();
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index 5c4ba8a51a7..6f6a92c131a 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -193,7 +193,7 @@ Write counts in human readable format (like 24k).
 
 @smallexample
 file:@var{source_file_name}
-function:@var{line_number},@var{execution_count},@var{function_name}
+function:@var{start_line_number},@var{end_line_number},@var{execution_count},@var{function_name}
 lcount:@var{line number},@var{execution_count},@var{has_unexecuted_block}
 branch:@var{line_number},@var{branch_coverage_type}
 
@@ -201,24 +201,55 @@ Where the @var{branch_coverage_type} is
    notexec (Branch not executed)
    taken (Branch executed and taken)
    nottaken (Branch executed, but not taken)
+@end smallexample
 
 There can be multiple @var{file} entries in an intermediate gcov
 file. All entries following a @var{file} pertain to that source file
-until the next @var{file} entry.
-@end smallexample
+until the next @var{file} entry.  If there are multiple functions that
+start on a single line, then corresponding lcount is repeated multiple
+times.
 
 Here is a sample when @option{-i} is used in conjunction with @option{-b} option:
 
 @smallexample
-file:array.cc
-function:11,1,_Z3sumRKSt6vectorIPiSaIS0_EE
-function:22,1,main
-lcount:11,1,0
-lcount:12,1,0
-lcount:14,1,0
-branch:14,taken
-lcount:26,1,0
-branch:28,nottaken
+file:tmp.cpp
+function:7,7,0,_ZN3FooIcEC2Ev
+function:7,7,1,_ZN3FooIiEC2Ev
+function:8,8,0,_ZN3FooIcE3incEv
+function:8,8,2,_ZN3FooIiE3incEv
+function:18,37,1,main
+lcount:7,0,1
+lcount:7,1,0
+lcount:8,0,1
+lcount:8,2,0
+lcount:18,1,0
+lcount:21,1,0
+branch:21,taken
+branch:21,nottaken
+lcount:23,1,0
+branch:23,taken
+branch:23,nottaken
+lcount:24,1,0
+branch:24,taken
+branch:24,nottaken
+lcount:25,1,0
+lcount:27,11,0
+branch:27,taken
+branch:27,taken
+lcount:28,10,0
+lcount:30,1,1
+branch:30,nottaken
+branch:30,taken
+lcount:32,1,0
+branch:32,nottaken
+branch:32,taken
+lcount:33,0,1
+branch:33,notexec
+branch:33,notexec
+lcount:35,1,0
+branch:35,taken
+branch:35,nottaken
+lcount:36,1,0
 @end smallexample
 
 @item -k
@@ -391,79 +422,158 @@ source file compiled with @option{-fprofile-arcs}, an accompanying
 
 Running @command{gcov} with your program's source file names as arguments
 will now produce a listing of the code along with frequency of execution
-for each line.  For example, if your program is called @file{tmp.c}, this
+for each line.  For example, if your program is called @file{tmp.cpp}, this
 is what you see when you use the basic @command{gcov} facility:
 
 @smallexample
-$ gcc -fprofile-arcs -ftest-coverage tmp.c
+$ g++ -fprofile-arcs -ftest-coverage tmp.cpp
 $ a.out
-$ gcov tmp.c
-File 'tmp.c'
-Lines executed:90.00% of 10
-Creating 'tmp.c.gcov'
+$ gcov tmp.cpp -m
+File 'tmp.cpp'
+Lines executed:92.86% of 14
+Creating 'tmp.cpp.gcov'
 @end smallexample
 
-The file @file{tmp.c.gcov} contains output from @command{gcov}.
+The file @file{tmp.cpp.gcov} contains output from @command{gcov}.
 Here is a sample:
 
 @smallexample
-        -:    0:Source:tmp.c
+        -:    0:Source:tmp.cpp
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
-        -:    3:int main (void)
-        1:    4:@{
-        1:    5:  int i, total;
-        -:    6:
-        1:    7:  total = 0;
-        -:    8:
-       11:    9:  for (i = 0; i < 10; i++)
-       10:   10:    total += i;
-        -:   11:
-        1:   12:  if (total != 45)
-    #####:   13:    printf ("Failure\n");
-        -:   14:  else
-        1:   15:    printf ("Success\n");
-        1:   16:  return 0;
-        -:   17:@}
+        -:    3:template<class T>
+        -:    4:class Foo
+        -:    5:@{
+        -:    6:  public:
+       1*:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<char>::Foo():
+    #####:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<int>::Foo():
+        1:    7:  Foo(): b (1000) @{@}
+------------------
+       2*:    8:  void inc () @{ b++; @}
+------------------
+Foo<char>::inc():
+    #####:    8:  void inc () @{ b++; @}
+------------------
+Foo<int>::inc():
+        2:    8:  void inc () @{ b++; @}
+------------------
+        -:    9:
+        -:   10:  private:
+        -:   11:  int b;
+        -:   12:@};
+        -:   13:
+        -:   14:template class Foo<int>;
+        -:   15:template class Foo<char>;
+        -:   16:
+        -:   17:int
+        1:   18:main (void)
+        -:   19:@{
+        -:   20:  int i, total;
+        1:   21:  Foo<int> counter;
+        -:   22:
+        1:   23:  counter.inc();
+        1:   24:  counter.inc();
+        1:   25:  total = 0;
+        -:   26:
+       11:   27:  for (i = 0; i < 10; i++)
+       10:   28:    total += i;
+        -:   29:
+       1*:   30:  int v = total > 100 ? 1 : 2;
+        -:   31:
+        1:   32:  if (total != 45)
+    #####:   33:    printf ("Failure\n");
+        -:   34:  else
+        1:   35:    printf ("Success\n");
+        1:   36:  return 0;
+        -:   37:@}
 @end smallexample
 
+Note that line 7 is shown in the report multiple times.  First occurrence
+presents total number of execution of the line and the next two belong
+to instances of class Foo constructors.  As you can also see, line 30 contains
+some unexecuted basic blocks and thus execution count has asterisk symbol.
+
 When you use the @option{-a} option, you will get individual block
 counts, and the output looks like this:
 
 @smallexample
-        -:    0:Source:tmp.c
+        -:    0:Source:tmp.cpp
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
-        -:    3:int main (void)
-        1:    4:@{
-        1:    4-block  0
-        1:    5:  int i, total;
-        -:    6:
-        1:    7:  total = 0;
-        -:    8:
-       11:    9:  for (i = 0; i < 10; i++)
-       11:    9-block  0
-       10:   10:    total += i;
-       10:   10-block  0
-        -:   11:
-        1:   12:  if (total != 45)
-        1:   12-block  0
-    #####:   13:    printf ("Failure\n");
-    $$$$$:   13-block  0
-        -:   14:  else
-        1:   15:    printf ("Success\n");
-        1:   15-block  0
-        1:   16:  return 0;
-        1:   16-block  0
-        -:   17:@}
+        -:    3:template<class T>
+        -:    4:class Foo
+        -:    5:@{
+        -:    6:  public:
+       1*:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<char>::Foo():
+    #####:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<int>::Foo():
+        1:    7:  Foo(): b (1000) @{@}
+------------------
+       2*:    8:  void inc () @{ b++; @}
+------------------
+Foo<char>::inc():
+    #####:    8:  void inc () @{ b++; @}
+------------------
+Foo<int>::inc():
+        2:    8:  void inc () @{ b++; @}
+------------------
+        -:    9:
+        -:   10:  private:
+        -:   11:  int b;
+        -:   12:@};
+        -:   13:
+        -:   14:template class Foo<int>;
+        -:   15:template class Foo<char>;
+        -:   16:
+        -:   17:int
+        1:   18:main (void)
+        -:   19:@{
+        -:   20:  int i, total;
+        1:   21:  Foo<int> counter;
+        1:   21-block  0
+        -:   22:
+        1:   23:  counter.inc();
+        1:   23-block  0
+        1:   24:  counter.inc();
+        1:   24-block  0
+        1:   25:  total = 0;
+        -:   26:
+       11:   27:  for (i = 0; i < 10; i++)
+        1:   27-block  0
+       11:   27-block  1
+       10:   28:    total += i;
+       10:   28-block  0
+        -:   29:
+       1*:   30:  int v = total > 100 ? 1 : 2;
+        1:   30-block  0
+    %%%%%:   30-block  1
+        1:   30-block  2
+        -:   31:
+        1:   32:  if (total != 45)
+        1:   32-block  0
+    #####:   33:    printf ("Failure\n");
+    %%%%%:   33-block  0
+        -:   34:  else
+        1:   35:    printf ("Success\n");
+        1:   35-block  0
+        1:   36:  return 0;
+        1:   36-block  0
+        -:   37:@}
 @end smallexample
 
 In this mode, each basic block is only shown on one line -- the last
@@ -477,53 +587,94 @@ block, the branch and call counts of the block will be shown, if the
 
 Because of the way GCC instruments calls, a call count can be shown
 after a line with no individual blocks.
-As you can see, line 13 contains a basic block that was not executed.
+As you can see, line 33 contains a basic block that was not executed.
 
 @need 450
 When you use the @option{-b} option, your output looks like this:
 
 @smallexample
-$ gcov -b tmp.c
-File 'tmp.c'
-Lines executed:90.00% of 10
-Branches executed:80.00% of 5
-Taken at least once:80.00% of 5
-Calls executed:50.00% of 2
-Creating 'tmp.c.gcov'
-@end smallexample
-
-Here is a sample of a resulting @file{tmp.c.gcov} file:
-
-@smallexample
-        -:    0:Source:tmp.c
+        -:    0:Source:tmp.cpp
         -:    0:Graph:tmp.gcno
         -:    0:Data:tmp.gcda
         -:    0:Runs:1
         -:    0:Programs:1
         -:    1:#include <stdio.h>
         -:    2:
-        -:    3:int main (void)
-function main called 1 returned 1 blocks executed 75%
-        1:    4:@{
-        1:    5:  int i, total;
-        -:    6:
-        1:    7:  total = 0;
-        -:    8:
-       11:    9:  for (i = 0; i < 10; i++)
+        -:    3:template<class T>
+        -:    4:class Foo
+        -:    5:@{
+        -:    6:  public:
+       1*:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<char>::Foo():
+function Foo<char>::Foo() called 0 returned 0% blocks executed 0%
+    #####:    7:  Foo(): b (1000) @{@}
+------------------
+Foo<int>::Foo():
+function Foo<int>::Foo() called 1 returned 100% blocks executed 100%
+        1:    7:  Foo(): b (1000) @{@}
+------------------
+       2*:    8:  void inc () @{ b++; @}
+------------------
+Foo<char>::inc():
+function Foo<char>::inc() called 0 returned 0% blocks executed 0%
+    #####:    8:  void inc () @{ b++; @}
+------------------
+Foo<int>::inc():
+function Foo<int>::inc() called 2 returned 100% blocks executed 100%
+        2:    8:  void inc () @{ b++; @}
+------------------
+        -:    9:
+        -:   10:  private:
+        -:   11:  int b;
+        -:   12:@};
+        -:   13:
+        -:   14:template class Foo<int>;
+        -:   15:template class Foo<char>;
+        -:   16:
+        -:   17:int
+function main called 1 returned 100% blocks executed 81%
+        1:   18:main (void)
+        -:   19:@{
+        -:   20:  int i, total;
+        1:   21:  Foo<int> counter;
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        -:   22:
+        1:   23:  counter.inc();
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        1:   24:  counter.inc();
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        1:   25:  total = 0;
+        -:   26:
+       11:   27:  for (i = 0; i < 10; i++)
 branch  0 taken 91% (fallthrough)
 branch  1 taken 9%
-       10:   10:    total += i;
-        -:   11:
-        1:   12:  if (total != 45)
+       10:   28:    total += i;
+        -:   29:
+       1*:   30:  int v = total > 100 ? 1 : 2;
+branch  0 taken 0% (fallthrough)
+branch  1 taken 100%
+        -:   31:
+        1:   32:  if (total != 45)
 branch  0 taken 0% (fallthrough)
 branch  1 taken 100%
-    #####:   13:    printf ("Failure\n");
+    #####:   33:    printf ("Failure\n");
 call    0 never executed
-        -:   14:  else
-        1:   15:    printf ("Success\n");
-call    0 called 1 returned 100%
-        1:   16:  return 0;
-        -:   17:@}
+branch  1 never executed
+branch  2 never executed
+        -:   34:  else
+        1:   35:    printf ("Success\n");
+call    0 returned 100%
+branch  1 taken 100% (fallthrough)
+branch  2 taken 0% (throw)
+        1:   36:  return 0;
+        -:   37:@}
 @end smallexample
 
 For each function, a line is printed showing how many times the function
diff --git a/gcc/gcov-dump.c b/gcc/gcov-dump.c
index d24e72ac4a1..c4e05cd4795 100644
--- a/gcc/gcov-dump.c
+++ b/gcc/gcov-dump.c
@@ -308,9 +308,15 @@ tag_function (const char *filename ATTRIBUTE_UNUSED,
 	  
 	  name = gcov_read_string ();
 	  printf (", `%s'", name ? name : "NULL");
+	  unsigned artificial = gcov_read_unsigned ();
 	  name = gcov_read_string ();
 	  printf (" %s", name ? name : "NULL");
-	  printf (":%u", gcov_read_unsigned ());
+	  unsigned line_start = gcov_read_unsigned ();
+	  unsigned column_start = gcov_read_unsigned ();
+	  unsigned line_end = gcov_read_unsigned ();
+	  printf (":%u:%u:%u", line_start, column_start, line_end);
+	  if (artificial)
+	    printf (", artificial");
 	}
     }
 }
diff --git a/gcc/gcov.c b/gcc/gcov.c
index 48bcdc0d4c3..846a2326196 100644
--- a/gcc/gcov.c
+++ b/gcc/gcov.c
@@ -34,6 +34,8 @@ along with Gcov; see the file COPYING3.  If not see
 #define INCLUDE_ALGORITHM
 #define INCLUDE_VECTOR
 #define INCLUDE_STRING
+#define INCLUDE_MAP
+#define INCLUDE_SET
 #include "system.h"
 #include "coretypes.h"
 #include "tm.h"
@@ -183,6 +185,42 @@ block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0),
   cycle.arc = NULL;
 }
 
+/* Describes a single line of source.  Contains a chain of basic blocks
+   with code on it.  */
+
+struct line_info
+{
+  /* Default constructor.  */
+  line_info ();
+
+  /* Return true when NEEDLE is one of basic blocks the line belongs to.  */
+  bool has_block (block_t *needle);
+
+  /* Execution count.  */
+  gcov_type count;
+
+  /* Branches from blocks that end on this line.  */
+  vector<arc_t *> branches;
+
+  /* blocks which start on this line.  Used in all-blocks mode.  */
+  vector<block_t *> blocks;
+
+  unsigned exists : 1;
+  unsigned unexceptional : 1;
+  unsigned has_unexecuted_block : 1;
+};
+
+line_info::line_info (): count (0), branches (), blocks (), exists (false),
+  unexceptional (0), has_unexecuted_block (0)
+{
+}
+
+bool
+line_info::has_block (block_t *needle)
+{
+  return std::find (blocks.begin (), blocks.end (), needle) != blocks.end ();
+}
+
 /* Describes a single function. Contains an array of basic blocks.  */
 
 typedef struct function_info
@@ -190,6 +228,10 @@ typedef struct function_info
   function_info ();
   ~function_info ();
 
+  /* Return true when line N belongs to the function in source file SRC_IDX.
+     The line must be defined in body of the function, can't be inlined.  */
+  bool group_line_p (unsigned n, unsigned src_idx);
+
   /* Name of function.  */
   char *name;
   char *demangled_name;
@@ -200,6 +242,13 @@ typedef struct function_info
   /* The graph contains at least one fake incoming edge.  */
   unsigned has_catch : 1;
 
+  /* True when the function is artificial and does not exist
+     in a source file.  */
+  unsigned artificial : 1;
+
+  /* True when multiple functions start at a line in a source file.  */
+  unsigned is_group : 1;
+
   /* Array of basic blocks.  Like in GCC, the entry block is
      at blocks[0] and the exit block is at blocks[1].  */
 #define ENTRY_BLOCK (0)
@@ -211,17 +260,39 @@ typedef struct function_info
   gcov_type *counts;
   unsigned num_counts;
 
-  /* First line number & file.  */
-  unsigned line;
+  /* First line number.  */
+  unsigned start_line;
+
+  /* First line column.  */
+  unsigned start_column;
+
+  /* Last line number.  */
+  unsigned end_line;
+
+  /* Index of source file where the function is defined.  */
   unsigned src;
 
-  /* Next function in same source file.  */
-  struct function_info *next_file_fn;
+  /* Vector of line information.  */
+  vector<line_info> lines;
 
   /* Next function.  */
   struct function_info *next;
 } function_t;
 
+/* Function info comparer that will sort functions according to starting
+   line.  */
+
+struct function_line_start_cmp
+{
+  inline bool operator() (const function_info *lhs,
+			  const function_info *rhs)
+    {
+      return (lhs->start_line == rhs->start_line
+	      ? lhs->start_column < rhs->start_column
+	      : lhs->start_line < rhs->start_line);
+    }
+};
+
 /* Describes coverage of a file or function.  */
 
 typedef struct coverage_info
@@ -239,42 +310,6 @@ typedef struct coverage_info
   char *name;
 } coverage_t;
 
-/* Describes a single line of source. Contains a chain of basic blocks
-   with code on it.  */
-
-struct line_info
-{
-  /* Default constructor.  */
-  line_info ();
-
-  /* Return true when NEEDLE is one of basic blocks the line belongs to.  */
-  bool has_block (block_t *needle);
-
-  /* Execution count.  */
-  gcov_type count;
-
-  /* Branches from blocks that end on this line.  */
-  vector<arc_t *> branches;
-
-  /* blocks which start on this line.  Used in all-blocks mode.  */
-  vector<block_t *> blocks;
-
-  unsigned exists : 1;
-  unsigned unexceptional : 1;
-  unsigned has_unexecuted_block : 1;
-};
-
-line_info::line_info (): count (0), branches (), blocks (), exists (false),
-  unexceptional (0), has_unexecuted_block (0)
-{
-}
-
-bool
-line_info::has_block (block_t *needle)
-{
-  return std::find (blocks.begin (), blocks.end (), needle) != blocks.end ();
-}
-
 /* Describes a file mentioned in the block graph.  Contains an array
    of line info.  */
 
@@ -283,6 +318,11 @@ struct source_info
   /* Default constructor.  */
   source_info ();
 
+  vector<function_t *> get_functions_at_location (unsigned line_num) const;
+
+  /* Index of the source_info in sources vector.  */
+  unsigned index;
+
   /* Canonical name of source file.  */
   char *name;
   time_t file_time;
@@ -294,14 +334,31 @@ struct source_info
 
   /* Functions in this source file.  These are in ascending line
      number order.  */
-  function_t *functions;
+  vector <function_t *> functions;
 };
 
-source_info::source_info (): name (NULL), file_time (), lines (),
-  coverage (), functions (NULL)
+source_info::source_info (): index (0), name (NULL), file_time (),
+  lines (), coverage (), functions ()
 {
 }
 
+vector<function_t *>
+source_info::get_functions_at_location (unsigned line_num) const
+{
+  vector<function_t *> r;
+
+  for (vector<function_t *>::const_iterator it = functions.begin ();
+       it != functions.end (); it++)
+    {
+      if ((*it)->start_line == line_num && (*it)->src == index)
+	r.push_back (*it);
+    }
+
+  std::sort (r.begin (), r.end (), function_line_start_cmp ());
+
+  return r;
+}
+
 class name_map
 {
 public:
@@ -495,8 +552,9 @@ extern int main (int, char **);
 
 function_info::function_info (): name (NULL), demangled_name (NULL),
   ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0),
+  artificial (0), is_group (0),
   blocks (), blocks_executed (0), counts (NULL), num_counts (0),
-  line (0), src (0), next_file_fn (NULL), next (NULL)
+  start_line (0), start_column (0), end_line (0), src (0), lines (), next (NULL)
 {
 }
 
@@ -518,6 +576,11 @@ function_info::~function_info ()
   free (name);
 }
 
+bool function_info::group_line_p (unsigned n, unsigned src_idx)
+{
+  return is_group && src == src_idx && start_line <= n && n <= end_line;
+}
+
 /* Cycle detection!
    There are a bajillion algorithms that do this.  Boost's function is named
    hawick_cycles, so I used the algorithm by K. A. Hawick and H. A. James in
@@ -889,6 +952,42 @@ process_args (int argc, char **argv)
   return optind;
 }
 
+/* Output intermediate LINE sitting on LINE_NUM to output file F.  */
+
+static void
+output_intermediate_line (FILE *f, line_info *line, unsigned line_num)
+{
+  if (!line->exists)
+    return;
+
+  fprintf (f, "lcount:%u,%s,%d\n", line_num,
+	   format_gcov (line->count, 0, -1),
+	   line->has_unexecuted_block);
+
+  vector<arc_t *>::const_iterator it;
+  if (flag_branches)
+    for (it = line->branches.begin (); it != line->branches.end ();
+	 it++)
+      {
+	if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
+	  {
+	    const char *branch_type;
+	    /* branch:<line_num>,<branch_coverage_type>
+	       branch_coverage_type
+	       : notexec (Branch not executed)
+	       : taken (Branch executed and taken)
+	       : nottaken (Branch executed, but not taken)
+	       */
+	    if ((*it)->src->count)
+		 branch_type
+			= ((*it)->count > 0) ? "taken" : "nottaken";
+	    else
+	      branch_type = "notexec";
+	    fprintf (f, "branch:%d,%s\n", line_num, branch_type);
+	  }
+      }
+}
+
 /* Output the result in intermediate format used by 'lcov'.
 
 The intermediate format contains a single file named 'foo.cc.gcov',
@@ -902,50 +1001,95 @@ file 'foo.cc.gcov' similar to the above example. */
 static void
 output_intermediate_file (FILE *gcov_file, source_info *src)
 {
-  unsigned line_num;    /* current line number.  */
-  const line_info *line;   /* current line info ptr.  */
-  function_t *fn;       /* current function info ptr. */
-
   fprintf (gcov_file, "file:%s\n", src->name);    /* source file name */
 
-  for (fn = src->functions; fn; fn = fn->next_file_fn)
+  std::sort (src->functions.begin (), src->functions.end (),
+	     function_line_start_cmp ());
+  for (vector<function_t *>::iterator it = src->functions.begin ();
+       it != src->functions.end (); it++)
     {
       /* function:<name>,<line_number>,<execution_count> */
-      fprintf (gcov_file, "function:%d,%s,%s\n", fn->line,
-	       format_gcov (fn->blocks[0].count, 0, -1),
-	       flag_demangled_names ? fn->demangled_name : fn->name);
+      fprintf (gcov_file, "function:%d,%d,%s,%s\n", (*it)->start_line,
+	       (*it)->end_line, format_gcov ((*it)->blocks[0].count, 0, -1),
+	       flag_demangled_names ? (*it)->demangled_name : (*it)->name);
     }
 
-  for (line_num = 1, line = &src->lines[line_num];
-       line_num < src->lines.size ();
-       line_num++, line++)
+  for (unsigned line_num = 0; line_num <= src->lines.size (); line_num++)
     {
-      if (line->exists)
-	fprintf (gcov_file, "lcount:%u,%s,%d\n", line_num,
-		 format_gcov (line->count, 0, -1), line->has_unexecuted_block);
-      if (flag_branches)
-	for (vector<arc_t *>::const_iterator it = line->branches.begin ();
-	     it != line->branches.end (); it++)
-	  {
-	    if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
-	      {
-		const char *branch_type;
-		/* branch:<line_num>,<branch_coverage_type>
-		   branch_coverage_type
-		     : notexec (Branch not executed)
-		     : taken (Branch executed and taken)
-		     : nottaken (Branch executed, but not taken)
-		*/
-		if ((*it)->src->count)
-		  branch_type = ((*it)->count > 0) ? "taken" : "nottaken";
-		else
-		  branch_type = "notexec";
-		fprintf (gcov_file, "branch:%d,%s\n", line_num, branch_type);
-	      }
-	  }
+      vector<function_t *> fns = src->get_functions_at_location (line_num);
+
+      /* Print first group functions that begin on the line.  */
+      for (vector<function_t *>::iterator it2 = fns.begin ();
+	   it2 != fns.end (); it2++)
+	{
+	  vector<line_info> &lines = (*it2)->lines;
+	  for (unsigned i = 0; i < lines.size (); i++)
+	    {
+	      line_info *line = &lines[i];
+	      output_intermediate_line (gcov_file, line, line_num + i);
+	    }
+	}
+
+      /* Follow with lines associated with the source file.  */
+      output_intermediate_line (gcov_file, &src->lines[line_num], line_num);
     }
 }
 
+/* Function start pair.  */
+struct function_start
+{
+  unsigned source_file_idx;
+  unsigned start_line;
+};
+
+/* Traits class for function start hash maps below.  */
+
+struct function_start_pair_hash : typed_noop_remove <function_start>
+{
+  typedef function_start value_type;
+  typedef function_start compare_type;
+
+  static hashval_t
+  hash (const function_start &ref)
+  {
+    inchash::hash hstate (0);
+    hstate.add_int (ref.source_file_idx);
+    hstate.add_int (ref.start_line);
+    return hstate.end ();
+  }
+
+  static bool
+  equal (const function_start &ref1, const function_start &ref2)
+  {
+    return (ref1.source_file_idx == ref2.source_file_idx
+	    && ref1.start_line == ref2.start_line);
+  }
+
+  static void
+  mark_deleted (function_start &ref)
+  {
+    ref.start_line = ~1U;
+  }
+
+  static void
+  mark_empty (function_start &ref)
+  {
+    ref.start_line = ~2U;
+  }
+
+  static bool
+  is_deleted (const function_start &ref)
+  {
+    return ref.start_line == ~1U;
+  }
+
+  static bool
+  is_empty (const function_start &ref)
+  {
+    return ref.start_line == ~2U;
+  }
+};
+
 /* Process a single input file.  */
 
 static void
@@ -959,6 +1103,28 @@ process_file (const char *file_name)
     return;
 
   read_count_file (fns);
+
+  hash_map<function_start_pair_hash, function_t *> fn_map;
+
+  /* Identify group functions.  */
+  for (function_t *f = fns; f; f = f->next)
+    if (!f->artificial)
+      {
+	function_start needle;
+	needle.source_file_idx = f->src;
+	needle.start_line = f->start_line;
+
+	function_t **slot = fn_map.get (needle);
+	if (slot)
+	  {
+	    gcc_assert ((*slot)->end_line == f->end_line);
+	    (*slot)->is_group = 1;
+	    f->is_group = 1;
+	  }
+	else
+	  fn_map.put (needle, f);
+      }
+
   while (fns)
     {
       function_t *fn = fns;
@@ -968,46 +1134,50 @@ process_file (const char *file_name)
       if (fn->counts || no_data_file)
 	{
 	  unsigned src = fn->src;
-	  unsigned line = fn->line;
 	  unsigned block_no;
-	  function_t *probe, **prev;
-
-	  /* 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.  Note we're
-	     building this list in reverse order.  */
-	  for (prev = &sources[src].functions;
-	       (probe = *prev); prev = &probe->next_file_fn)
-	    if (probe->line <= line)
-	      break;
-	  fn->next_file_fn = probe;
-	  *prev = fn;
 
-	  /* Mark last line in files touched by function.  */
-	  for (block_no = 0; block_no != fn->blocks.size (); block_no++)
+	  /* Process only non-artificial functions.  */
+	  if (!fn->artificial)
 	    {
-	      block_t *block = &fn->blocks[block_no];
-	      for (unsigned i = 0; i < block->locations.size (); i++)
-		{
-		  unsigned s = block->locations[i].source_file_idx;
-
-		  /* Sort lines of locations.  */
-		  sort (block->locations[i].lines.begin (),
-			block->locations[i].lines.end ());
+	      source_info *s = &sources[src];
+	      s->functions.push_back (fn);
 
-		  if (!block->locations[i].lines.empty ())
+	      /* Mark last line in files touched by function.  */
+	      for (block_no = 0; block_no != fn->blocks.size (); block_no++)
+		{
+		  block_t *block = &fn->blocks[block_no];
+		  for (unsigned i = 0; i < block->locations.size (); i++)
 		    {
-		      unsigned last_line
-			= block->locations[i].lines.back () + 1;
-		      if (last_line > sources[s].lines.size ())
-			sources[s].lines.resize (last_line);
+		      /* Sort lines of locations.  */
+		      sort (block->locations[i].lines.begin (),
+			    block->locations[i].lines.end ());
+
+		      if (!block->locations[i].lines.empty ())
+			{
+			  s = &sources[block->locations[i].source_file_idx];
+			  unsigned last_line
+			    = block->locations[i].lines.back ();
+
+			  /* Record new lines for the function.  */
+			  if (last_line >= s->lines.size ())
+			    {
+			      /* Record new lines for a source file.  */
+			      s->lines.resize (last_line + 1);
+			    }
+			}
 		    }
+
+		  /* Allocate lines for group function, following start_line
+		     and end_line information of the function.  */
+		  if (fn->is_group)
+		    fn->lines.resize (fn->end_line - fn->start_line + 1);
 		}
+
+	      solve_flow_graph (fn);
+	      if (fn->has_catch)
+		find_exception_blocks (fn);
 	    }
 
-	  solve_flow_graph (fn);
-	  if (fn->has_catch)
-	    find_exception_blocks (fn);
 	  *fn_end = fn;
 	  fn_end = &fn->next;
 	}
@@ -1057,6 +1227,8 @@ generate_results (const char *file_name)
   for (fn = functions; fn; fn = fn->next)
     {
       coverage_t coverage;
+      if (fn->artificial)
+	continue;
 
       memset (&coverage, 0, sizeof (coverage));
       coverage.name = flag_demangled_names ? fn->demangled_name : fn->name;
@@ -1237,6 +1409,7 @@ find_source (const char *file_name)
       src = &sources.back ();
       src->name = canon;
       src->coverage.name = src->name;
+      src->index = idx;
       if (source_length
 #if HAVE_DOS_BASED_FILE_SYSTEM
 	  /* You lose if separators don't match exactly in the
@@ -1328,15 +1501,18 @@ read_graph_file (void)
       if (tag == GCOV_TAG_FUNCTION)
 	{
 	  char *function_name;
-	  unsigned ident, lineno;
+	  unsigned ident;
 	  unsigned lineno_checksum, cfg_checksum;
 
 	  ident = gcov_read_unsigned ();
 	  lineno_checksum = gcov_read_unsigned ();
 	  cfg_checksum = gcov_read_unsigned ();
 	  function_name = xstrdup (gcov_read_string ());
+	  unsigned artificial = gcov_read_unsigned ();
 	  unsigned src_idx = find_source (gcov_read_string ());
-	  lineno = gcov_read_unsigned ();
+	  unsigned start_line = gcov_read_unsigned ();
+	  unsigned start_column = gcov_read_unsigned ();
+	  unsigned end_line = gcov_read_unsigned ();
 
 	  fn = new function_t;
 	  fn->name = function_name;
@@ -1350,9 +1526,11 @@ read_graph_file (void)
 	  fn->lineno_checksum = lineno_checksum;
 	  fn->cfg_checksum = cfg_checksum;
 	  fn->src = src_idx;
-	  fn->line = lineno;
+	  fn->start_line = start_line;
+	  fn->start_column = start_column;
+	  fn->end_line = end_line;
+	  fn->artificial = artificial;
 
-	  fn->next_file_fn = NULL;
 	  fn->next = NULL;
 	  *fns_end = fn;
 	  fns_end = &fn->next;
@@ -2266,48 +2444,66 @@ add_line_counts (coverage_t *coverage, function_t *fn)
 	fn->blocks_executed++;
       for (unsigned i = 0; i < block->locations.size (); i++)
 	{
-	  source_info *src = &sources[block->locations[i].source_file_idx];
-
+	  unsigned src_idx = block->locations[i].source_file_idx;
 	  vector<unsigned> &lines = block->locations[i].lines;
+
+	  block->cycle.arc = NULL;
+	  block->cycle.ident = ~0U;
+
 	  for (unsigned j = 0; j < lines.size (); j++)
 	    {
-	      line = &src->lines[lines[j]];
-	      if (coverage)
+	      unsigned ln = lines[j];
+
+	      /* Line belongs to a function that is in a group.  */
+	      if (fn->group_line_p (ln, src_idx))
 		{
-		  if (!line->exists)
-		    coverage->lines++;
-		  if (!line->count && block->count)
-		    coverage->lines_executed++;
+		  gcc_assert (lines[j] - fn->start_line < fn->lines.size ());
+		  line = &(fn->lines[lines[j] - fn->start_line]);
+		  line->exists = 1;
+		  if (!block->exceptional)
+		    {
+		      line->unexceptional = 1;
+		      if (block->count == 0)
+			line->has_unexecuted_block = 1;
+		    }
+		  line->count += block->count;
 		}
-	      line->exists = 1;
-	      if (!block->exceptional)
+	      else
 		{
-		  line->unexceptional = 1;
-		  if (block->count == 0)
-		    line->has_unexecuted_block = 1;
+		  gcc_assert (ln < sources[src_idx].lines.size ());
+		  line = &(sources[src_idx].lines[ln]);
+		  if (coverage)
+		    {
+		      if (!line->exists)
+			coverage->lines++;
+		      if (!line->count && block->count)
+			coverage->lines_executed++;
+		    }
+		  line->exists = 1;
+		  if (!block->exceptional)
+		    {
+		      line->unexceptional = 1;
+		      if (block->count == 0)
+			line->has_unexecuted_block = 1;
+		    }
+		  line->count += block->count;
 		}
-	      line->count += block->count;
 	    }
-	}
-      block->cycle.arc = NULL;
-      block->cycle.ident = ~0U;
-      has_any_line = true;
 
-      if (!ix || ix + 1 == fn->blocks.size ())
-	/* Entry or exit block */;
-      else if (line != NULL)
-	{
-	  line->blocks.push_back (block);
+	  has_any_line = true;
 
-	  if (flag_branches)
+	  if (!ix || ix + 1 == fn->blocks.size ())
+	    /* Entry or exit block.  */;
+	  else if (line != NULL)
 	    {
-	      arc_t *arc;
+	      line->blocks.push_back (block);
 
-	      for (arc = block->succ; arc; arc = arc->succ_next)
+	      if (flag_branches)
 		{
-		  line->branches.push_back (arc);
-		  if (coverage && !arc->is_unconditional)
-		    add_branch_counts (coverage, arc);
+		  arc_t *arc;
+
+		  for (arc = block->succ; arc; arc = arc->succ_next)
+		    line->branches.push_back (arc);
 		}
 	    }
 	}
@@ -2317,72 +2513,113 @@ add_line_counts (coverage_t *coverage, function_t *fn)
     fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name);
 }
 
+/* Accumulate info for LINE that belongs to SRC source file.  If ADD_COVERAGE
+   is set to true, update source file summary.  */
+
+static void accumulate_line_info (line_info *line, source_info *src,
+				  bool add_coverage)
+{
+  if (add_coverage)
+    for (vector<arc_info *>::iterator it = line->branches.begin ();
+	 it != line->branches.end (); it++)
+      add_branch_counts (&src->coverage, *it);
+
+  if (!line->blocks.empty ())
+    {
+      /* 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.  */
+      gcov_type count = 0;
+
+      /* Cycle detection.  */
+      for (vector<block_t *>::iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next)
+	    if (!line->has_block (arc->src))
+	      count += arc->count;
+	  for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next)
+	    arc->cs_count = arc->count;
+	}
+
+      /* Now, add the count of loops entirely on this line.  */
+      count += get_cycles_count (*line);
+      line->count = count;
+    }
+
+  if (line->exists && add_coverage)
+    {
+      src->coverage.lines++;
+      if (line->count)
+	src->coverage.lines_executed++;
+    }
+}
+
 /* Accumulate the line counts of a file.  */
 
 static void
 accumulate_line_counts (source_info *src)
 {
-  function_t *fn, *fn_p, *fn_n;
-  unsigned ix = 0;
-
-  /* Reverse the function order.  */
-  for (fn = src->functions, fn_p = NULL; fn; fn_p = fn, fn = fn_n)
+  /* First work on group functions.  */
+  for (vector<function_t *>::iterator it = src->functions.begin ();
+       it != src->functions.end (); it++)
     {
-      fn_n = fn->next_file_fn;
-      fn->next_file_fn = fn_p;
+      function_info *fn = *it;
+
+      if (fn->src != src->index || !fn->is_group)
+	continue;
+
+      for (vector<line_info>::iterator it2 = fn->lines.begin ();
+	   it2 != fn->lines.end (); it2++)
+	  {
+	    line_info *line = &(*it2);
+	    accumulate_line_info (line, src, false);
+	  }
     }
-  src->functions = fn_p;
 
-  for (vector<line_info>::reverse_iterator it = src->lines.rbegin ();
-       it != src->lines.rend (); it++)
-    {
-      line_info *line = &(*it);
-      if (!line->blocks.empty ())
-	{
-	  /* 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.  */
-	  gcov_type count = 0;
-
-	  /* Sum the entry arcs.  */
-	  for (vector<block_t *>::iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
-	    {
-	      arc_t *arc;
+  /* Work on global lines that line in source file SRC.  */
+  for (vector<line_info>::iterator it = src->lines.begin ();
+       it != src->lines.end (); it++)
+    accumulate_line_info (&(*it), src, true);
 
-	      for (arc = (*it)->pred; arc; arc = arc->pred_next)
-		if (flag_branches)
-		  add_branch_counts (&src->coverage, arc);
-	    }
+  /* If not using intermediate mode, sum lines of group functions and
+     add them to lines that live in a source file.  */
+  if (!flag_intermediate_format)
+    for (vector<function_t *>::iterator it = src->functions.begin ();
+	 it != src->functions.end (); it++)
+      {
+	function_info *fn = *it;
 
-	  /* Cycle detection.  */
-	  for (vector<block_t *>::iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
-	    {
-	      for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next)
-		if (!line->has_block (arc->src))
-		  count += arc->count;
-	      for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next)
-		arc->cs_count = arc->count;
-	    }
+	if (fn->src != src->index || !fn->is_group)
+	  continue;
 
-	  /* Now, add the count of loops entirely on this line.  */
-	  count += get_cycles_count (*line);
-	  line->count = count;
-	}
+	for (unsigned i = 0; i < fn->lines.size (); i++)
+	  {
+	    line_info *fn_line = &fn->lines[i];
+	    if (fn_line->exists)
+	      {
+		unsigned ln = fn->start_line + i;
+		line_info *src_line = &src->lines[ln];
 
-      if (line->exists)
-	{
-	  src->coverage.lines++;
-	  if (line->count)
-	    src->coverage.lines_executed++;
-	}
+		if (!src_line->exists)
+		  src->coverage.lines++;
+		if (!src_line->count && fn_line->count)
+		  src->coverage.lines_executed++;
 
-      ix++;
-    }
+		src_line->count += fn_line->count;
+		src_line->exists = 1;
+
+		if (fn_line->has_unexecuted_block)
+		  src_line->has_unexecuted_block = 1;
+
+		if (fn_line->unexceptional)
+		  src_line->unexceptional = 1;
+	      }
+	  }
+      }
 }
 
 /* Output information about ARC number IX.  Returns nonzero if
@@ -2500,7 +2737,8 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional,
 	      if (flag_use_colors)
 		{
 		  pad_count_string (s);
-		  s = SGR_SEQ (COLOR_BG_MAGENTA COLOR_SEPARATOR COLOR_FG_WHITE);
+		  s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA
+					COLOR_SEPARATOR COLOR_FG_WHITE));
 		  s += SGR_RESET;
 		}
 	      else
@@ -2538,6 +2776,86 @@ output_line_beginning (FILE *f, bool exists, bool unexceptional,
   fprintf (f, "%s:%5u", s.c_str (), line_num);
 }
 
+static void
+print_source_line (FILE *f, const vector<const char *> &source_lines,
+		   unsigned line)
+{
+  gcc_assert (line >= 1);
+  gcc_assert (line <= source_lines.size ());
+
+  fprintf (f, ":%s\n", source_lines[line - 1]);
+}
+
+/* Output line details for LINE and print it to F file.  LINE lives on
+   LINE_NUM.  */
+
+static void
+output_line_details (FILE *f, const line_info *line, unsigned line_num)
+{
+  if (flag_all_blocks)
+    {
+      arc_t *arc;
+      int ix, jx;
+
+      ix = jx = 0;
+      for (vector<block_t *>::const_iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  if (!(*it)->is_call_return)
+	    {
+	      output_line_beginning (f, line->exists,
+				     (*it)->exceptional, false,
+				     (*it)->count, line_num,
+				     "%%%%%", "$$$$$");
+	      fprintf (f, "-block %2d", ix++);
+	      if (flag_verbose)
+		fprintf (f, " (BB %u)", (*it)->id);
+	      fprintf (f, "\n");
+	    }
+	  if (flag_branches)
+	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
+	      jx += output_branch_count (f, jx, arc);
+	}
+    }
+  else if (flag_branches)
+    {
+      int ix;
+
+      ix = 0;
+      for (vector<arc_t *>::const_iterator it = line->branches.begin ();
+	   it != line->branches.end (); it++)
+	ix += output_branch_count (f, ix, (*it));
+    }
+}
+
+/* Output detail statistics about function FN to file F.  */
+
+static void
+output_function_details (FILE *f, const function_info *fn)
+{
+  if (!flag_branches)
+    return;
+
+  arc_t *arc = fn->blocks[EXIT_BLOCK].pred;
+  gcov_type return_count = fn->blocks[EXIT_BLOCK].count;
+  gcov_type called_count = fn->blocks[ENTRY_BLOCK].count;
+
+  for (; arc; arc = arc->pred_next)
+    if (arc->fake)
+      return_count -= arc->count;
+
+  fprintf (f, "function %s",
+	   flag_demangled_names ? fn->demangled_name : fn->name);
+  fprintf (f, " called %s",
+	   format_gcov (called_count, 0, -1));
+  fprintf (f, " returned %s",
+	   format_gcov (return_count, called_count, 0));
+  fprintf (f, " blocks executed %s",
+	   format_gcov (fn->blocks_executed, fn->blocks.size () - 2,
+			0));
+  fprintf (f, "\n");
+}
+
 /* 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.  */
@@ -2546,12 +2864,10 @@ static void
 output_lines (FILE *gcov_file, const source_info *src)
 {
 #define  DEFAULT_LINE_START "        -:    0:"
+#define FN_SEPARATOR "------------------\n"
 
   FILE *source_file;
-  unsigned line_num;	/* current line number.  */
-  const line_info *line;  /* current line info ptr.  */
-  const char *retval = "";	/* status of source file reading.  */
-  function_t *fn = NULL;
+  const char *retval;
 
   fprintf (gcov_file, DEFAULT_LINE_START "Source:%s\n", src->coverage.name);
   if (!multiple_files)
@@ -2565,43 +2881,40 @@ output_lines (FILE *gcov_file, const source_info *src)
 
   source_file = fopen (src->name, "r");
   if (!source_file)
-    {
-      fnotice (stderr, "Cannot open source file %s\n", src->name);
-      retval = NULL;
-    }
+    fnotice (stderr, "Cannot open source file %s\n", src->name);
   else if (src->file_time == 0)
     fprintf (gcov_file, DEFAULT_LINE_START "Source is newer than graph\n");
 
-  if (flag_branches)
-    fn = src->functions;
+  vector<const char *> source_lines;
+  if (source_file)
+    while ((retval = read_line (source_file)) != NULL)
+      source_lines.push_back (xstrdup (retval));
 
-  for (line_num = 1, line = &src->lines[line_num];
-       line_num < src->lines.size (); line_num++, line++)
+  unsigned line_start_group = 0;
+  vector<function_t *> fns;
+
+  for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++)
     {
-      for (; fn && fn->line == line_num; fn = fn->next_file_fn)
+      if (line_num >= src->lines.size ())
 	{
-	  arc_t *arc = fn->blocks[EXIT_BLOCK].pred;
-	  gcov_type return_count = fn->blocks[EXIT_BLOCK].count;
-	  gcov_type called_count = fn->blocks[ENTRY_BLOCK].count;
-
-	  for (; arc; arc = arc->pred_next)
-	    if (arc->fake)
-	      return_count -= arc->count;
-
-	  fprintf (gcov_file, "function %s", flag_demangled_names ?
-                   fn->demangled_name : fn->name);
-	  fprintf (gcov_file, " called %s",
-		   format_gcov (called_count, 0, -1));
-	  fprintf (gcov_file, " returned %s",
-		   format_gcov (return_count, called_count, 0));
-	  fprintf (gcov_file, " blocks executed %s",
-		   format_gcov (fn->blocks_executed, fn->blocks.size () - 2,
-				0));
-	  fprintf (gcov_file, "\n");
+	  fprintf (gcov_file, "%9s:%5u", "-", line_num);
+	  print_source_line (gcov_file, source_lines, line_num);
+	  continue;
 	}
 
-      if (retval)
-	retval = read_line (source_file);
+      const line_info *line = &src->lines[line_num];
+
+      if (line_start_group == 0)
+	{
+	  fns = src->get_functions_at_location (line_num);
+	  if (fns.size () > 1)
+	    line_start_group = fns[0]->end_line;
+	  else if (fns.size () == 1)
+	    {
+	      function_t *fn = fns[0];
+	      output_function_details (gcov_file, fn);
+	    }
+	}
 
       /* For lines which don't exist in the .bb file, print '-' before
 	 the source line.  For lines which exist but were never
@@ -2610,54 +2923,64 @@ output_lines (FILE *gcov_file, const source_info *src)
 	 There are 16 spaces of indentation added before the source
 	 line so that tabs won't be messed up.  */
       output_line_beginning (gcov_file, line->exists, line->unexceptional,
-			     line->has_unexecuted_block, line->count, line_num,
-			     "=====", "#####");
-      fprintf (gcov_file, ":%s\n", retval ? retval : "/*EOF*/");
+			     line->has_unexecuted_block, line->count,
+			     line_num, "=====", "#####");
 
-      if (flag_all_blocks)
-	{
-	  arc_t *arc;
-	  int ix, jx;
+      print_source_line (gcov_file, source_lines, line_num);
+      output_line_details (gcov_file, line, line_num);
 
-	  ix = jx = 0;
-	  for (vector<block_t *>::const_iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
+      if (line_start_group == line_num)
+	{
+	  for (vector<function_t *>::iterator it = fns.begin ();
+	       it != fns.end (); it++)
 	    {
-	      if (!(*it)->is_call_return)
+	      function_info *fn = *it;
+	      vector<line_info> &lines = fn->lines;
+
+	      fprintf (gcov_file, FN_SEPARATOR);
+
+	      string fn_name
+		= flag_demangled_names ? fn->demangled_name : fn->name;
+
+	      if (flag_use_colors)
 		{
+		  fn_name.insert (0, SGR_SEQ (COLOR_FG_CYAN));
+		  fn_name += SGR_RESET;
+		}
+
+	      fprintf (gcov_file, "%s:\n", fn_name.c_str ());
+
+	      output_function_details (gcov_file, fn);
+
+	      /* Print all lines covered by the function.  */
+	      for (unsigned i = 0; i < lines.size (); i++)
+		{
+		  line_info *line = &lines[i];
+		  unsigned l = fn->start_line + i;
+
+		  /* For lines which don't exist in the .bb file, print '-'
+		     before the source line.  For lines which exist but
+		     were never executed, print '#####' or '=====' 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.  */
 		  output_line_beginning (gcov_file, line->exists,
-					 (*it)->exceptional, false,
-					 (*it)->count, line_num,
-					 "%%%%%", "$$$$$");
-		  fprintf (gcov_file, "-block %2d", ix++);
-		  if (flag_verbose)
-		    fprintf (gcov_file, " (BB %u)", (*it)->id);
-		  fprintf (gcov_file, "\n");
+					 line->unexceptional,
+					 line->has_unexecuted_block,
+					 line->count,
+					 l, "=====", "#####");
+
+		  print_source_line (gcov_file, source_lines, l);
+		  output_line_details (gcov_file, line, l);
 		}
-	      if (flag_branches)
-		for (arc = (*it)->succ; arc; arc = arc->succ_next)
-		  jx += output_branch_count (gcov_file, jx, arc);
 	    }
-	}
-      else if (flag_branches)
-	{
-	  int ix;
 
-	  ix = 0;
-	  for (vector<arc_t *>::const_iterator it = line->branches.begin ();
-	       it != line->branches.end (); it++)
-	    ix += output_branch_count (gcov_file, ix, (*it));
+	  fprintf (gcov_file, FN_SEPARATOR);
+	  line_start_group = 0;
 	}
     }
 
-  /* Handle all remaining source lines.  There may be lines after the
-     last line of code.  */
-  if (retval)
-    {
-      for (; (retval = read_line (source_file)); line_num++)
-	fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval);
-    }
-
   if (source_file)
     fclose (source_file);
 }
Nathan Sidwell Nov. 8, 2017, 2:57 p.m. UTC | #4
On 11/08/2017 06:03 AM, Martin Liška wrote:
> On 11/07/2017 03:49 PM, Nathan Sidwell wrote:
>> On 11/07/2017 05:53 AM, Martin Liška wrote:
>>> Hello.
>>>
>>> This is slightly updated version from the previous. Various small issues were fixed
>>> and I update documentation in order to reflect the changes.
>>
>>> +  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
>>>     gcov_write_filename (xloc.file);
>>>     gcov_write_unsigned (xloc.line);
>>> +  gcov_write_unsigned (expand_location (cfun->function_end_locus).line);
>>>     gcov_write_length (offset);
>>
>> this is presuming the end line is in the same file as the start line.  A reasonable assumption, but users can have exciting ideas!  What is the failure mode if the function straddles a file boundary?
> 
> Hi.
> 
> I decided to fix that with change that set line_end = line_start if line_end < line_start.
> That survives reasonably well with cases like this:

> +  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
>    gcov_write_filename (xloc.file);
>    gcov_write_unsigned (xloc.line);
> +  gcov_write_unsigned (xloc.column);
> +
> +  /* Function can start in a single file and end in another one.  */
> +  int fn_end_line = expand_location (cfun->function_end_locus).line;
> +  gcov_write_unsigned (fn_end_line > xloc.line ? fn_end_line : xloc.line);

sorry to be picky, but of course the end line could be greater than the 
start. but in a different file.  Please check file too.


>> This isn't stable if they start on the same line.  Will output order depend on the vaguaries of the sorting algorithm?
>>
>>> +      vector<line_info> &lines = (*it2)->lines;
>>> +      /* Print all lines covered by the function.  */
>>> +      for (unsigned i = 0; i < lines.size (); i++)
> 
> So fixed by introduction of line column that is used for sorting as well.

thanks.
> May I understand the reply as ACK?

Patch ok, with the above check for file names added.

nathan
Martin Liška Nov. 9, 2017, 9:12 a.m. UTC | #5
On 11/08/2017 03:57 PM, Nathan Sidwell wrote:
> On 11/08/2017 06:03 AM, Martin Liška wrote:
>> On 11/07/2017 03:49 PM, Nathan Sidwell wrote:
>>> On 11/07/2017 05:53 AM, Martin Liška wrote:
>>>> Hello.
>>>>
>>>> This is slightly updated version from the previous. Various small issues were fixed
>>>> and I update documentation in order to reflect the changes.
>>>
>>>> +  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
>>>>     gcov_write_filename (xloc.file);
>>>>     gcov_write_unsigned (xloc.line);
>>>> +  gcov_write_unsigned (expand_location (cfun->function_end_locus).line);
>>>>     gcov_write_length (offset);
>>>
>>> this is presuming the end line is in the same file as the start line.  A reasonable assumption, but users can have exciting ideas!  What is the failure mode if the function straddles a file boundary?
>>
>> Hi.
>>
>> I decided to fix that with change that set line_end = line_start if line_end < line_start.
>> That survives reasonably well with cases like this:
> 
>> +  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
>>    gcov_write_filename (xloc.file);
>>    gcov_write_unsigned (xloc.line);
>> +  gcov_write_unsigned (xloc.column);
>> +
>> +  /* Function can start in a single file and end in another one.  */
>> +  int fn_end_line = expand_location (cfun->function_end_locus).line;
>> +  gcov_write_unsigned (fn_end_line > xloc.line ? fn_end_line : xloc.line);
> 
> sorry to be picky, but of course the end line could be greater than the start. but in a different file.  Please check file too.

That's fine, thank you for good feedback!

> 
> 
>>> This isn't stable if they start on the same line.  Will output order depend on the vaguaries of the sorting algorithm?
>>>
>>>> +      vector<line_info> &lines = (*it2)->lines;
>>>> +      /* Print all lines covered by the function.  */
>>>> +      for (unsigned i = 0; i < lines.size (); i++)
>>
>> So fixed by introduction of line column that is used for sorting as well.
> 
> thanks.
>> May I understand the reply as ACK?
> 
> Patch ok, with the above check for file names added.
> 
> nathan
> 

Just installed as r254562. I'm planning to apply some small code clean-up
and I'm done :)

Martin
diff mbox series

Patch

From cf05cc4b236d7fd58091a4fd3fd4e88bfee28c00 Mon Sep 17 00:00:00 2001
From: marxin <mliska@suse.cz>
Date: Thu, 26 Oct 2017 10:39:40 +0200
Subject: [PATCH] GCOV: support multiple functions per a line

gcc/ChangeLog:

2017-11-01  Martin Liska  <mliska@suse.cz>

	* coverage.c (coverage_begin_function): Output also end locus
	of a function and information whether the function is
	artificial.
	* gcov-dump.c (tag_function): Parse and print the information.
	* gcov.c (INCLUDE_MAP): Add include.
	(INCLUDE_SET): Likewise.
	(struct line_info): Move earlier in the source file because
	of vector<line_info> in function_info structure.
	(line_info::line_info): Likewise.
	(line_info::has_block): Likewise.
	(struct source_info): Add new member index.
	(source_info::get_functions_at_location): New function.
	(function_info::group_line_p): New function.
	(output_intermediate_line): New function.
	(output_intermediate_file): Use the mentioned function.
	(struct function_start): New.
	(struct function_start_pair_hash): Likewise.
	(process_file): Add code that identifies group functions.
	Assign lines either to global or function scope.
	(generate_results): Skip artificial functions.
	(find_source): Assign index for each source file.
	(read_graph_file): Read new flag artificial and end_line.
	(add_line_counts): Assign it either to global of function scope.
	(accumulate_line_counts): Isolate core of the function to
	accumulate_line_info and call it for both function and global
	scope lines.
	(accumulate_line_info): New function.
	(output_line_beginning): Fix GNU coding style.
	(print_source_line): New function.
	(output_line_details): Likewise.
	(output_function_details): Likewise.
	(output_lines): Iterate both source (global) scope and function
	scope.
---
 gcc/coverage.c  |   2 +
 gcc/gcov-dump.c |   7 +-
 gcc/gcov.c      | 823 ++++++++++++++++++++++++++++++++++++++------------------
 3 files changed, 569 insertions(+), 263 deletions(-)

diff --git a/gcc/coverage.c b/gcc/coverage.c
index 8a56a677f15..8ac593920b6 100644
--- a/gcc/coverage.c
+++ b/gcc/coverage.c
@@ -663,8 +663,10 @@  coverage_begin_function (unsigned lineno_checksum, unsigned cfg_checksum)
   gcov_write_unsigned (cfg_checksum);
   gcov_write_string (IDENTIFIER_POINTER
 		     (DECL_ASSEMBLER_NAME (current_function_decl)));
+  gcov_write_unsigned (DECL_ARTIFICIAL (current_function_decl));
   gcov_write_filename (xloc.file);
   gcov_write_unsigned (xloc.line);
+  gcov_write_unsigned (expand_location (cfun->function_end_locus).line);
   gcov_write_length (offset);
 
   return !gcov_is_error ();
diff --git a/gcc/gcov-dump.c b/gcc/gcov-dump.c
index d24e72ac4a1..22d08c04c1e 100644
--- a/gcc/gcov-dump.c
+++ b/gcc/gcov-dump.c
@@ -308,9 +308,14 @@  tag_function (const char *filename ATTRIBUTE_UNUSED,
 	  
 	  name = gcov_read_string ();
 	  printf (", `%s'", name ? name : "NULL");
+	  unsigned artificial = gcov_read_unsigned ();
 	  name = gcov_read_string ();
 	  printf (" %s", name ? name : "NULL");
-	  printf (":%u", gcov_read_unsigned ());
+	  unsigned line_start = gcov_read_unsigned ();
+	  unsigned line_end = gcov_read_unsigned ();
+	  printf (":%u:%u", line_start, line_end);
+	  if (artificial)
+	    printf (", artificial");
 	}
     }
 }
diff --git a/gcc/gcov.c b/gcc/gcov.c
index f1e7777cf94..9a6c74cd2d6 100644
--- a/gcc/gcov.c
+++ b/gcc/gcov.c
@@ -34,6 +34,8 @@  along with Gcov; see the file COPYING3.  If not see
 #define INCLUDE_ALGORITHM
 #define INCLUDE_VECTOR
 #define INCLUDE_STRING
+#define INCLUDE_MAP
+#define INCLUDE_SET
 #include "system.h"
 #include "coretypes.h"
 #include "tm.h"
@@ -183,6 +185,42 @@  block_info::block_info (): succ (NULL), pred (NULL), num_succ (0), num_pred (0),
   cycle.arc = NULL;
 }
 
+/* Describes a single line of source.  Contains a chain of basic blocks
+   with code on it.  */
+
+struct line_info
+{
+  /* Default constructor.  */
+  line_info ();
+
+  /* Return true when NEEDLE is one of basic blocks the line belongs to.  */
+  bool has_block (block_t *needle);
+
+  /* Execution count.  */
+  gcov_type count;
+
+  /* Branches from blocks that end on this line.  */
+  vector<arc_t *> branches;
+
+  /* blocks which start on this line.  Used in all-blocks mode.  */
+  vector<block_t *> blocks;
+
+  unsigned exists : 1;
+  unsigned unexceptional : 1;
+  unsigned has_unexecuted_block : 1;
+};
+
+line_info::line_info (): count (0), branches (), blocks (), exists (false),
+  unexceptional (0), has_unexecuted_block (0)
+{
+}
+
+bool
+line_info::has_block (block_t *needle)
+{
+  return std::find (blocks.begin (), blocks.end (), needle) != blocks.end ();
+}
+
 /* Describes a single function. Contains an array of basic blocks.  */
 
 typedef struct function_info
@@ -190,6 +228,10 @@  typedef struct function_info
   function_info ();
   ~function_info ();
 
+  /* Return true when line N belongs to the function in source file SRC_IDX.
+     The line must be defined in body of the function, can't be inlined.  */
+  bool group_line_p (unsigned n, unsigned src_idx);
+
   /* Name of function.  */
   char *name;
   char *demangled_name;
@@ -200,6 +242,13 @@  typedef struct function_info
   /* The graph contains at least one fake incoming edge.  */
   unsigned has_catch : 1;
 
+  /* True when the function is artificial and does not exist
+     in a source file.  */
+  unsigned artificial : 1;
+
+  /* True when multiple functions start at a line in a source file.  */
+  unsigned is_group : 1;
+
   /* Array of basic blocks.  Like in GCC, the entry block is
      at blocks[0] and the exit block is at blocks[1].  */
 #define ENTRY_BLOCK (0)
@@ -211,12 +260,17 @@  typedef struct function_info
   gcov_type *counts;
   unsigned num_counts;
 
-  /* First line number & file.  */
-  unsigned line;
+  /* First line number.  */
+  unsigned start_line;
+
+  /* Last line number.  */
+  unsigned end_line;
+
+  /* Index of source file where the function is defined.  */
   unsigned src;
 
-  /* Next function in same source file.  */
-  struct function_info *next_file_fn;
+  /* Vector of line information.  */
+  vector<line_info> lines;
 
   /* Next function.  */
   struct function_info *next;
@@ -239,42 +293,6 @@  typedef struct coverage_info
   char *name;
 } coverage_t;
 
-/* Describes a single line of source. Contains a chain of basic blocks
-   with code on it.  */
-
-struct line_info
-{
-  /* Default constructor.  */
-  line_info ();
-
-  /* Return true when NEEDLE is one of basic blocks the line belongs to.  */
-  bool has_block (block_t *needle);
-
-  /* Execution count.  */
-  gcov_type count;
-
-  /* Branches from blocks that end on this line.  */
-  vector<arc_t *> branches;
-
-  /* blocks which start on this line.  Used in all-blocks mode.  */
-  vector<block_t *> blocks;
-
-  unsigned exists : 1;
-  unsigned unexceptional : 1;
-  unsigned has_unexecuted_block : 1;
-};
-
-line_info::line_info (): count (0), branches (), blocks (), exists (false),
-  unexceptional (0), has_unexecuted_block (0)
-{
-}
-
-bool
-line_info::has_block (block_t *needle)
-{
-  return std::find (blocks.begin (), blocks.end (), needle) != blocks.end ();
-}
-
 /* Describes a file mentioned in the block graph.  Contains an array
    of line info.  */
 
@@ -283,6 +301,11 @@  struct source_info
   /* Default constructor.  */
   source_info ();
 
+  vector<function_t *> get_functions_at_location (unsigned line_num) const;
+
+  /* Index of the source_info in sources vector.  */
+  unsigned index;
+
   /* Canonical name of source file.  */
   char *name;
   time_t file_time;
@@ -294,14 +317,29 @@  struct source_info
 
   /* Functions in this source file.  These are in ascending line
      number order.  */
-  function_t *functions;
+  vector <function_t *> functions;
 };
 
-source_info::source_info (): name (NULL), file_time (), lines (),
-  coverage (), functions (NULL)
+source_info::source_info (): name (NULL), file_time (),
+  lines (), coverage (), functions ()
 {
 }
 
+vector<function_t *>
+source_info::get_functions_at_location (unsigned line_num) const
+{
+  vector<function_t *> r;
+
+  for (vector<function_t *>::const_iterator it = functions.begin ();
+       it != functions.end (); it++)
+    {
+      if ((*it)->start_line == line_num && (*it)->src == index)
+	r.push_back (*it);
+    }
+
+  return r;
+}
+
 class name_map
 {
 public:
@@ -491,8 +529,9 @@  extern int main (int, char **);
 
 function_info::function_info (): name (NULL), demangled_name (NULL),
   ident (0), lineno_checksum (0), cfg_checksum (0), has_catch (0),
+  artificial (0), is_group (0),
   blocks (), blocks_executed (0), counts (NULL), num_counts (0),
-  line (0), src (0), next_file_fn (NULL), next (NULL)
+  start_line (0), end_line (0), src (0), lines (), next (NULL)
 {
 }
 
@@ -514,6 +553,11 @@  function_info::~function_info ()
   free (name);
 }
 
+bool function_info::group_line_p (unsigned n, unsigned src_idx)
+{
+  return is_group && src == src_idx && start_line <= n && n <= end_line;
+}
+
 /* Cycle detection!
    There are a bajillion algorithms that do this.  Boost's function is named
    hawick_cycles, so I used the algorithm by K. A. Hawick and H. A. James in
@@ -880,6 +924,42 @@  process_args (int argc, char **argv)
   return optind;
 }
 
+/* Output intermediate LINE sitting on LINE_NUM to output file F.  */
+
+static void
+output_intermediate_line (FILE *f, line_info *line, unsigned line_num)
+{
+  if (!line->exists)
+    return;
+
+  fprintf (f, "lcount:%u,%s,%d\n", line_num,
+	   format_gcov (line->count, 0, -1),
+	   line->has_unexecuted_block);
+
+  vector<arc_t *>::const_iterator it;
+  if (flag_branches)
+    for (it = line->branches.begin (); it != line->branches.end ();
+	 it++)
+      {
+	if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
+	  {
+	    const char *branch_type;
+	    /* branch:<line_num>,<branch_coverage_type>
+	       branch_coverage_type
+	       : notexec (Branch not executed)
+	       : taken (Branch executed and taken)
+	       : nottaken (Branch executed, but not taken)
+	       */
+	    if ((*it)->src->count)
+		 branch_type
+			= ((*it)->count > 0) ? "taken" : "nottaken";
+	    else
+	      branch_type = "notexec";
+	    fprintf (f, "branch:%d,%s\n", line_num, branch_type);
+	  }
+      }
+}
+
 /* Output the result in intermediate format used by 'lcov'.
 
 The intermediate format contains a single file named 'foo.cc.gcov',
@@ -893,50 +973,94 @@  file 'foo.cc.gcov' similar to the above example. */
 static void
 output_intermediate_file (FILE *gcov_file, source_info *src)
 {
-  unsigned line_num;    /* current line number.  */
-  const line_info *line;   /* current line info ptr.  */
-  function_t *fn;       /* current function info ptr. */
-
   fprintf (gcov_file, "file:%s\n", src->name);    /* source file name */
 
-  for (fn = src->functions; fn; fn = fn->next_file_fn)
+  for (vector<function_t *>::iterator it = src->functions.begin ();
+       it != src->functions.end (); it++)
     {
       /* function:<name>,<line_number>,<execution_count> */
-      fprintf (gcov_file, "function:%d,%s,%s\n", fn->line,
-	       format_gcov (fn->blocks[0].count, 0, -1),
-	       flag_demangled_names ? fn->demangled_name : fn->name);
+      fprintf (gcov_file, "function:%d,%s,%s\n", (*it)->start_line,
+	       format_gcov ((*it)->blocks[0].count, 0, -1),
+	       flag_demangled_names ? (*it)->demangled_name : (*it)->name);
     }
 
-  for (line_num = 1, line = &src->lines[line_num];
-       line_num < src->lines.size ();
-       line_num++, line++)
+  for (unsigned line_num = 0; line_num <= src->lines.size (); line_num++)
     {
-      if (line->exists)
-	fprintf (gcov_file, "lcount:%u,%s,%d\n", line_num,
-		 format_gcov (line->count, 0, -1), line->has_unexecuted_block);
-      if (flag_branches)
-	for (vector<arc_t *>::const_iterator it = line->branches.begin ();
-	     it != line->branches.end (); it++)
-	  {
-	    if (!(*it)->is_unconditional && !(*it)->is_call_non_return)
-	      {
-		const char *branch_type;
-		/* branch:<line_num>,<branch_coverage_type>
-		   branch_coverage_type
-		     : notexec (Branch not executed)
-		     : taken (Branch executed and taken)
-		     : nottaken (Branch executed, but not taken)
-		*/
-		if ((*it)->src->count)
-		  branch_type = ((*it)->count > 0) ? "taken" : "nottaken";
-		else
-		  branch_type = "notexec";
-		fprintf (gcov_file, "branch:%d,%s\n", line_num, branch_type);
-	      }
-	  }
+      vector<function_t *> fns = src->get_functions_at_location (line_num);
+
+      /* Print first group functions that begin on the line.  */
+      for (vector<function_t *>::iterator it2 = fns.begin ();
+	   it2 != fns.end (); it2++)
+	{
+	  vector<line_info> &lines = (*it2)->lines;
+	  /* Print all lines covered by the function.  */
+	  for (unsigned i = 0; i < lines.size (); i++)
+	    {
+	      line_info *line = &lines[i];
+	      output_intermediate_line (gcov_file, line, line_num + i);
+	    }
+	}
+
+      /* Follow with lines associated with the source file.  */
+      output_intermediate_line (gcov_file, &src->lines[line_num], line_num);
     }
 }
 
+/* Function start pair.  */
+struct function_start
+{
+  unsigned source_file_idx;
+  unsigned start_line;
+};
+
+/* Traits class for function start hash maps below.  */
+
+struct function_start_pair_hash : typed_noop_remove <function_start>
+{
+  typedef function_start value_type;
+  typedef function_start compare_type;
+
+  static hashval_t
+  hash (const function_start &ref)
+  {
+    inchash::hash hstate (0);
+    hstate.add_int (ref.source_file_idx);
+    hstate.add_int (ref.start_line);
+    return hstate.end ();
+  }
+
+  static bool
+  equal (const function_start &ref1, const function_start &ref2)
+  {
+    return (ref1.source_file_idx == ref2.source_file_idx
+	    && ref1.start_line == ref2.start_line);
+  }
+
+  static void
+  mark_deleted (function_start &ref)
+  {
+    ref.start_line = ~1U;
+  }
+
+  static void
+  mark_empty (function_start &ref)
+  {
+    ref.start_line = ~2U;
+  }
+
+  static bool
+  is_deleted (const function_start &ref)
+  {
+    return ref.start_line == ~1U;
+  }
+
+  static bool
+  is_empty (const function_start &ref)
+  {
+    return ref.start_line == ~2U;
+  }
+};
+
 /* Process a single input file.  */
 
 static void
@@ -949,6 +1073,27 @@  process_file (const char *file_name)
   if (!fns)
     return;
 
+  hash_map<function_start_pair_hash, function_t *> fn_map;
+
+  /* Identify group functions.  */
+  for (function_t *f = fns; f; f = f->next)
+    if (!f->artificial)
+      {
+	function_start needle;
+	needle.source_file_idx = f->src;
+	needle.start_line = f->start_line;
+
+	function_t **slot = fn_map.get (needle);
+	if (slot)
+	  {
+	    gcc_assert ((*slot)->end_line == f->end_line);
+	    (*slot)->is_group = 1;
+	    f->is_group = 1;
+	  }
+	else
+	  fn_map.put (needle, f);
+      }
+
   read_count_file (fns);
   while (fns)
     {
@@ -959,46 +1104,61 @@  process_file (const char *file_name)
       if (fn->counts || no_data_file)
 	{
 	  unsigned src = fn->src;
-	  unsigned line = fn->line;
 	  unsigned block_no;
-	  function_t *probe, **prev;
-
-	  /* 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.  Note we're
-	     building this list in reverse order.  */
-	  for (prev = &sources[src].functions;
-	       (probe = *prev); prev = &probe->next_file_fn)
-	    if (probe->line <= line)
-	      break;
-	  fn->next_file_fn = probe;
-	  *prev = fn;
 
-	  /* Mark last line in files touched by function.  */
-	  for (block_no = 0; block_no != fn->blocks.size (); block_no++)
+	  /* Process only non-artificial functions.  */
+	  if (!fn->artificial)
 	    {
-	      block_t *block = &fn->blocks[block_no];
-	      for (unsigned i = 0; i < block->locations.size (); i++)
-		{
-		  unsigned s = block->locations[i].source_file_idx;
+	      source_info *s = &sources[src];
+	      s->functions.push_back (fn);
 
-		  /* Sort lines of locations.  */
-		  sort (block->locations[i].lines.begin (),
-			block->locations[i].lines.end ());
-
-		  if (!block->locations[i].lines.empty ())
+	      /* Mark last line in files touched by function.  */
+	      for (block_no = 0; block_no != fn->blocks.size (); block_no++)
+		{
+		  block_t *block = &fn->blocks[block_no];
+		  for (unsigned i = 0; i < block->locations.size (); i++)
 		    {
-		      unsigned last_line
-			= block->locations[i].lines.back () + 1;
-		      if (last_line > sources[s].lines.size ())
-			sources[s].lines.resize (last_line);
+		      /* Sort lines of locations.  */
+		      sort (block->locations[i].lines.begin (),
+			    block->locations[i].lines.end ());
+
+		      if (!block->locations[i].lines.empty ())
+			{
+			  s = &sources[block->locations[i].source_file_idx];
+			  unsigned last_line
+			    = block->locations[i].lines.back ();
+
+			  /* Record new lines for the function.  */
+			  if (last_line >= s->lines.size ())
+			    {
+			      /* Record new lines for a source file.  */
+			      s->lines.resize (last_line + 1);
+			    }
+
+			  /* It's possible that a function has some inlined
+			     lines that are out of scope of the function.  */
+			  for (int j = block->locations[i].lines.size () - 1;
+			      j >= 0; j--)
+			  {
+			    unsigned ln = block->locations[i].lines[j];
+			    if (fn->group_line_p (ln, s->index))
+			    {
+			      unsigned size = ln - fn->start_line + 1;
+			      if (fn->lines.size () < size)
+				fn->lines.resize (size);
+
+			      break;
+			    }
+			  }
+			}
 		    }
 		}
+
+	      solve_flow_graph (fn);
+	      if (fn->has_catch)
+		find_exception_blocks (fn);
 	    }
 
-	  solve_flow_graph (fn);
-	  if (fn->has_catch)
-	    find_exception_blocks (fn);
 	  *fn_end = fn;
 	  fn_end = &fn->next;
 	}
@@ -1048,6 +1208,8 @@  generate_results (const char *file_name)
   for (fn = functions; fn; fn = fn->next)
     {
       coverage_t coverage;
+      if (fn->artificial)
+	continue;
 
       memset (&coverage, 0, sizeof (coverage));
       coverage.name = flag_demangled_names ? fn->demangled_name : fn->name;
@@ -1228,6 +1390,7 @@  find_source (const char *file_name)
       src = &sources.back ();
       src->name = canon;
       src->coverage.name = src->name;
+      src->index = idx;
       if (source_length
 #if HAVE_DOS_BASED_FILE_SYSTEM
 	  /* You lose if separators don't match exactly in the
@@ -1319,15 +1482,17 @@  read_graph_file (void)
       if (tag == GCOV_TAG_FUNCTION)
 	{
 	  char *function_name;
-	  unsigned ident, lineno;
+	  unsigned ident;
 	  unsigned lineno_checksum, cfg_checksum;
 
 	  ident = gcov_read_unsigned ();
 	  lineno_checksum = gcov_read_unsigned ();
 	  cfg_checksum = gcov_read_unsigned ();
 	  function_name = xstrdup (gcov_read_string ());
+	  unsigned artificial = gcov_read_unsigned ();
 	  unsigned src_idx = find_source (gcov_read_string ());
-	  lineno = gcov_read_unsigned ();
+	  unsigned start_line = gcov_read_unsigned ();
+	  unsigned end_line = gcov_read_unsigned ();
 
 	  fn = new function_t;
 	  fn->name = function_name;
@@ -1341,9 +1506,10 @@  read_graph_file (void)
 	  fn->lineno_checksum = lineno_checksum;
 	  fn->cfg_checksum = cfg_checksum;
 	  fn->src = src_idx;
-	  fn->line = lineno;
+	  fn->start_line = start_line;
+	  fn->end_line = end_line;
+	  fn->artificial = artificial;
 
-	  fn->next_file_fn = NULL;
 	  fn->next = NULL;
 	  *fns_end = fn;
 	  fns_end = &fn->next;
@@ -2230,48 +2396,70 @@  add_line_counts (coverage_t *coverage, function_t *fn)
 	fn->blocks_executed++;
       for (unsigned i = 0; i < block->locations.size (); i++)
 	{
-	  source_info *src = &sources[block->locations[i].source_file_idx];
-
+	  unsigned src_idx = block->locations[i].source_file_idx;
 	  vector<unsigned> &lines = block->locations[i].lines;
+
+	  block->cycle.arc = NULL;
+	  block->cycle.ident = ~0U;
+
 	  for (unsigned j = 0; j < lines.size (); j++)
 	    {
-	      line = &src->lines[lines[j]];
-	      if (coverage)
+	      unsigned ln = lines[j];
+
+	      /* Line belongs to a function that is in a group.  */
+	      if (fn->group_line_p (ln, src_idx))
 		{
-		  if (!line->exists)
-		    coverage->lines++;
-		  if (!line->count && block->count)
-		    coverage->lines_executed++;
+		  gcc_assert (lines[j] - fn->start_line < fn->lines.size ());
+		  line = &(fn->lines[lines[j] - fn->start_line]);
+		  line->exists = 1;
+		  if (!block->exceptional)
+		    {
+		      line->unexceptional = 1;
+		      if (block->count == 0)
+			line->has_unexecuted_block = 1;
+		    }
+		  line->count += block->count;
 		}
-	      line->exists = 1;
-	      if (!block->exceptional)
+	      else
 		{
-		  line->unexceptional = 1;
-		  if (block->count == 0)
-		    line->has_unexecuted_block = 1;
+		  gcc_assert (ln < sources[src_idx].lines.size ());
+		  line = &(sources[src_idx].lines[ln]);
+		  if (coverage)
+		    {
+		      if (!line->exists)
+			coverage->lines++;
+		      if (!line->count && block->count)
+			coverage->lines_executed++;
+		    }
+		  line->exists = 1;
+		  if (!block->exceptional)
+		    {
+		      line->unexceptional = 1;
+		      if (block->count == 0)
+			line->has_unexecuted_block = 1;
+		    }
+		  line->count += block->count;
 		}
-	      line->count += block->count;
 	    }
-	}
-      block->cycle.arc = NULL;
-      block->cycle.ident = ~0U;
-      has_any_line = true;
 
-      if (!ix || ix + 1 == fn->blocks.size ())
-	/* Entry or exit block */;
-      else if (line != NULL)
-	{
-	  line->blocks.push_back (block);
+	  has_any_line = true;
 
-	  if (flag_branches)
+	  if (!ix || ix + 1 == fn->blocks.size ())
+	    /* Entry or exit block.  */;
+	  else if (line != NULL)
 	    {
-	      arc_t *arc;
+	      line->blocks.push_back (block);
 
-	      for (arc = block->succ; arc; arc = arc->succ_next)
+	      if (flag_branches)
 		{
-		  line->branches.push_back (arc);
-		  if (coverage && !arc->is_unconditional)
-		    add_branch_counts (coverage, arc);
+		  arc_t *arc;
+
+		  for (arc = block->succ; arc; arc = arc->succ_next)
+		    {
+		      line->branches.push_back (arc);
+		      if (coverage && !arc->is_unconditional)
+			add_branch_counts (coverage, arc);
+		    }
 		}
 	    }
 	}
@@ -2281,72 +2469,105 @@  add_line_counts (coverage_t *coverage, function_t *fn)
     fnotice (stderr, "%s:no lines for '%s'\n", bbg_file_name, fn->name);
 }
 
-/* Accumulate the line counts of a file.  */
+/* Accumulate info for LINE that belongs to SRC source file.  If ADD_COVERAGE
+   is set to true, update source file summary.  */
 
-static void
-accumulate_line_counts (source_info *src)
+static void accumulate_line_info (line_info *line, source_info *src,
+				  bool add_coverage)
 {
-  function_t *fn, *fn_p, *fn_n;
-  unsigned ix = 0;
+  if (!line->blocks.empty ())
+    {
+      /* 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.  */
+      gcov_type count = 0;
+
+      /* Sum the entry arcs.  */
+      for (vector<block_t *>::iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  arc_t *arc;
 
-  /* Reverse the function order.  */
-  for (fn = src->functions, fn_p = NULL; fn; fn_p = fn, fn = fn_n)
+	  for (arc = (*it)->pred; arc; arc = arc->pred_next)
+	    if (flag_branches && add_coverage)
+	      add_branch_counts (&src->coverage, arc);
+	}
+
+      /* Cycle detection.  */
+      for (vector<block_t *>::iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next)
+	    if (!line->has_block (arc->src))
+	      count += arc->count;
+	  for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next)
+	    arc->cs_count = arc->count;
+	}
+
+      /* Now, add the count of loops entirely on this line.  */
+      count += get_cycles_count (*line);
+      line->count = count;
+    }
+
+  if (line->exists && add_coverage)
     {
-      fn_n = fn->next_file_fn;
-      fn->next_file_fn = fn_p;
+      src->coverage.lines++;
+      if (line->count)
+	src->coverage.lines_executed++;
     }
-  src->functions = fn_p;
+}
 
-  for (vector<line_info>::reverse_iterator it = src->lines.rbegin ();
-       it != src->lines.rend (); it++)
+/* Accumulate the line counts of a file.  */
+
+static void
+accumulate_line_counts (source_info *src)
+{
+  /* First work on group functions.  */
+  for (vector<function_t *>::iterator it = src->functions.begin ();
+       it != src->functions.end (); it++)
     {
-      line_info *line = &(*it);
-      if (!line->blocks.empty ())
-	{
-	  /* 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.  */
-	  gcov_type count = 0;
-
-	  /* Sum the entry arcs.  */
-	  for (vector<block_t *>::iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
-	    {
-	      arc_t *arc;
+      function_info *fn = *it;
 
-	      for (arc = (*it)->pred; arc; arc = arc->pred_next)
-		if (flag_branches)
-		  add_branch_counts (&src->coverage, arc);
-	    }
+      if (fn->src != src->index || !fn->is_group)
+	continue;
 
-	  /* Cycle detection.  */
-	  for (vector<block_t *>::iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
-	    {
-	      for (arc_t *arc = (*it)->pred; arc; arc = arc->pred_next)
-		if (!line->has_block (arc->src))
-		  count += arc->count;
-	      for (arc_t *arc = (*it)->succ; arc; arc = arc->succ_next)
-		arc->cs_count = arc->count;
-	    }
+      for (vector<line_info>::iterator it2 = fn->lines.begin ();
+	   it2 != fn->lines.end (); it2++)
+	  {
+	    line_info *line = &(*it2);
+	    accumulate_line_info (line, src, false);
+	  }
+    }
 
-	  /* Now, add the count of loops entirely on this line.  */
-	  count += get_cycles_count (*line);
-	  line->count = count;
-	}
+  /* Work on global lines that line in source file SRC.  */
+  for (vector<line_info>::iterator it = src->lines.begin ();
+       it != src->lines.end (); it++)
+    accumulate_line_info (&(*it), src, true);
 
-      if (line->exists)
-	{
-	  src->coverage.lines++;
-	  if (line->count)
-	    src->coverage.lines_executed++;
-	}
+  /* If not using intermediate mode, sum lines of group functions and
+     add them to lines that live in a source file.  */
+  if (!flag_intermediate_format)
+    for (vector<function_t *>::iterator it = src->functions.begin ();
+	 it != src->functions.end (); it++)
+      {
+	function_info *fn = *it;
 
-      ix++;
-    }
+	if (fn->src != src->index || !fn->is_group)
+	  continue;
+
+	for (unsigned i = 0; i < fn->lines.size (); i++)
+	  {
+	    line_info &line = fn->lines[i];
+	    if (line.exists)
+	      {
+		src->lines[fn->start_line + i].count += line.count;
+		src->lines[fn->start_line + i].exists = 1;
+	      }
+	  }
+      }
 }
 
 /* Output information about ARC number IX.  Returns nonzero if
@@ -2464,7 +2685,8 @@  output_line_beginning (FILE *f, bool exists, bool unexceptional,
 	      if (flag_use_colors)
 		{
 		  pad_count_string (s);
-		  s = SGR_SEQ (COLOR_BG_MAGENTA COLOR_SEPARATOR COLOR_FG_WHITE);
+		  s.insert (0, SGR_SEQ (COLOR_BG_MAGENTA
+					COLOR_SEPARATOR COLOR_FG_WHITE));
 		  s += SGR_RESET;
 		}
 	      else
@@ -2502,6 +2724,86 @@  output_line_beginning (FILE *f, bool exists, bool unexceptional,
   fprintf (f, "%s:%5u", s.c_str (), line_num);
 }
 
+static void
+print_source_line (FILE *f, const vector<const char *> &source_lines,
+		   unsigned line)
+{
+  gcc_assert (line >= 1);
+  gcc_assert (line <= source_lines.size ());
+
+  fprintf (f, ":%s\n", source_lines[line - 1]);
+}
+
+/* Output line details for LINE and print it to F file.  LINE lives on
+   LINE_NUM.  */
+
+static void
+output_line_details (FILE *f, const line_info *line, unsigned line_num)
+{
+  if (flag_all_blocks)
+    {
+      arc_t *arc;
+      int ix, jx;
+
+      ix = jx = 0;
+      for (vector<block_t *>::const_iterator it = line->blocks.begin ();
+	   it != line->blocks.end (); it++)
+	{
+	  if (!(*it)->is_call_return)
+	    {
+	      output_line_beginning (f, line->exists,
+				     (*it)->exceptional, false,
+				     (*it)->count, line_num,
+				     "%%%%%", "$$$$$");
+	      fprintf (f, "-block %2d", ix++);
+	      if (flag_verbose)
+		fprintf (f, " (BB %u)", (*it)->id);
+	      fprintf (f, "\n");
+	    }
+	  if (flag_branches)
+	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
+	      jx += output_branch_count (f, jx, arc);
+	}
+    }
+  else if (flag_branches)
+    {
+      int ix;
+
+      ix = 0;
+      for (vector<arc_t *>::const_iterator it = line->branches.begin ();
+	   it != line->branches.end (); it++)
+	ix += output_branch_count (f, ix, (*it));
+    }
+}
+
+/* Output detail statistics about function FN to file F.  */
+
+static void
+output_function_details (FILE *f, const function_info *fn)
+{
+  if (!flag_branches)
+    return;
+
+  arc_t *arc = fn->blocks[EXIT_BLOCK].pred;
+  gcov_type return_count = fn->blocks[EXIT_BLOCK].count;
+  gcov_type called_count = fn->blocks[ENTRY_BLOCK].count;
+
+  for (; arc; arc = arc->pred_next)
+    if (arc->fake)
+      return_count -= arc->count;
+
+  fprintf (f, "function %s",
+	   flag_demangled_names ? fn->demangled_name : fn->name);
+  fprintf (f, " called %s",
+	   format_gcov (called_count, 0, -1));
+  fprintf (f, " returned %s",
+	   format_gcov (return_count, called_count, 0));
+  fprintf (f, " blocks executed %s",
+	   format_gcov (fn->blocks_executed, fn->blocks.size () - 2,
+			0));
+  fprintf (f, "\n");
+}
+
 /* 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.  */
@@ -2512,10 +2814,7 @@  output_lines (FILE *gcov_file, const source_info *src)
 #define  DEFAULT_LINE_START "        -:    0:"
 
   FILE *source_file;
-  unsigned line_num;	/* current line number.  */
-  const line_info *line;  /* current line info ptr.  */
   const char *retval = "";	/* status of source file reading.  */
-  function_t *fn = NULL;
 
   fprintf (gcov_file, DEFAULT_LINE_START "Source:%s\n", src->coverage.name);
   if (!multiple_files)
@@ -2536,36 +2835,35 @@  output_lines (FILE *gcov_file, const source_info *src)
   else if (src->file_time == 0)
     fprintf (gcov_file, DEFAULT_LINE_START "Source is newer than graph\n");
 
-  if (flag_branches)
-    fn = src->functions;
+  vector<const char *> source_lines;
+  while ((retval = read_line (source_file)) != NULL)
+    source_lines.push_back (xstrdup (retval));
 
-  for (line_num = 1, line = &src->lines[line_num];
-       line_num < src->lines.size (); line_num++, line++)
+  unsigned line_start_group = 0;
+  vector<function_t *> fns;
+
+  for (unsigned line_num = 1; line_num <= source_lines.size (); line_num++)
     {
-      for (; fn && fn->line == line_num; fn = fn->next_file_fn)
+      if (line_num >= src->lines.size ())
 	{
-	  arc_t *arc = fn->blocks[EXIT_BLOCK].pred;
-	  gcov_type return_count = fn->blocks[EXIT_BLOCK].count;
-	  gcov_type called_count = fn->blocks[ENTRY_BLOCK].count;
-
-	  for (; arc; arc = arc->pred_next)
-	    if (arc->fake)
-	      return_count -= arc->count;
-
-	  fprintf (gcov_file, "function %s", flag_demangled_names ?
-                   fn->demangled_name : fn->name);
-	  fprintf (gcov_file, " called %s",
-		   format_gcov (called_count, 0, -1));
-	  fprintf (gcov_file, " returned %s",
-		   format_gcov (return_count, called_count, 0));
-	  fprintf (gcov_file, " blocks executed %s",
-		   format_gcov (fn->blocks_executed, fn->blocks.size () - 2,
-				0));
-	  fprintf (gcov_file, "\n");
+	  fprintf (gcov_file, "%9s:%5u", "-", line_num);
+	  print_source_line (gcov_file, source_lines, line_num);
+	  continue;
 	}
 
-      if (retval)
-	retval = read_line (source_file);
+      const line_info *line = &src->lines[line_num];
+
+      if (line_start_group == 0)
+	{
+	  fns = src->get_functions_at_location (line_num);
+	  if (fns.size () > 1)
+	    line_start_group = fns[0]->end_line;
+	  else if (fns.size () == 1)
+	    {
+	      function_t *fn = fns[0];
+	      output_function_details (gcov_file, fn);
+	    }
+	}
 
       /* For lines which don't exist in the .bb file, print '-' before
 	 the source line.  For lines which exist but were never
@@ -2574,54 +2872,55 @@  output_lines (FILE *gcov_file, const source_info *src)
 	 There are 16 spaces of indentation added before the source
 	 line so that tabs won't be messed up.  */
       output_line_beginning (gcov_file, line->exists, line->unexceptional,
-			     line->has_unexecuted_block, line->count, line_num,
-			     "=====", "#####");
-      fprintf (gcov_file, ":%s\n", retval ? retval : "/*EOF*/");
+			     line->has_unexecuted_block, line->count,
+			     line_num, "=====", "#####");
 
-      if (flag_all_blocks)
-	{
-	  arc_t *arc;
-	  int ix, jx;
+      print_source_line (gcov_file, source_lines, line_num);
+      output_line_details (gcov_file, line, line_num);
 
-	  ix = jx = 0;
-	  for (vector<block_t *>::const_iterator it = line->blocks.begin ();
-	       it != line->blocks.end (); it++)
+      if (line_start_group == line_num)
+	{
+	  for (vector<function_t *>::iterator it = fns.begin ();
+	       it != fns.end (); it++)
 	    {
-	      if (!(*it)->is_call_return)
+	      function_info *fn = *it;
+	      vector<line_info> &lines = fn->lines;
+
+	      fprintf (gcov_file, "------------------\n");
+	      fprintf (gcov_file, "%s:\n", flag_demangled_names
+		       ? fn->demangled_name : fn->name);
+
+	      output_function_details (gcov_file, fn);
+
+	      /* Print all lines covered by the function.  */
+	      for (unsigned i = 0; i < lines.size (); i++)
 		{
+		  line_info *line = &lines[i];
+		  unsigned l = fn->start_line + i;
+
+		  /* For lines which don't exist in the .bb file, print '-'
+		     before the source line.  For lines which exist but
+		     were never executed, print '#####' or '=====' 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.  */
 		  output_line_beginning (gcov_file, line->exists,
-					 (*it)->exceptional, false,
-					 (*it)->count, line_num,
-					 "%%%%%", "$$$$$");
-		  fprintf (gcov_file, "-block %2d", ix++);
-		  if (flag_verbose)
-		    fprintf (gcov_file, " (BB %u)", (*it)->id);
-		  fprintf (gcov_file, "\n");
+					 line->unexceptional,
+					 line->has_unexecuted_block,
+					 line->count,
+					 l, "=====", "#####");
+
+		  print_source_line (gcov_file, source_lines, l);
+		  output_line_details (gcov_file, line, l);
 		}
-	      if (flag_branches)
-		for (arc = (*it)->succ; arc; arc = arc->succ_next)
-		  jx += output_branch_count (gcov_file, jx, arc);
 	    }
-	}
-      else if (flag_branches)
-	{
-	  int ix;
 
-	  ix = 0;
-	  for (vector<arc_t *>::const_iterator it = line->branches.begin ();
-	       it != line->branches.end (); it++)
-	    ix += output_branch_count (gcov_file, ix, (*it));
+	  fprintf (gcov_file, "------------------\n");
+	  line_start_group = 0;
 	}
     }
 
-  /* Handle all remaining source lines.  There may be lines after the
-     last line of code.  */
-  if (retval)
-    {
-      for (; (retval = read_line (source_file)); line_num++)
-	fprintf (gcov_file, "%9s:%5u:%s\n", "-", line_num, retval);
-    }
-
   if (source_file)
     fclose (source_file);
 }
-- 
2.14.3