diff mbox

Comdat-aware code coverage data

Message ID 4EB1A5A6.1090508@acm.org
State New
Headers show

Commit Message

Nathan Sidwell Nov. 2, 2011, 8:18 p.m. UTC
This patch modifies the coverage scheme so that it works better with comdat 
sections.  The existing scheme uses a single file-scope array for counters, with 
each function using a chunk of that array.  If the function is comdat, it's 
possible that that instance will be omitted from the link, in preference to one 
from another object file.  The chunk of the counter array will remain and remain 
as zero counts.  When processed by gcov, that instance of the function will 
appear unexecuted, which is misleading, because it's not present at all in the 
final executable.  For C, this affects unlined inline functions, for C++ it also 
affects template instantiations.

This patch changes things so that counters are allocated as per-function arrays 
with the same comdatedness as the function being instrumented.  Thus the array 
will be discarded, if the function itself is.  Each function's 
function-descriptor now also points to the counter array, in addition to 
specifying the number of counters instrumented (plus the existing checksums). 
This object too has the same comdatedness (& visibility) as the function.  The 
name of this object is derived from the name of the function, via a mangling scheme.

In the existing scheme, there's a global file-scope object-descriptor that (a) 
points to the global counter array, and (b) has a trailing array of 
function-descriptors.  The patch changes things so that there's no pointer to 
the now-deleted global counter array, and the trailing array is now an array of 
pointers to function-descriptors.  In the absence of comdat, we can now generate 
the same gcov data file as before, but with an additional layer of indirection.

However, with comdat functions the function-descriptor selected for a particular 
function may come from a different object file.  It'll be pointed to by the 
object-descriptor's trailing array.  When iterating over this array we do not 
want to emit data for the instance of the function being pointed to, and so need 
some way of detecting a foreign function descriptor.  We do this by adding a key 
field to the function descriptor.  It is initialized to point to the (static) 
object-descriptor.  Thus foreign function-descriptors will have a key that does 
not point to the object-descriptor being iterated over.

The above description only discusses the block counters.  The coverage system 
instruments more than that, and each distinct counter type is modified in the 
same way.  One significant change is that the merge function pointers are no 
longer per-function-descriptor, but are in the object-descriptor and a NULL 
value there indicates a skipped counter.

For the data-file itself, the per-object summary data is deleted, as it no 
longer makes much sense.  The number of instrumented functions from an object 
file can vary, depending on which comdat functions are selected from it in the 
final links it participates in.  For convenience the program summaries are now 
placed before the per-function data.  Also, because an object can be in multiple 
executables (or shared objects), we need to keep track of functions that were 
emitted in the object file but not in the final link.  Thus we now have 
place-holder function data slots, and have the ability to buffer file data for a 
function that was instrumented in some other executable but not in the current 
one -- I couldn't figure out a sane way to deal this other than using malloc.  I 
have not removed gcov-dump support for the per-object summary, to allow use with 
older data files.  But remember the gcov file format is explicitly tied to the 
version of the compiler used to generate it -- it is not supposed to be version 
neutral.

As mentioned above, there is an ABI impact, because the function-descriptors of 
externally visible functions must also be externally visible.  These are named 
'__gcov__FUNCTION', where 'FUNCTION' is the (possibly mangled) name of the 
function being described.  The per-function counter arrays are not externally 
visible, so their mangling only needs to guarantee uniqueness in the object 
file.  These are mangled as '__gcovN_FUNCTION', where 'N' is the counter number.

There are a couple of other changes, due to existing shortfalls exposed by this 
work.  Firstly, for an inline function that is inlined everywhere, we 
(currently) generate a block of counters in the global array.  We never emit the 
non-inlined body of function, so these ended up as unused parts of the global 
array.  When emitting the object-descriptor, we elide functions whose 
DECL_STRUCT_FUNCTION has been cleared.  The impact of this in reading the count 
data file is that we no longer emit a diagnostic for functions that are not 
described in the data file -- we don't know at this point whether the function 
being optimized will actually be emitted.

Tested by normal and profiled bootstrap on i686-pc-linux-gnu for all languages.

I'll apply this patch shortly, unless there are questions or comments.

nathan
2011-11-01  Nathan Sidwell  <nathan@acm.org>

	libgcc/
	* libgcov.c (struct gcov_fn_buffer): New struct.
	(buffer_fn_data): New helper.
	(gcov_exit): Rework for new gcov data structures.

	gcc/
	* gcov.c (object_summary): Replace with ...
	(object_runs): ... this.
	(process_file): Remove functions with no data.
	(generate_results): Ignore files with no lines.
	(release_function): New helper, broken out of ...
	(release_structures): ... here.  Use it.
	(read_count_file): Adjust for new data file format.
	(output_lines): Use object_runs.
	* gcov-io.h (GCOV_TAG_OBJECT_SUMMARY): Obsolete.
	(struct gcov_ctr_info): Move definition.
	(struct gcov_fn_info): Add key field, use gcov_ctr_info for
	trailing array.
	(struct gcov_info): Add merge function array, remove mask and
	counts.  Trailing array is array of pointers to function info.
	* coverage.c (struct function_list): Replace counter numbers with
	counter arrays.  Add fndecl field.  GTYify.
	(counts_entry): Remove chain workspace.
	(functions_head): GTYify.
	(prg_n_ctrs): Remove.
	(fn_v_ctrs): New.
	(tree_ctr_tables): Remove.
	(read_counts_file): Cope with blank entries and expect program
	summaries before functions.  Don't warn on missing entries.
	(coverage_counter_alloc): Allocate individual function arrays.
	(tree_coverage_counter_ref, tree_coverage_counter_addr): Adjust
	for individual function arrays.
	(coverage_end_function): GTYify function list object. Finalize
	function's counter arrays.
	(build_var): New.  Create a counter-related variable with
	appropriate linkage.
	(build_fn_info_type): Adjust for new runtime structure.
	(build_fn_info_value): Rename to ...
	(build_fn_info): ... here.  Build new format data.
	(build_ctr_info_type, build_ctr_info_value): Remove.
	(build_info_type): New. Build new format data structure.
	(build_info): Adjust for new format data.
	(create_coverage): Likewise.
	* gcov-dump.c (tag_function): Recognize placeholders.

	gcc/testsuite/
	* gcc.dg/profile-dir-1.c: Adjust final scan.
	* gcc.dg/profile-dir-2.c: Adjust final scan.
	* gcc.dg/profile-dir-3.c: Adjust final scan.
	* gcc.misc-tests/gcov.exp: Adjust regexp.
	* gcc.misc-tests/gcov-12.c: New.
	* gcc.misc-tests/gcov-13.c: New.
	* gcc.misc-tests/gcovpart-13b.c: New.
	* gcc.misc-tests/gcov-14.c: New.

Comments

Xinliang David Li Jan. 20, 2012, 10:41 p.m. UTC | #1
Nathan, I just noticed this path. This is a good improvement over the
existing scheme.

I see one potential problem with the patch -- different instances of
the same comdat function can potentially have different control flows
(due to differences in early inline, early optimizations in different
module contexts)  before profile counter allocation -- which means
making the profile counters 'comdat' can later cause coverage mismatch
problems during profile-use phase.

In this patch, fn data buffer is introduced. What is that for and how
does it work?

thanks,

David

On Wed, Nov 2, 2011 at 1:18 PM, Nathan Sidwell <nathan@acm.org> wrote:
> This patch modifies the coverage scheme so that it works better with comdat
> sections.  The existing scheme uses a single file-scope array for counters,
> with each function using a chunk of that array.  If the function is comdat,
> it's possible that that instance will be omitted from the link, in
> preference to one from another object file.  The chunk of the counter array
> will remain and remain as zero counts.  When processed by gcov, that
> instance of the function will appear unexecuted, which is misleading,
> because it's not present at all in the final executable.  For C, this
> affects unlined inline functions, for C++ it also affects template
> instantiations.
>
> This patch changes things so that counters are allocated as per-function
> arrays with the same comdatedness as the function being instrumented.  Thus
> the array will be discarded, if the function itself is.  Each function's
> function-descriptor now also points to the counter array, in addition to
> specifying the number of counters instrumented (plus the existing
> checksums). This object too has the same comdatedness (& visibility) as the
> function.  The name of this object is derived from the name of the function,
> via a mangling scheme.
>
> In the existing scheme, there's a global file-scope object-descriptor that
> (a) points to the global counter array, and (b) has a trailing array of
> function-descriptors.  The patch changes things so that there's no pointer
> to the now-deleted global counter array, and the trailing array is now an
> array of pointers to function-descriptors.  In the absence of comdat, we can
> now generate the same gcov data file as before, but with an additional layer
> of indirection.
>
> However, with comdat functions the function-descriptor selected for a
> particular function may come from a different object file.  It'll be pointed
> to by the object-descriptor's trailing array.  When iterating over this
> array we do not want to emit data for the instance of the function being
> pointed to, and so need some way of detecting a foreign function descriptor.
>  We do this by adding a key field to the function descriptor.  It is
> initialized to point to the (static) object-descriptor.  Thus foreign
> function-descriptors will have a key that does not point to the
> object-descriptor being iterated over.
>
> The above description only discusses the block counters.  The coverage
> system instruments more than that, and each distinct counter type is
> modified in the same way.  One significant change is that the merge function
> pointers are no longer per-function-descriptor, but are in the
> object-descriptor and a NULL value there indicates a skipped counter.
>
> For the data-file itself, the per-object summary data is deleted, as it no
> longer makes much sense.  The number of instrumented functions from an
> object file can vary, depending on which comdat functions are selected from
> it in the final links it participates in.  For convenience the program
> summaries are now placed before the per-function data.  Also, because an
> object can be in multiple executables (or shared objects), we need to keep
> track of functions that were emitted in the object file but not in the final
> link.  Thus we now have place-holder function data slots, and have the
> ability to buffer file data for a function that was instrumented in some
> other executable but not in the current one -- I couldn't figure out a sane
> way to deal this other than using malloc.  I have not removed gcov-dump
> support for the per-object summary, to allow use with older data files.  But
> remember the gcov file format is explicitly tied to the version of the
> compiler used to generate it -- it is not supposed to be version neutral.
>
> As mentioned above, there is an ABI impact, because the function-descriptors
> of externally visible functions must also be externally visible.  These are
> named '__gcov__FUNCTION', where 'FUNCTION' is the (possibly mangled) name of
> the function being described.  The per-function counter arrays are not
> externally visible, so their mangling only needs to guarantee uniqueness in
> the object file.  These are mangled as '__gcovN_FUNCTION', where 'N' is the
> counter number.
>
> There are a couple of other changes, due to existing shortfalls exposed by
> this work.  Firstly, for an inline function that is inlined everywhere, we
> (currently) generate a block of counters in the global array.  We never emit
> the non-inlined body of function, so these ended up as unused parts of the
> global array.  When emitting the object-descriptor, we elide functions whose
> DECL_STRUCT_FUNCTION has been cleared.  The impact of this in reading the
> count data file is that we no longer emit a diagnostic for functions that
> are not described in the data file -- we don't know at this point whether
> the function being optimized will actually be emitted.
>
> Tested by normal and profiled bootstrap on i686-pc-linux-gnu for all
> languages.
>
> I'll apply this patch shortly, unless there are questions or comments.
>
> nathan
Xinliang David Li Jan. 21, 2012, 1:33 a.m. UTC | #2
Looks like the change to put fn_info and counter vars into comdat
section is reverted due to regression.

Assuming the comdat changes are kept, I don't see the point of doing
fn data buffering -- it is certainly important to compare the key with
the current gcov_info to avoid merging to happen multiple times for
the same comdat function, but there seems no point to do the buffering
-- just need to read the profile data and discard (for file seeking).
The buffering is also wrong in the sense that it basically skips the
merging for the function instance in modules other than the owning
module (the final defining module of the comdat).

Am I missing something?

thanks,

David

On Fri, Jan 20, 2012 at 2:41 PM, Xinliang David Li <davidxl@google.com> wrote:
> Nathan, I just noticed this path. This is a good improvement over the
> existing scheme.
>
> I see one potential problem with the patch -- different instances of
> the same comdat function can potentially have different control flows
> (due to differences in early inline, early optimizations in different
> module contexts)  before profile counter allocation -- which means
> making the profile counters 'comdat' can later cause coverage mismatch
> problems during profile-use phase.
>
> In this patch, fn data buffer is introduced. What is that for and how
> does it work?
>
> thanks,
>
> David
>
> On Wed, Nov 2, 2011 at 1:18 PM, Nathan Sidwell <nathan@acm.org> wrote:
>> This patch modifies the coverage scheme so that it works better with comdat
>> sections.  The existing scheme uses a single file-scope array for counters,
>> with each function using a chunk of that array.  If the function is comdat,
>> it's possible that that instance will be omitted from the link, in
>> preference to one from another object file.  The chunk of the counter array
>> will remain and remain as zero counts.  When processed by gcov, that
>> instance of the function will appear unexecuted, which is misleading,
>> because it's not present at all in the final executable.  For C, this
>> affects unlined inline functions, for C++ it also affects template
>> instantiations.
>>
>> This patch changes things so that counters are allocated as per-function
>> arrays with the same comdatedness as the function being instrumented.  Thus
>> the array will be discarded, if the function itself is.  Each function's
>> function-descriptor now also points to the counter array, in addition to
>> specifying the number of counters instrumented (plus the existing
>> checksums). This object too has the same comdatedness (& visibility) as the
>> function.  The name of this object is derived from the name of the function,
>> via a mangling scheme.
>>
>> In the existing scheme, there's a global file-scope object-descriptor that
>> (a) points to the global counter array, and (b) has a trailing array of
>> function-descriptors.  The patch changes things so that there's no pointer
>> to the now-deleted global counter array, and the trailing array is now an
>> array of pointers to function-descriptors.  In the absence of comdat, we can
>> now generate the same gcov data file as before, but with an additional layer
>> of indirection.
>>
>> However, with comdat functions the function-descriptor selected for a
>> particular function may come from a different object file.  It'll be pointed
>> to by the object-descriptor's trailing array.  When iterating over this
>> array we do not want to emit data for the instance of the function being
>> pointed to, and so need some way of detecting a foreign function descriptor.
>>  We do this by adding a key field to the function descriptor.  It is
>> initialized to point to the (static) object-descriptor.  Thus foreign
>> function-descriptors will have a key that does not point to the
>> object-descriptor being iterated over.
>>
>> The above description only discusses the block counters.  The coverage
>> system instruments more than that, and each distinct counter type is
>> modified in the same way.  One significant change is that the merge function
>> pointers are no longer per-function-descriptor, but are in the
>> object-descriptor and a NULL value there indicates a skipped counter.
>>
>> For the data-file itself, the per-object summary data is deleted, as it no
>> longer makes much sense.  The number of instrumented functions from an
>> object file can vary, depending on which comdat functions are selected from
>> it in the final links it participates in.  For convenience the program
>> summaries are now placed before the per-function data.  Also, because an
>> object can be in multiple executables (or shared objects), we need to keep
>> track of functions that were emitted in the object file but not in the final
>> link.  Thus we now have place-holder function data slots, and have the
>> ability to buffer file data for a function that was instrumented in some
>> other executable but not in the current one -- I couldn't figure out a sane
>> way to deal this other than using malloc.  I have not removed gcov-dump
>> support for the per-object summary, to allow use with older data files.  But
>> remember the gcov file format is explicitly tied to the version of the
>> compiler used to generate it -- it is not supposed to be version neutral.
>>
>> As mentioned above, there is an ABI impact, because the function-descriptors
>> of externally visible functions must also be externally visible.  These are
>> named '__gcov__FUNCTION', where 'FUNCTION' is the (possibly mangled) name of
>> the function being described.  The per-function counter arrays are not
>> externally visible, so their mangling only needs to guarantee uniqueness in
>> the object file.  These are mangled as '__gcovN_FUNCTION', where 'N' is the
>> counter number.
>>
>> There are a couple of other changes, due to existing shortfalls exposed by
>> this work.  Firstly, for an inline function that is inlined everywhere, we
>> (currently) generate a block of counters in the global array.  We never emit
>> the non-inlined body of function, so these ended up as unused parts of the
>> global array.  When emitting the object-descriptor, we elide functions whose
>> DECL_STRUCT_FUNCTION has been cleared.  The impact of this in reading the
>> count data file is that we no longer emit a diagnostic for functions that
>> are not described in the data file -- we don't know at this point whether
>> the function being optimized will actually be emitted.
>>
>> Tested by normal and profiled bootstrap on i686-pc-linux-gnu for all
>> languages.
>>
>> I'll apply this patch shortly, unless there are questions or comments.
>>
>> nathan
Nathan Sidwell Jan. 23, 2012, 8:52 p.m. UTC | #3
On 01/20/12 22:41, Xinliang David Li wrote:
> Nathan, I just noticed this path. This is a good improvement over the
> existing scheme.
>
> I see one potential problem with the patch -- different instances of
> the same comdat function can potentially have different control flows
> (due to differences in early inline, early optimizations in different
> module contexts)  before profile counter allocation -- which means
> making the profile counters 'comdat' can later cause coverage mismatch
> problems during profile-use phase.

Right, I have an new patch that reenables the comdat-awareness, however there is 
one case I theorized about but couldn't generate a testcase for.  Your email 
convinced me to try harder, and I now have such a testcase that needs 
addressing.  Anyway, the tree-based coverage counters treat the per-function 
counters as-if they are function-scope static variables.  Hence inlining Foo 
into Baz will not add new counters to Baz but increment Foo's counters.  So you 
can't distinguish between different inlinings of Foo.  Of course optimizing 
Foo's inline instance in Baz may delete various basic blocks and so lead to 
non-increments.  However, for the purposes of profiling, the average behaviour 
over all inlinings in that translation unit is used.   As I recall the rtl-based 
counters didn't do this.

Anyway, with comdat-awareness, the case I need to address is a function Foo that 
is inlined in some places but not in others.  So there's a comdat Foo instance 
emitted.  That instance may or may not be selected by the linker.  If it is not 
selected, we still want to track the counter values for its inlinings in that 
TU.  If it is selected, we don't want to double count its counter values.  I 
need to think about this a bit, but have some ideas.

> In this patch, fn data buffer is introduced. What is that for and how
> does it work?

The intention is that the fn_info structure is in the same comdat group as the 
comdat function to which it refers.  Therefore, if the instance of fn_info in 
the executable will match the instance of the function to which it refers. 
However, each object file needs to generate coverage information for all the 
functions in that object file and correctly mark whether a comdat function in it 
was in the final link or not.  So its array of pointers to fn_info will name the 
fn_infos of all its functions.  For a comdat function, the slot will select the 
fn_info of some other TU.  The gcov_info back-pointer in each fn_info allows the 
gcov runtime to detect this case.

Does that help?

nathan
Xinliang David Li Jan. 23, 2012, 10:13 p.m. UTC | #4
On Mon, Jan 23, 2012 at 12:52 PM, Nathan Sidwell <nathan@acm.org> wrote:
> On 01/20/12 22:41, Xinliang David Li wrote:
>>
>> Nathan, I just noticed this path. This is a good improvement over the
>> existing scheme.
>>
>> I see one potential problem with the patch -- different instances of
>> the same comdat function can potentially have different control flows
>> (due to differences in early inline, early optimizations in different
>> module contexts)  before profile counter allocation -- which means
>> making the profile counters 'comdat' can later cause coverage mismatch
>> problems during profile-use phase.
>
>
> Right, I have an new patch that reenables the comdat-awareness, however
> there is one case I theorized about but couldn't generate a testcase for.

One possible way to generate a test case is like the following -- bar
will be inlined into the instance of foo<int> in foo_1.cc, but not in
the one in foo_2.cc

foo.h
---------

extern int bar ();
template <typename T> __attribute__((noinline)) T foo (T)
{
  return bar();
}

foo_1.cc
--------------

#include "foo.h"
int g;

__attribute__((always_inline)) int bar()
{
   if (g > 10)
    return 1;
  else
    return 0;
}

int test_1 ()
{
   return foo<int>();
}


foo_2.cc
------------

#include "foo.h"

int test_2 ()
{
   return foo<int>();
}


>  Your email convinced me to try harder, and I now have such a testcase that
> needs addressing.  Anyway, the tree-based coverage counters treat the
> per-function counters as-if they are function-scope static variables.

I like this part of the change (splitting the counter array in a
module into per-function arrays) by itself -- it simplifies the
implementation and is much cleaner.

>Hence
> inlining Foo into Baz will not add new counters to Baz but increment Foo's
> counters.  So you can't distinguish between different inlinings of Foo.

This is not true for early inlining. If Foo is inlined into Baz in
early inline pass, Foo's inline instance will have its own 'private'
copy of the profile counters 'embedded' in the caller's counter.

For ipa inlining,  the old implementation allows all inline instances
of 'foo' in the same module to share the same counters -- but they do
not share the counters with the out of line copy of 'foo' which is
defined in another module (and picked up by the linker).

Your original comdat aware implementation forces all inline instances
of 'foo' also share counters across modules. This however may not only
cause problems in profile-use pass, but also cause runtime problems in
profile collection run such as out of bound accesses..


>  Of
> course optimizing Foo's inline instance in Baz may delete various basic
> blocks and so lead to non-increments.  However, for the purposes of
> profiling, the average behaviour over all inlinings in that translation unit
> is used.   As I recall the rtl-based counters didn't do this.
>
> Anyway, with comdat-awareness, the case I need to address is a function Foo
> that is inlined in some places but not in others.  So there's a comdat Foo
> instance emitted.  That instance may or may not be selected by the linker.
>  If it is not selected, we still want to track the counter values for its
> inlinings in that TU.  If it is selected, we don't want to double count its
> counter values.  I need to think about this a bit, but have some ideas.

See above. I think the main problems is that comdat function Foo in
different TUs may have incompatible counter variables (due to cfg
difference) -- common incompatible variables via comdat may cause
problems.

>
>
>> In this patch, fn data buffer is introduced. What is that for and how
>> does it work?
>
>
> The intention is that the fn_info structure is in the same comdat group as
> the comdat function to which it refers.  Therefore, if the instance of
> fn_info in the executable will match the instance of the function to which
> it refers. However, each object file needs to generate coverage information
> for all the functions in that object file and correctly mark whether a
> comdat function in it was in the final link or not.  So its array of
> pointers to fn_info will name the fn_infos of all its functions.  For a
> comdat function, the slot will select the fn_info of some other TU.  The
> gcov_info back-pointer in each fn_info allows the gcov runtime to detect
> this case.
>

Yes I knew the intention after reading the code. See also my follow up
email about it --- I think it is necessary to track where the counter
variable is coming from (the key), but not buffering is not needed.


thanks,

David

> Does that help?
>
> nathan
diff mbox

Patch

Index: libgcc/libgcov.c
===================================================================
--- libgcc/libgcov.c	(revision 180739)
+++ libgcc/libgcov.c	(working copy)
@@ -77,6 +77,14 @@  void __gcov_merge_delta (gcov_type *coun
 #ifdef L_gcov
 #include "gcov-io.c"
 
+struct gcov_fn_buffer
+{
+  struct gcov_fn_buffer *next;
+  unsigned fn_ix;
+  struct gcov_fn_info info;
+  /* note gcov_fn_info ends in a trailing array.  */
+};
+
 /* Chain of per-object gcov structures.  */
 static struct gcov_info *gcov_list;
 
@@ -134,6 +142,64 @@  create_file_directory (char *filename)
 #endif
 }
 
+static struct gcov_fn_buffer **
+buffer_fn_data (struct gcov_info *gi_ptr, struct gcov_fn_buffer **end_ptr,
+		unsigned fn_ix)
+{
+  unsigned n_ctrs = 0, ix;
+  struct gcov_fn_buffer *fn_buffer;
+
+  for (ix = GCOV_COUNTERS; ix--;)
+    if (gi_ptr->merge[ix])
+      n_ctrs++;
+
+  fn_buffer = (struct gcov_fn_buffer *)malloc
+    (sizeof (*fn_buffer) + sizeof (fn_buffer->info.ctrs[0]) * n_ctrs);
+
+  if (!fn_buffer)
+    return 0; /* We'll horribly fail.  */
+  
+  fn_buffer->next = 0;
+  fn_buffer->fn_ix = fn_ix;
+  fn_buffer->info.ident = gcov_read_unsigned ();
+  fn_buffer->info.lineno_checksum = gcov_read_unsigned ();
+  fn_buffer->info.cfg_checksum = gcov_read_unsigned ();
+
+  for (n_ctrs = ix = 0; ix != GCOV_COUNTERS; ix++)
+    {
+      gcov_unsigned_t length;
+      gcov_type *values;
+
+      if (!gi_ptr->merge[ix])
+	continue;
+      
+      if (gcov_read_unsigned () != GCOV_TAG_FOR_COUNTER (ix))
+	goto fail;
+
+      length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ());
+      values = (gcov_type *)malloc (length * sizeof (gcov_type));
+      if (!values)
+	{
+	  while (n_ctrs--)
+	    free (fn_buffer->info.ctrs[n_ctrs].values);
+	  goto fail;
+	}
+      fn_buffer->info.ctrs[n_ctrs].num = length;
+      fn_buffer->info.ctrs[n_ctrs].values = values;
+
+      while (length--)
+	*values++ = gcov_read_counter ();
+      n_ctrs++;
+    }
+  
+  *end_ptr = fn_buffer;
+  return &fn_buffer->next;
+
+ fail:
+  free (fn_buffer);
+  return 0;
+}
+
 /* Check if VERSION of the info block PTR matches libgcov one.
    Return 1 on success, or zero in case of versions mismatch.
    If FILENAME is not NULL, its value used for reporting purposes
@@ -169,39 +235,46 @@  static void
 gcov_exit (void)
 {
   struct gcov_info *gi_ptr;
-  struct gcov_summary this_program;
-  struct gcov_summary all;
+  const struct gcov_fn_info *gfi_ptr;
+  struct gcov_summary this_prg; /* summary for program.  */
+  struct gcov_summary all_prg;  /* summary for all instances of program.  */
   struct gcov_ctr_summary *cs_ptr;
   const struct gcov_ctr_info *ci_ptr;
-  unsigned t_ix;
+  unsigned t_ix, f_ix;
   gcov_unsigned_t c_num;
   const char *gcov_prefix;
   int gcov_prefix_strip = 0;
   size_t prefix_length;
   char *gi_filename, *gi_filename_up;
 
-  memset (&all, 0, sizeof (all));
+  memset (&all_prg, 0, sizeof (all_prg));
   /* Find the totals for this execution.  */
-  memset (&this_program, 0, sizeof (this_program));
+  memset (&this_prg, 0, sizeof (this_prg));
   for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
-    {
-      ci_ptr = gi_ptr->counts;
-      for (t_ix = 0; t_ix < GCOV_COUNTERS_SUMMABLE; t_ix++)
-	{
-	  if (!((1 << t_ix) & gi_ptr->ctr_mask))
-	    continue;
+    for (f_ix = 0; f_ix != gi_ptr->n_functions; f_ix++)
+      {
+	gfi_ptr = gi_ptr->functions[f_ix];
+	
+	if (!gfi_ptr || gfi_ptr->key != gi_ptr)
+	  continue;
+	
+	ci_ptr = gfi_ptr->ctrs;
+	for (t_ix = 0; t_ix != GCOV_COUNTERS_SUMMABLE; t_ix++)
+	  {
+	    if (!gi_ptr->merge[t_ix])
+	      continue;
 
-	  cs_ptr = &this_program.ctrs[t_ix];
-	  cs_ptr->num += ci_ptr->num;
-	  for (c_num = 0; c_num < ci_ptr->num; c_num++)
-	    {
-      	      cs_ptr->sum_all += ci_ptr->values[c_num];
-	      if (cs_ptr->run_max < ci_ptr->values[c_num])
-		cs_ptr->run_max = ci_ptr->values[c_num];
-	    }
-	  ci_ptr++;
-	}
-    }
+	    cs_ptr = &this_prg.ctrs[t_ix];
+	    cs_ptr->num += ci_ptr->num;
+	    for (c_num = 0; c_num < ci_ptr->num; c_num++)
+	      {
+		cs_ptr->sum_all += ci_ptr->values[c_num];
+		if (cs_ptr->run_max < ci_ptr->values[c_num])
+		  cs_ptr->run_max = ci_ptr->values[c_num];
+	      }
+	    ci_ptr++;
+	  }
+      }
 
   {
     /* Check if the level of dirs to strip off specified. */
@@ -214,6 +287,7 @@  gcov_exit (void)
 	  gcov_prefix_strip = 0;
       }
   }
+
   /* Get file name relocation prefix.  Non-absolute values are ignored. */
   gcov_prefix = getenv("GCOV_PREFIX");
   if (gcov_prefix)
@@ -243,24 +317,20 @@  gcov_exit (void)
   /* Now merge each file.  */
   for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
     {
-      struct gcov_summary this_object;
-      struct gcov_summary object, program;
-      gcov_type *values[GCOV_COUNTERS];
-      const struct gcov_fn_info *fi_ptr;
-      unsigned fi_stride;
-      unsigned c_ix, f_ix, n_counts;
-      struct gcov_ctr_summary *cs_obj, *cs_tobj, *cs_prg, *cs_tprg, *cs_all;
+      unsigned n_counts;
+      struct gcov_summary prg; /* summary for this object over all
+				  program.  */
+      struct gcov_ctr_summary *cs_prg, *cs_tprg, *cs_all;
       int error = 0;
       gcov_unsigned_t tag, length;
       gcov_position_t summary_pos = 0;
       gcov_position_t eof_pos = 0;
       const char *fname, *s;
+      struct gcov_fn_buffer *fn_buffer = 0;
+      struct gcov_fn_buffer **fn_tail = &fn_buffer;
 
       fname = gi_ptr->filename;
 
-      memset (&this_object, 0, sizeof (this_object));
-      memset (&object, 0, sizeof (object));
-
       /* Avoid to add multiple drive letters into combined path.  */
       if (prefix_length != 0 && HAS_DRIVE_SPEC(fname))
         fname += 2;
@@ -282,6 +352,7 @@  gcov_exit (void)
 		level++;
 	      }
         }
+
       /* Update complete filename with stripped original. */
       if (prefix_length != 0 && !IS_DIR_SEPARATOR (*fname))
         {
@@ -292,42 +363,6 @@  gcov_exit (void)
       else
         strcpy (gi_filename_up, fname);
 
-      /* Totals for this object file.  */
-      ci_ptr = gi_ptr->counts;
-      for (t_ix = 0; t_ix < GCOV_COUNTERS_SUMMABLE; t_ix++)
-	{
-	  if (!((1 << t_ix) & gi_ptr->ctr_mask))
-	    continue;
-
-	  cs_ptr = &this_object.ctrs[t_ix];
-	  cs_ptr->num += ci_ptr->num;
-	  for (c_num = 0; c_num < ci_ptr->num; c_num++)
-	    {
-	      cs_ptr->sum_all += ci_ptr->values[c_num];
-	      if (cs_ptr->run_max < ci_ptr->values[c_num])
-		cs_ptr->run_max = ci_ptr->values[c_num];
-	    }
-
-	  ci_ptr++;
-	}
-
-      c_ix = 0;
-      for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
-	if ((1 << t_ix) & gi_ptr->ctr_mask)
-	  {
-	    values[c_ix] = gi_ptr->counts[c_ix].values;
-	    c_ix++;
-	  }
-
-      /* Calculate the function_info stride. This depends on the
-	 number of counter types being measured.  */
-      fi_stride = sizeof (struct gcov_fn_info) + c_ix * sizeof (unsigned);
-      if (__alignof__ (struct gcov_fn_info) > sizeof (unsigned))
-	{
-	  fi_stride += __alignof__ (struct gcov_fn_info) - 1;
-	  fi_stride &= ~(__alignof__ (struct gcov_fn_info) - 1);
-	}
-
       if (!gcov_open (gi_filename))
 	{
 	  /* Open failed likely due to missed directory.
@@ -363,83 +398,98 @@  gcov_exit (void)
 	    /* Read from a different compilation. Overwrite the file.  */
 	    goto rewrite;
 
-	  /* Merge execution counts for each function.  */
-	  for (f_ix = 0; f_ix < gi_ptr->n_functions; f_ix++)
+	  /* Look for program summary.  */
+	  for (f_ix = ~0u;;)
 	    {
-	      fi_ptr = (const struct gcov_fn_info *)
-		      ((const char *) gi_ptr->functions + f_ix * fi_stride);
+	      struct gcov_summary tmp;
+	      
+	      eof_pos = gcov_position ();
 	      tag = gcov_read_unsigned ();
+	      if (tag != GCOV_TAG_PROGRAM_SUMMARY)
+		break;
+
 	      length = gcov_read_unsigned ();
+	      if (length != GCOV_TAG_SUMMARY_LENGTH)
+		goto read_mismatch;
+	      gcov_read_summary (&tmp);
+	      if ((error = gcov_is_error ()))
+		goto read_error;
+	      if (!summary_pos && tmp.checksum == gcov_crc32)
+		{
+		  prg = tmp;
+		  summary_pos = eof_pos;
+		}
+	    }
+	  
+	  /* Merge execution counts for each function.  */
+	  for (f_ix = 0; f_ix != gi_ptr->n_functions;
+	       f_ix++, tag = gcov_read_unsigned ())
+	    {
+	      gfi_ptr = gi_ptr->functions[f_ix];
+
+	      if (tag != GCOV_TAG_FUNCTION)
+		goto read_mismatch;
+	      length = gcov_read_unsigned ();
+
+	      if (!length)
+		/* This function did not appear in the other program.
+		   We have nothing to merge.  */
+		continue;
 
-	      /* Check function.  */
-	      if (tag != GCOV_TAG_FUNCTION
-	          || length != GCOV_TAG_FUNCTION_LENGTH
-		  || gcov_read_unsigned () != fi_ptr->ident
-		  || gcov_read_unsigned () != fi_ptr->lineno_checksum
-		  || gcov_read_unsigned () != fi_ptr->cfg_checksum)
+	      if (length != GCOV_TAG_FUNCTION_LENGTH)
+		goto read_mismatch;
+	      
+	      if (!gfi_ptr || gfi_ptr->key != gi_ptr)
 		{
-		read_mismatch:;
-		  fprintf (stderr, "profiling:%s:Merge mismatch for %s\n",
-			   gi_filename,
-			   f_ix + 1 ? "function" : "summaries");
-		  goto read_fatal;
+		  /* This function appears in the other program.  We
+		     need to buffer the information in order to write
+		     it back out -- we'll be inserting data before
+		     this point, so cannot simply keep the data in the
+		     file.  */
+		  fn_tail = buffer_fn_data (gi_ptr, fn_tail, f_ix);
+		  if (!fn_tail)
+		    goto read_mismatch;
+		  continue;
 		}
 
-	      c_ix = 0;
+	      if (gcov_read_unsigned () != gfi_ptr->ident
+		  || gcov_read_unsigned () != gfi_ptr->lineno_checksum
+		  || gcov_read_unsigned () != gfi_ptr->cfg_checksum)
+		goto read_mismatch;
+	      
+	      ci_ptr = gfi_ptr->ctrs;
 	      for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
 		{
-		  gcov_merge_fn merge;
+		  gcov_merge_fn merge = gi_ptr->merge[t_ix];
 
-		  if (!((1 << t_ix) & gi_ptr->ctr_mask))
+		  if (!merge)
 		    continue;
 
-		  n_counts = fi_ptr->n_ctrs[c_ix];
-		  merge = gi_ptr->counts[c_ix].merge;
-
 		  tag = gcov_read_unsigned ();
 		  length = gcov_read_unsigned ();
 		  if (tag != GCOV_TAG_FOR_COUNTER (t_ix)
-		      || length != GCOV_TAG_COUNTER_LENGTH (n_counts))
+		      || length != GCOV_TAG_COUNTER_LENGTH (ci_ptr->num))
 		    goto read_mismatch;
-		  (*merge) (values[c_ix], n_counts);
-		  values[c_ix] += n_counts;
-		  c_ix++;
+		  (*merge) (ci_ptr->values, ci_ptr->num);
+		  ci_ptr++;
 		}
 	      if ((error = gcov_is_error ()))
 		goto read_error;
 	    }
 
-	  f_ix = ~0u;
-	  /* Check program & object summary */
-	  while (1)
+	  if (tag)
 	    {
-	      int is_program;
-
-	      eof_pos = gcov_position ();
-	      tag = gcov_read_unsigned ();
-	      if (!tag)
-		break;
-
-	      length = gcov_read_unsigned ();
-	      is_program = tag == GCOV_TAG_PROGRAM_SUMMARY;
-	      if (length != GCOV_TAG_SUMMARY_LENGTH
-		  || (!is_program && tag != GCOV_TAG_OBJECT_SUMMARY))
-		goto read_mismatch;
-	      gcov_read_summary (is_program ? &program : &object);
-	      if ((error = gcov_is_error ()))
-		goto read_error;
-	      if (is_program && program.checksum == gcov_crc32)
-		{
-		  summary_pos = eof_pos;
-		  goto rewrite;
-		}
+	    read_mismatch:;
+	      fprintf (stderr, "profiling:%s:Merge mismatch for %s\n",
+		       gi_filename, f_ix + 1 ? "function" : "summaries");
+	      goto read_fatal;
 	    }
 	}
       goto rewrite;
 
     read_error:;
-      fprintf (stderr, error < 0 ? "profiling:%s:Overflow merging\n"
-	       : "profiling:%s:Error merging\n", gi_filename);
+      fprintf (stderr, "profiling:%s:%s merging\n", gi_filename,
+	       error < 0 ? "Overflow": "Error");
 
     read_fatal:;
       gcov_close ();
@@ -448,29 +498,20 @@  gcov_exit (void)
     rewrite:;
       gcov_rewrite ();
       if (!summary_pos)
-	memset (&program, 0, sizeof (program));
+	{
+	  memset (&prg, 0, sizeof (prg));
+	  summary_pos = eof_pos;
+	}
 
       /* Merge the summaries.  */
-      f_ix = ~0u;
       for (t_ix = 0; t_ix < GCOV_COUNTERS_SUMMABLE; t_ix++)
 	{
-	  cs_obj = &object.ctrs[t_ix];
-	  cs_tobj = &this_object.ctrs[t_ix];
-	  cs_prg = &program.ctrs[t_ix];
-	  cs_tprg = &this_program.ctrs[t_ix];
-	  cs_all = &all.ctrs[t_ix];
-
-	  if ((1 << t_ix) & gi_ptr->ctr_mask)
-	    {
-	      if (!cs_obj->runs++)
-		cs_obj->num = cs_tobj->num;
-	      else if (cs_obj->num != cs_tobj->num)
-		goto read_mismatch;
-	      cs_obj->sum_all += cs_tobj->sum_all;
-	      if (cs_obj->run_max < cs_tobj->run_max)
-		cs_obj->run_max = cs_tobj->run_max;
-	      cs_obj->sum_max += cs_tobj->run_max;
+	  cs_prg = &prg.ctrs[t_ix];
+	  cs_tprg = &this_prg.ctrs[t_ix];
+	  cs_all = &all_prg.ctrs[t_ix];
 
+	  if (gi_ptr->merge[t_ix])
+	    {
 	      if (!cs_prg->runs++)
 		cs_prg->num = cs_tprg->num;
 	      else if (cs_prg->num != cs_tprg->num)
@@ -480,78 +521,94 @@  gcov_exit (void)
 		cs_prg->run_max = cs_tprg->run_max;
 	      cs_prg->sum_max += cs_tprg->run_max;
 	    }
-	  else if (cs_obj->num || cs_prg->num)
+	  else if (cs_prg->runs)
 	    goto read_mismatch;
 
 	  if (!cs_all->runs && cs_prg->runs)
 	    memcpy (cs_all, cs_prg, sizeof (*cs_all));
-	  else if (!all.checksum
+	  else if (!all_prg.checksum
 		   && (!GCOV_LOCKED || cs_all->runs == cs_prg->runs)
 		   && memcmp (cs_all, cs_prg, sizeof (*cs_all)))
 	    {
-	      fprintf (stderr, "profiling:%s:Invocation mismatch - some data files may have been removed%s",
+	      fprintf (stderr, "profiling:%s:Invocation mismatch - some data files may have been removed%s\n",
 		       gi_filename, GCOV_LOCKED
-		       ? "" : " or concurrent update without locking support");
-	      all.checksum = ~0u;
+		       ? "" : " or concurrently updated without locking support");
+	      all_prg.checksum = ~0u;
 	    }
 	}
 
-      c_ix = 0;
-      for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
-	if ((1 << t_ix) & gi_ptr->ctr_mask)
-	  {
-	    values[c_ix] = gi_ptr->counts[c_ix].values;
-	    c_ix++;
-	  }
-
-      program.checksum = gcov_crc32;
+      prg.checksum = gcov_crc32;
 
       /* Write out the data.  */
-      gcov_write_tag_length (GCOV_DATA_MAGIC, GCOV_VERSION);
-      gcov_write_unsigned (gi_ptr->stamp);
+      if (!eof_pos)
+	{
+	  gcov_write_tag_length (GCOV_DATA_MAGIC, GCOV_VERSION);
+	  gcov_write_unsigned (gi_ptr->stamp);
+	}
+
+      if (summary_pos)
+	gcov_seek (summary_pos);
+
+      /* Generate whole program statistics.  */
+      gcov_write_summary (GCOV_TAG_PROGRAM_SUMMARY, &prg);
+
+      if (summary_pos < eof_pos)
+	gcov_seek (eof_pos);
 
       /* Write execution counts for each function.  */
       for (f_ix = 0; f_ix < gi_ptr->n_functions; f_ix++)
 	{
-	  fi_ptr = (const struct gcov_fn_info *)
-		  ((const char *) gi_ptr->functions + f_ix * fi_stride);
+	  unsigned buffered = 0;
 
-	  /* Announce function.  */
-	  gcov_write_tag_length (GCOV_TAG_FUNCTION, GCOV_TAG_FUNCTION_LENGTH);
-	  gcov_write_unsigned (fi_ptr->ident);
-	  gcov_write_unsigned (fi_ptr->lineno_checksum);
-	  gcov_write_unsigned (fi_ptr->cfg_checksum);
+	  if (fn_buffer && fn_buffer->fn_ix == f_ix)
+	    {
+	      /* Buffered data from another program.  */
+	      buffered = 1;
+	      gfi_ptr = &fn_buffer->info;
+	      length = GCOV_TAG_FUNCTION_LENGTH;
+	    }
+	  else
+	    {
+	      gfi_ptr = gi_ptr->functions[f_ix];
+	      if (gfi_ptr && gfi_ptr->key == gi_ptr)
+		length = GCOV_TAG_FUNCTION_LENGTH;
+	      else
+		length = 0;
+	    }
+	  
+	  gcov_write_tag_length (GCOV_TAG_FUNCTION, length);
+	  if (!length)
+	    continue;
+	  
+	  gcov_write_unsigned (gfi_ptr->ident);
+	  gcov_write_unsigned (gfi_ptr->lineno_checksum);
+	  gcov_write_unsigned (gfi_ptr->cfg_checksum);
 
-	  c_ix = 0;
+	  ci_ptr = gfi_ptr->ctrs;
 	  for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
 	    {
-	      gcov_type *c_ptr;
-
-	      if (!((1 << t_ix) & gi_ptr->ctr_mask))
+	      if (!gi_ptr->merge[t_ix])
 		continue;
 
-	      n_counts = fi_ptr->n_ctrs[c_ix];
-
+	      n_counts = ci_ptr->num;
 	      gcov_write_tag_length (GCOV_TAG_FOR_COUNTER (t_ix),
 				     GCOV_TAG_COUNTER_LENGTH (n_counts));
-	      c_ptr = values[c_ix];
+	      gcov_type *c_ptr = ci_ptr->values;
 	      while (n_counts--)
 		gcov_write_counter (*c_ptr++);
-
-	      values[c_ix] = c_ptr;
-	      c_ix++;
+	      if (buffered)
+		free (ci_ptr->values);
+	      ci_ptr++;
+	    }
+	  if (buffered)
+	    {
+	      struct gcov_fn_buffer *tmp = fn_buffer;
+	      fn_buffer = fn_buffer->next;
+	      free (tmp);
 	    }
 	}
 
-      /* Object file summary.  */
-      gcov_write_summary (GCOV_TAG_OBJECT_SUMMARY, &object);
-
-      /* Generate whole program statistics.  */
-      if (eof_pos)
-	gcov_seek (eof_pos);
-      gcov_write_summary (GCOV_TAG_PROGRAM_SUMMARY, &program);
-      if (!summary_pos)
-	gcov_write_unsigned (0);
+      gcov_write_unsigned (0);
       if ((error = gcov_close ()))
 	  fprintf (stderr, error  < 0 ?
 		   "profiling:%s:Overflow writing\n" :
@@ -617,15 +674,25 @@  __gcov_flush (void)
   gcov_exit ();
   for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
     {
-      unsigned t_ix;
-      const struct gcov_ctr_info *ci_ptr;
+      unsigned f_ix;
 
-      for (t_ix = 0, ci_ptr = gi_ptr->counts; t_ix != GCOV_COUNTERS; t_ix++)
-	if ((1 << t_ix) & gi_ptr->ctr_mask)
-	  {
-	    memset (ci_ptr->values, 0, sizeof (gcov_type) * ci_ptr->num);
-	    ci_ptr++;
-	  }
+      for (f_ix = 0; f_ix < gi_ptr->n_functions; f_ix++)
+	{
+	  unsigned t_ix;
+	  const struct gcov_fn_info *gfi_ptr = gi_ptr->functions[f_ix];
+
+	  if (!gfi_ptr || gfi_ptr->key != gi_ptr)
+	    continue;
+	  const struct gcov_ctr_info *ci_ptr = gfi_ptr->ctrs;
+	  for (t_ix = 0; t_ix != GCOV_COUNTERS; t_ix++)
+	    {
+	      if (!gi_ptr->merge[t_ix])
+		continue;
+	      
+	      memset (ci_ptr->values, 0, sizeof (gcov_type) * ci_ptr->num);
+	      ci_ptr++;
+	    }
+	}
     }
 }
 
Index: gcc/gcov.c
===================================================================
--- gcc/gcov.c	(revision 180739)
+++ gcc/gcov.c	(working copy)
@@ -265,7 +265,7 @@  static unsigned source_index;
 
 /* This holds data summary information.  */
 
-static struct gcov_summary object_summary;
+static unsigned object_runs;
 static unsigned program_count;
 
 /* Modification time of graph file.  */
@@ -362,6 +362,7 @@  static int output_branch_count (FILE *,
 static void output_lines (FILE *, const source_t *);
 static char *make_gcov_file_name (const char *, const char *);
 static void release_structures (void);
+static void release_function (function_t *);
 extern int main (int, char **);
 
 int
@@ -537,7 +538,7 @@  static void
 process_file (const char *file_name)
 {
   function_t *fn;
-  function_t *fn_p;
+  function_t **fn_p;
   function_t *old_functions;
 
   /* Save and clear the list of current functions.  They will be appended
@@ -558,11 +559,25 @@  process_file (const char *file_name)
   if (read_count_file ())
     return;
 
-  for (fn_p = NULL, fn = functions; fn; fn_p = fn, fn = fn->next)
-    solve_flow_graph (fn);
+  fn_p = &functions;
+  while ((fn = *fn_p) != NULL)
+    {
+      if (fn->counts)
+	{
+	  solve_flow_graph (fn);
+	  fn_p = &fn->next;
+	}
+      else
+	{
+	  /* The function was not in the executable -- some other
+	     instance must have been selected.  */
+	  function_t *next = fn->next;
+	  release_function (fn);
+	  *fn_p = next;
+	}
+    }
 
-  if (fn_p)
-    fn_p->next = old_functions;
+  *fn_p = old_functions;
 }
 
 static void
@@ -591,7 +606,7 @@  generate_results (const char *file_name)
     {
       accumulate_line_counts (src);
       function_summary (&src->coverage, "File");
-      if (flag_gcov_file)
+      if (flag_gcov_file && src->coverage.lines)
 	{
 	  char *gcov_file_name = make_gcov_file_name (file_name, src->name);
 	  FILE *gcov_file = fopen (gcov_file_name, "w");
@@ -615,6 +630,28 @@  generate_results (const char *file_name)
     }
 }
 
+/* Release a function structure */
+
+static void
+release_function (function_t *fn)
+{
+  unsigned ix;
+  block_t *block;
+
+  for (ix = fn->num_blocks, block = fn->blocks; ix--; block++)
+    {
+      arc_t *arc, *arc_n;
+
+      for (arc = block->succ; arc; arc = arc_n)
+	{
+	  arc_n = arc->succ_next;
+	  free (arc);
+	}
+    }
+  free (fn->blocks);
+  free (fn->counts);
+}
+
 /* Release all memory used.  */
 
 static void
@@ -633,22 +670,8 @@  release_structures (void)
 
   while ((fn = functions))
     {
-      unsigned ix;
-      block_t *block;
-
       functions = fn->next;
-      for (ix = fn->num_blocks, block = fn->blocks; ix--; block++)
-	{
-	  arc_t *arc, *arc_n;
-
-	  for (arc = block->succ; arc; arc = arc_n)
-	    {
-	      arc_n = arc->succ_next;
-	      free (arc);
-	    }
-	}
-      free (fn->blocks);
-      free (fn->counts);
+      release_function (fn);
     }
 }
 
@@ -1085,35 +1108,39 @@  read_count_file (void)
       unsigned length = gcov_read_unsigned ();
       unsigned long base = gcov_position ();
 
-      if (tag == GCOV_TAG_OBJECT_SUMMARY)
-	gcov_read_summary (&object_summary);
-      else if (tag == GCOV_TAG_PROGRAM_SUMMARY)
-	program_count++;
-      else if (tag == GCOV_TAG_FUNCTION)
+      if (tag == GCOV_TAG_PROGRAM_SUMMARY)
 	{
-	  {
-	    unsigned ident = gcov_read_unsigned ();
-	    struct function_info *fn_n = functions;
+	  struct gcov_summary summary;
+	  gcov_read_summary (&summary);
+	  object_runs += summary.ctrs[GCOV_COUNTER_ARCS].runs;
+	  program_count++;
+	}
+      else if (tag == GCOV_TAG_FUNCTION && !length)
+	; /* placeholder  */
+      else if (tag == GCOV_TAG_FUNCTION && length == GCOV_TAG_FUNCTION_LENGTH)
+	{
+	  unsigned ident;
+	  struct function_info *fn_n;
 
-	    /* Try to find the function in the list.
-	       To speed up the search, first start from the last function
-	       found.   */
-	    for (fn = fn ? fn->next : NULL; ; fn = fn->next)
-	      {
-		if (fn)
-		  ;
-		else if ((fn = fn_n))
-		  fn_n = NULL;
-		else
-		  {
-		    fnotice (stderr, "%s:unknown function '%u'\n",
-			     da_file_name, ident);
-		    break;
-		  }
-		if (fn->ident == ident)
+	  /* Try to find the function in the list.  To speed up the
+	     search, first start from the last function found.  */
+	  ident = gcov_read_unsigned ();
+	  fn_n = functions;
+	  for (fn = fn ? fn->next : NULL; ; fn = fn->next)
+	    {
+	      if (fn)
+		;
+	      else if ((fn = fn_n))
+		fn_n = NULL;
+	      else
+		{
+		  fnotice (stderr, "%s:unknown function '%u'\n",
+			   da_file_name, ident);
 		  break;
-	      }
-	  }
+		}
+	      if (fn->ident == ident)
+		break;
+	    }
 
 	  if (!fn)
 	    ;
@@ -1908,8 +1935,7 @@  output_lines (FILE *gcov_file, const sou
       fprintf (gcov_file, "%9s:%5d:Graph:%s\n", "-", 0, bbg_file_name);
       fprintf (gcov_file, "%9s:%5d:Data:%s\n", "-", 0,
 	       no_data_file ? "-" : da_file_name);
-      fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0,
-	       object_summary.ctrs[GCOV_COUNTER_ARCS].runs);
+      fprintf (gcov_file, "%9s:%5d:Runs:%u\n", "-", 0, object_runs);
     }
   fprintf (gcov_file, "%9s:%5d:Programs:%u\n", "-", 0, program_count);
 
Index: gcc/testsuite/gcc.dg/profile-dir-1.c
===================================================================
--- gcc/testsuite/gcc.dg/profile-dir-1.c	(revision 180739)
+++ gcc/testsuite/gcc.dg/profile-dir-1.c	(working copy)
@@ -1,6 +1,6 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O -fprofile-generate=. -fdump-ipa-profile" } */
-/* { dg-final { scan-ipa-dump " ./profile-dir-1.gcda" "profile" } } */
+/* { dg-options "-O -fprofile-generate=. -fdump-ipa-cgraph" } */
+/* { dg-final { scan-ipa-dump " ./profile-dir-1.gcda" "cgraph" } } */
 
 int
 main(void)
@@ -8,4 +8,4 @@  main(void)
   return 0;
 }
 
-/* { dg-final { cleanup-ipa-dump "profile" } } */
+/* { dg-final { cleanup-ipa-dump "cgraph" } } */
Index: gcc/testsuite/gcc.dg/profile-dir-2.c
===================================================================
--- gcc/testsuite/gcc.dg/profile-dir-2.c	(revision 180739)
+++ gcc/testsuite/gcc.dg/profile-dir-2.c	(working copy)
@@ -1,6 +1,6 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O -fprofile-generate -fdump-ipa-profile" } */
-/* { dg-final { scan-ipa-dump "/profile-dir-2.gcda" "profile" } } */
+/* { dg-options "-O -fprofile-generate -fdump-ipa-cgraph" } */
+/* { dg-final { scan-ipa-dump "/profile-dir-2.gcda" "cgraph" } } */
 
 int
 main(void)
@@ -8,4 +8,4 @@  main(void)
   return 0;
 }
 
-/* { dg-final { cleanup-ipa-dump "profile" } } */
+/* { dg-final { cleanup-ipa-dump "cgraph" } } */
Index: gcc/testsuite/gcc.dg/profile-dir-3.c
===================================================================
--- gcc/testsuite/gcc.dg/profile-dir-3.c	(revision 180739)
+++ gcc/testsuite/gcc.dg/profile-dir-3.c	(working copy)
@@ -1,6 +1,6 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O -fprofile-generate -fprofile-dir=. -fdump-ipa-profile" } */
-/* { dg-final { scan-ipa-dump " ./profile-dir-3.gcda" "profile" } } */
+/* { dg-options "-O -fprofile-generate -fprofile-dir=. -fdump-ipa-cgraph" } */
+/* { dg-final { scan-ipa-dump " ./profile-dir-3.gcda" "cgraph" } } */
 
 int
 main(void)
@@ -8,4 +8,4 @@  main(void)
   return 0;
 }
 
-/* { dg-final { cleanup-ipa-dump "profile" } } */
+/* { dg-final { cleanup-ipa-dump "cgraph" } } */
Index: gcc/testsuite/gcc.misc-tests/gcov-12.c
===================================================================
--- gcc/testsuite/gcc.misc-tests/gcov-12.c	(revision 0)
+++ gcc/testsuite/gcc.misc-tests/gcov-12.c	(revision 0)
@@ -0,0 +1,17 @@ 
+/* Test gcov weak ellision.  */
+
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-require-weak "" } */
+/* { dg-do run { target native } } */
+
+int __attribute__ ((weak)) weak ()
+{
+  return 0;  /* count(1) */
+}
+
+int main ()
+{
+  return weak (); /* count(1) */
+}
+
+/* { dg-final { run-gcov { -a gcov-12.c } } } */
Index: gcc/testsuite/gcc.misc-tests/gcov-13.c
===================================================================
--- gcc/testsuite/gcc.misc-tests/gcov-13.c	(revision 0)
+++ gcc/testsuite/gcc.misc-tests/gcov-13.c	(revision 0)
@@ -0,0 +1,18 @@ 
+/* Test gcov weak ellision.  */
+
+/* { dg-options "-fprofile-arcs -ftest-coverage" } */
+/* { dg-require-weak "" } */
+/* { dg-do run { target native } } */
+/* { dg-additional-sources "gcovpart-13b.c" } */
+
+int __attribute__ ((weak)) weak ()
+{
+  return 1;  /* count(-) */
+}
+
+int main ()
+{
+  return weak (); /* count(1) */
+}
+
+/* { dg-final { run-gcov { -a gcov-13.c } } } */
Index: gcc/testsuite/gcc.misc-tests/gcov-14.c
===================================================================
--- gcc/testsuite/gcc.misc-tests/gcov-14.c	(revision 0)
+++ gcc/testsuite/gcc.misc-tests/gcov-14.c	(revision 0)
@@ -0,0 +1,24 @@ 
+/* Test gcov extern inline.  */
+
+/* { dg-options "-O2 -fprofile-arcs -ftest-coverage" } */
+/* { dg-require-weak "" } */
+/* { dg-do run { target native } } */
+
+extern int __attribute__ ((weak)) Foo ();
+
+extern __inline int Foo ()
+{
+  return 0; /* count(-) */
+}
+
+int (* __attribute__ ((noinline)) Bar ()) ()
+{
+  return Foo; /* count(1) */
+}
+
+int main ()
+{
+  return Bar () != 0; /* count(1) */
+}
+
+/* { dg-final { run-gcov { -a gcov-14.c } } } */
Index: gcc/testsuite/gcc.misc-tests/gcov.exp
===================================================================
--- gcc/testsuite/gcc.misc-tests/gcov.exp	(revision 180739)
+++ gcc/testsuite/gcc.misc-tests/gcov.exp	(working copy)
@@ -33,7 +33,7 @@  if { ![is_remote host] && [string match
 dg-init
 
 # Delete old .gcda files.
-set files [glob -nocomplain gcov-*.gcda]
+set files [glob -nocomplain gcov*.gcda]
 if { $files != "" } {
     eval "remote_file build delete $files"
 }
Index: gcc/testsuite/gcc.misc-tests/gcovpart-13b.c
===================================================================
--- gcc/testsuite/gcc.misc-tests/gcovpart-13b.c	(revision 0)
+++ gcc/testsuite/gcc.misc-tests/gcovpart-13b.c	(revision 0)
@@ -0,0 +1,4 @@ 
+int weak ()
+{
+  return 0;  /* count(1) */
+}
Index: gcc/gcov-io.h
===================================================================
--- gcc/gcov-io.h	(revision 180739)
+++ gcc/gcov-io.h	(working copy)
@@ -130,26 +130,26 @@  see the files COPYING3 and COPYING.RUNTI
    blocks they are for.
 
    The data file contains the following records.
-        data: {unit function-data* summary:object summary:program*}*
+        data: {unit summary:object summary:program* function-data*}*
 	unit: header int32:checksum
-        function-data:	announce_function arc_counts
+        function-data:	announce_function present counts
 	announce_function: header int32:ident
 		int32:lineno_checksum int32:cfg_checksum
-	arc_counts: header int64:count*
-	summary: int32:checksum {count-summary}GCOV_COUNTERS
+	present: header int32:present
+	counts: header int64:count*
+	summary: int32:checksum {count-summary}GCOV_COUNTERS_SUMMABLE
 	count-summary:	int32:num int32:runs int64:sum
 			int64:max int64:sum_max
 
    The ANNOUNCE_FUNCTION record is the same as that in the note file,
-   but without the source location.  The ARC_COUNTS gives the counter
-   values for those arcs that are instrumented.  The SUMMARY records
-   give information about the whole object file and about the whole
+   but without the source location.  The COUNTS gives the
+   counter values for instrumented features.  The about the whole
    program.  The checksum is used for whole program summaries, and
    disambiguates different programs which include the same
    instrumented object file.  There may be several program summaries,
-   each with a unique checksum.  The object summary's checksum is zero.
-   Note that the data file might contain information from several runs
-   concatenated, or the data might be merged.
+   each with a unique checksum.  The object summary's checksum is
+   zero.  Note that the data file might contain information from
+   several runs concatenated, or the data might be merged.
 
    This file is included by both the compiler, gcov tools and the
    runtime support library libgcov. IN_LIBGCOV and IN_GCOV are used to
@@ -307,7 +307,7 @@  typedef HOST_WIDEST_INT gcov_type;
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2)
 #define GCOV_TAG_COUNTER_NUM(LENGTH) ((LENGTH) / 2)
-#define GCOV_TAG_OBJECT_SUMMARY  ((gcov_unsigned_t)0xa1000000)
+#define GCOV_TAG_OBJECT_SUMMARY  ((gcov_unsigned_t)0xa1000000) /* Obsolete */
 #define GCOV_TAG_PROGRAM_SUMMARY ((gcov_unsigned_t)0xa3000000)
 #define GCOV_TAG_SUMMARY_LENGTH  \
 	(1 + GCOV_COUNTERS_SUMMABLE * (2 + 3 * 2))
@@ -343,7 +343,7 @@  typedef HOST_WIDEST_INT gcov_type;
 
   /* A list of human readable names of the counters */
 #define GCOV_COUNTER_NAMES	{"arcs", "interval", "pow2", "single", \
-				 "delta","indirect_call", "average", "ior"}
+      				 "delta", "indirect_call", "average", "ior"}
 
   /* Names of merge functions for counters.  */
 #define GCOV_MERGE_FUNCTIONS	{"__gcov_merge_add",	\
@@ -410,30 +410,31 @@  struct gcov_summary
    by write_profile must match these.  */
 
 #if IN_LIBGCOV
+/* Information about counters for a single function.  */
+struct gcov_ctr_info
+{
+  gcov_unsigned_t num;		/* number of counters.  */
+  gcov_type *values;		/* their values.  */
+};
+
 /* Information about a single function.  This uses the trailing array
-   idiom. The number of counters is determined from the counter_mask
-   in gcov_info.  We hold an array of function info, so have to
-   explicitly calculate the correct array stride.  */
+   idiom. The number of counters is determined from the merge pointer
+   array in gcov_info.  The key is used to detect which of a set of
+   comdat functions was selected -- it points to the gcov_info object
+   of the object file containing the selected comdat function.  */
 
 struct gcov_fn_info
 {
-  gcov_unsigned_t ident;	/* unique ident of function */
+  const struct gcov_info *key;		/* comdat key */
+  gcov_unsigned_t ident;		/* unique ident of function */
   gcov_unsigned_t lineno_checksum;	/* function lineo_checksum */
-  gcov_unsigned_t cfg_checksum;	/* function cfg checksum */
-  unsigned n_ctrs[0];		/* instrumented counters */
+  gcov_unsigned_t cfg_checksum;		/* function cfg checksum */
+  struct gcov_ctr_info ctrs[0];		/* instrumented counters */
 };
 
 /* Type of function used to merge counters.  */
 typedef void (*gcov_merge_fn) (gcov_type *, gcov_unsigned_t);
 
-/* Information about counters.  */
-struct gcov_ctr_info
-{
-  gcov_unsigned_t num;		/* number of counters.  */
-  gcov_type *values;		/* their values.  */
-  gcov_merge_fn merge;  	/* The function used to merge them.  */
-};
-
 /* Information about a single object file.  */
 struct gcov_info
 {
@@ -443,14 +444,12 @@  struct gcov_info
   gcov_unsigned_t stamp;	/* uniquifying time stamp */
   const char *filename;		/* output file name */
 
+  gcov_merge_fn merge[GCOV_COUNTERS];  /* merge functions (null for
+					  unused) */
+  
   unsigned n_functions;		/* number of functions */
-  const struct gcov_fn_info *functions; /* table of functions */
-
-  unsigned ctr_mask;		/* mask of counters instrumented.  */
-  struct gcov_ctr_info counts[0]; /* count data. The number of bits
-				     set in the ctr_mask field
-				     determines how big this array
-				     is.  */
+  const struct gcov_fn_info *functions[0]; /* pointers to function
+					      information  */
 };
 
 /* Register a new object file module.  */
Index: gcc/coverage.c
===================================================================
--- gcc/coverage.c	(revision 180739)
+++ gcc/coverage.c	(working copy)
@@ -1,6 +1,6 @@ 
 /* Read and write coverage files, and associated functionality.
    Copyright (C) 1990, 1991, 1992, 1993, 1994, 1996, 1997, 1998, 1999,
-   2000, 2001, 2003, 2004, 2005, 2007, 2008, 2009, 2010
+   2000, 2001, 2003, 2004, 2005, 2007, 2008, 2009, 2010, 2011
    Free Software Foundation, Inc.
    Contributed by James E. Wilson, UC Berkeley/Cygnus Support;
    based on some ideas from Dain Samples of UC Berkeley.
@@ -54,13 +54,14 @@  along with GCC; see the file COPYING3.
 #include "gcov-io.h"
 #include "gcov-io.c"
 
-struct function_list
+struct GTY((chain_next ("%h.next"))) function_list
 {
   struct function_list *next;	 /* next function */
   unsigned ident;		 /* function ident */
   unsigned lineno_checksum;	 /* function lineno checksum */
   unsigned cfg_checksum;	 /* function cfg checksum */
-  unsigned n_ctrs[GCOV_COUNTERS];/* number of counters.  */
+  tree fn_decl;			 /* the function decl */
+  tree ctr_vars[GCOV_COUNTERS];	 /* counter variables.  */
 };
 
 /* Counts information for a function.  */
@@ -75,22 +76,18 @@  typedef struct counts_entry
   unsigned cfg_checksum;
   gcov_type *counts;
   struct gcov_ctr_summary summary;
-
-  /* Workspace */
-  struct counts_entry *chain;
-
 } counts_entry_t;
 
-static struct function_list *functions_head = 0;
+static GTY(()) struct function_list *functions_head = 0;
 static struct function_list **functions_tail = &functions_head;
 static unsigned no_coverage = 0;
 
 /* Cumulative counter information for whole program.  */
 static unsigned prg_ctr_mask; /* Mask of counter types generated.  */
-static unsigned prg_n_ctrs[GCOV_COUNTERS]; /* Total counters allocated.  */
 
 /* Counter information for current function.  */
 static unsigned fn_ctr_mask; /* Mask of counters used.  */
+static GTY(()) tree fn_v_ctrs[GCOV_COUNTERS];   /* counter variables.  */
 static unsigned fn_n_ctrs[GCOV_COUNTERS]; /* Counters allocated.  */
 static unsigned fn_b_ctrs[GCOV_COUNTERS]; /* Allocation base.  */
 
@@ -105,9 +102,6 @@  static char *da_file_name;
 /* Hash table of count data.  */
 static htab_t counts_hash = NULL;
 
-/* Trees representing the counter table arrays.  */
-static GTY(()) tree tree_ctr_tables[GCOV_COUNTERS];
-
 /* The names of merge functions for counters.  */
 static const char *const ctr_merge_functions[GCOV_COUNTERS] = GCOV_MERGE_FUNCTIONS;
 static const char *const ctr_names[GCOV_COUNTERS] = GCOV_COUNTER_NAMES;
@@ -117,11 +111,11 @@  static hashval_t htab_counts_entry_hash
 static int htab_counts_entry_eq (const void *, const void *);
 static void htab_counts_entry_del (void *);
 static void read_counts_file (void);
-static tree build_fn_info_type (unsigned);
-static tree build_fn_info_value (const struct function_list *, tree);
-static tree build_ctr_info_type (void);
-static tree build_ctr_info_value (unsigned, tree);
-static tree build_gcov_info (void);
+static tree build_var (tree, tree, int);
+static void build_fn_info_type (tree, unsigned, tree);
+static tree build_fn_info (const struct function_list *, tree, tree);
+static void build_info_type (tree, unsigned, tree);
+static tree build_info (tree, tree, tree, unsigned);
 static void create_coverage (void);
 
 /* Return the type node for gcov_type.  */
@@ -172,8 +166,8 @@  static void
 read_counts_file (void)
 {
   gcov_unsigned_t fn_ident = 0;
-  counts_entry_t *summaried = NULL;
-  unsigned seen_summary = 0;
+  struct gcov_summary summary;
+  unsigned new_summary = 1;
   gcov_unsigned_t tag;
   int is_error = 0;
   unsigned lineno_checksum = 0;
@@ -216,42 +210,34 @@  read_counts_file (void)
       offset = gcov_position ();
       if (tag == GCOV_TAG_FUNCTION)
 	{
-	  fn_ident = gcov_read_unsigned ();
-	  lineno_checksum = gcov_read_unsigned ();
-	  cfg_checksum = gcov_read_unsigned ();
-	  if (seen_summary)
+	  if (length)
 	    {
-	      /* We have already seen a summary, this means that this
-		 new function begins a new set of program runs. We
-		 must unlink the summaried chain.  */
-	      counts_entry_t *entry, *chain;
-
-	      for (entry = summaried; entry; entry = chain)
-		{
-		  chain = entry->chain;
-		  entry->chain = NULL;
-		}
-	      summaried = NULL;
-	      seen_summary = 0;
+	      fn_ident = gcov_read_unsigned ();
+	      lineno_checksum = gcov_read_unsigned ();
+	      cfg_checksum = gcov_read_unsigned ();
 	    }
+	  else
+	    fn_ident = lineno_checksum = cfg_checksum = 0;
+	  new_summary = 1;
 	}
       else if (tag == GCOV_TAG_PROGRAM_SUMMARY)
 	{
-	  counts_entry_t *entry;
-	  struct gcov_summary summary;
+	  struct gcov_summary sum;
+	  unsigned ix;
 
-	  gcov_read_summary (&summary);
-	  seen_summary = 1;
-	  for (entry = summaried; entry; entry = entry->chain)
-	    {
-	      struct gcov_ctr_summary *csum = &summary.ctrs[entry->ctr];
+	  if (new_summary)
+	    memset (&summary, 0, sizeof (summary));
 
-	      entry->summary.runs += csum->runs;
-	      entry->summary.sum_all += csum->sum_all;
-	      if (entry->summary.run_max < csum->run_max)
-		entry->summary.run_max = csum->run_max;
-	      entry->summary.sum_max += csum->sum_max;
+	  gcov_read_summary (&sum);
+	  for (ix = 0; ix != GCOV_COUNTERS_SUMMABLE; ix++)
+	    {
+	      summary.ctrs[ix].runs += sum.ctrs[ix].runs;
+	      summary.ctrs[ix].sum_all += sum.ctrs[ix].sum_all;
+	      if (summary.ctrs[ix].run_max < sum.ctrs[ix].run_max)
+		summary.ctrs[ix].run_max = sum.ctrs[ix].run_max;
+	      summary.ctrs[ix].sum_max += sum.ctrs[ix].sum_max;
 	    }
+	  new_summary = 0;
 	}
       else if (GCOV_TAG_IS_COUNTER (tag) && fn_ident)
 	{
@@ -272,6 +258,7 @@  read_counts_file (void)
 	      entry->ctr = elt.ctr;
 	      entry->lineno_checksum = lineno_checksum;
 	      entry->cfg_checksum = cfg_checksum;
+	      entry->summary = summary.ctrs[elt.ctr];
 	      entry->summary.num = n_counts;
 	      entry->counts = XCNEWVEC (gcov_type, n_counts);
 	    }
@@ -298,15 +285,13 @@  read_counts_file (void)
 		     ctr_names[elt.ctr], fn_ident);
 	      goto skip_merge;
 	    }
-
-	  if (elt.ctr < GCOV_COUNTERS_SUMMABLE
-	      /* This should always be true for a just allocated entry,
-		 and always false for an existing one. Check this way, in
-		 case the gcov file is corrupt.  */
-	      && (!entry->chain || summaried != entry))
+	  else
 	    {
-	      entry->chain = summaried;
-	      summaried = entry;
+	      entry->summary.runs += summary.ctrs[elt.ctr].runs;
+	      entry->summary.sum_all += summary.ctrs[elt.ctr].sum_all;
+	      if (entry->summary.run_max < summary.ctrs[elt.ctr].run_max)
+		entry->summary.run_max = summary.ctrs[elt.ctr].run_max;
+	      entry->summary.sum_max += summary.ctrs[elt.ctr].sum_max;
 	    }
 	  for (ix = 0; ix != n_counts; ix++)
 	    entry->counts[ix] += gcov_read_counter ();
@@ -350,13 +335,12 @@  get_coverage_counts (unsigned counter, u
   elt.ident = current_function_funcdef_no + 1;
   elt.ctr = counter;
   entry = (counts_entry_t *) htab_find (counts_hash, &elt);
-  if (!entry)
-    {
-      warning (0, "no coverage for function %qE found",
-	       DECL_ASSEMBLER_NAME (current_function_decl));
-      return NULL;
-    }
-
+  if (!entry || !entry->summary.num)
+    /* The function was not emitted, or is weak and not chosen in the
+       final executable.  Silently fail, because there's nothing we
+       can do about it.  */
+    return NULL;
+  
   if (entry->cfg_checksum != cfg_checksum
       || entry->summary.num != expected)
     {
@@ -366,11 +350,11 @@  get_coverage_counts (unsigned counter, u
 
       warning_printed =
 	warning_at (input_location, OPT_Wcoverage_mismatch,
-		    "The control flow of function %qE does not match "
+		    "the control flow of function %qE does not match "
 		    "its profile data (counter %qs)", id, ctr_names[counter]);
       if (warning_printed)
 	{
-	 inform (input_location, "Use -Wno-error=coverage-mismatch to tolerate "
+	 inform (input_location, "use -Wno-error=coverage-mismatch to tolerate "
 	 	 "the mismatch but performance may drop if the function is hot");
 	  
 	  if (!seen_error ()
@@ -388,12 +372,12 @@  get_coverage_counts (unsigned counter, u
 
       return NULL;
     }
-    else if (entry->lineno_checksum != lineno_checksum)
-      {
-        warning (0, "Source location for function %qE have changed,"
-                 " the profile data may be out of date",
-                 DECL_ASSEMBLER_NAME (current_function_decl));
-      }
+  else if (entry->lineno_checksum != lineno_checksum)
+    {
+      warning (0, "source location for function %qE have changed,"
+	       " the profile data may be out of date",
+	       DECL_ASSEMBLER_NAME (current_function_decl));
+    }
 
   if (summary)
     *summary = &entry->summary;
@@ -413,28 +397,17 @@  coverage_counter_alloc (unsigned counter
   if (!num)
     return 1;
 
-  if (!tree_ctr_tables[counter])
+  if (!fn_v_ctrs[counter])
     {
-      /* Generate and save a copy of this so it can be shared.  Leave
-	 the index type unspecified for now; it will be set after all
-	 functions have been compiled.  */
-      char buf[20];
-      tree gcov_type_node = get_gcov_type ();
-      tree gcov_type_array_type
-        = build_array_type (gcov_type_node, NULL_TREE);
-      tree_ctr_tables[counter]
-        = build_decl (BUILTINS_LOCATION,
-		      VAR_DECL, NULL_TREE, gcov_type_array_type);
-      TREE_STATIC (tree_ctr_tables[counter]) = 1;
-      ASM_GENERATE_INTERNAL_LABEL (buf, "LPBX", counter + 1);
-      DECL_NAME (tree_ctr_tables[counter]) = get_identifier (buf);
-      DECL_ALIGN (tree_ctr_tables[counter]) = TYPE_ALIGN (gcov_type_node);
+      tree array_type = build_array_type (get_gcov_type (), NULL_TREE);
 
-      if (dump_file)
-	fprintf (dump_file, "Using data file %s\n", da_file_name);
+      fn_v_ctrs[counter]
+	= build_var (current_function_decl, array_type, counter);
     }
+
   fn_b_ctrs[counter] = fn_n_ctrs[counter];
   fn_n_ctrs[counter] += num;
+  
   fn_ctr_mask |= 1 << counter;
   return 1;
 }
@@ -447,10 +420,11 @@  tree_coverage_counter_ref (unsigned coun
   tree gcov_type_node = get_gcov_type ();
 
   gcc_assert (no < fn_n_ctrs[counter] - fn_b_ctrs[counter]);
-  no += prg_n_ctrs[counter] + fn_b_ctrs[counter];
 
+  no += fn_b_ctrs[counter];
+  
   /* "no" here is an array index, scaled to bytes later.  */
-  return build4 (ARRAY_REF, gcov_type_node, tree_ctr_tables[counter],
+  return build4 (ARRAY_REF, gcov_type_node, fn_v_ctrs[counter],
 		 build_int_cst (integer_type_node, no), NULL, NULL);
 }
 
@@ -462,13 +436,11 @@  tree_coverage_counter_addr (unsigned cou
   tree gcov_type_node = get_gcov_type ();
 
   gcc_assert (no < fn_n_ctrs[counter] - fn_b_ctrs[counter]);
-  no += prg_n_ctrs[counter] + fn_b_ctrs[counter];
-
-  TREE_ADDRESSABLE (tree_ctr_tables[counter]) = 1;
+  no += fn_b_ctrs[counter];
 
   /* "no" here is an array index, scaled to bytes later.  */
   return build_fold_addr_expr (build4 (ARRAY_REF, gcov_type_node,
-				       tree_ctr_tables[counter],
+				       fn_v_ctrs[counter],
 				       build_int_cst (integer_type_node, no),
 				       NULL, NULL));
 }
@@ -647,78 +619,159 @@  coverage_end_function (unsigned lineno_c
     {
       struct function_list *item;
 
-      item = XCNEW (struct function_list);
-
-      *functions_tail = item;
-      functions_tail = &item->next;
-
+      item = ggc_alloc_function_list ();
 
       item->next = 0;
       item->ident = current_function_funcdef_no + 1;
       item->lineno_checksum = lineno_checksum;
       item->cfg_checksum = cfg_checksum;
+      item->fn_decl = current_function_decl;
       for (i = 0; i != GCOV_COUNTERS; i++)
 	{
-	  item->n_ctrs[i] = fn_n_ctrs[i];
-	  prg_n_ctrs[i] += fn_n_ctrs[i];
-	  fn_n_ctrs[i] = fn_b_ctrs[i] = 0;
+	  tree var = fn_v_ctrs[i];
+	  
+	  item->ctr_vars[i] = var;
+	  if (var)
+	    {
+	      tree array_type = build_index_type (size_int (fn_n_ctrs[i] - 1));
+	      array_type = build_array_type (get_gcov_type (), array_type);
+	      TREE_TYPE (var) = array_type;
+	      DECL_SIZE (var) = TYPE_SIZE (array_type);
+	      DECL_SIZE_UNIT (var) = TYPE_SIZE_UNIT (array_type);
+	      varpool_finalize_decl (var);
+	    }
+	  fn_b_ctrs[i] = fn_n_ctrs[i] = 0;
+	  fn_v_ctrs[i] = NULL_TREE;
 	}
       prg_ctr_mask |= fn_ctr_mask;
       fn_ctr_mask = 0;
+      /* If the function is extern (i.e. extern inline), then we won't
+	 be outputting it, so don't chain it onto the function list.  */
+      if (!DECL_EXTERNAL (item->fn_decl))
+	{
+	  *functions_tail = item;
+	  functions_tail = &item->next;
+	}
     }
   bbg_function_announced = 0;
 }
 
-/* Creates the gcov_fn_info RECORD_TYPE.  */
+/* Build a coverage variable of TYPE for function FN_DECL.  If COUNTER
+   >= 0 it is a counter array, and thus local.  Otherwise it is the
+   function structure and needs to be globalized.  All cases must be
+   in the same comdat group as FN_DECL.  */
 
 static tree
-build_fn_info_type (unsigned int counters)
+build_var (tree fn_decl, tree type, int counter)
+{
+  tree var = build_decl (BUILTINS_LOCATION, VAR_DECL, NULL_TREE, type);
+  tree fn_name = DECL_ASSEMBLER_NAME (fn_decl);
+  char *buf = (char *)alloca (IDENTIFIER_LENGTH (fn_name) + 10);
+
+  if (counter >= 0)
+    TREE_STATIC (var) = 1;
+  else
+    {
+      TREE_PUBLIC (var) = TREE_PUBLIC (fn_decl);
+      TREE_STATIC (var) = TREE_STATIC (fn_decl);
+    }
+  TREE_ADDRESSABLE (var) = 1;
+  DECL_ALIGN (var) = TYPE_ALIGN (type);
+
+  if (counter < 0)
+    sprintf (buf, "__gcov__%s", IDENTIFIER_POINTER (fn_name));
+  else
+    sprintf (buf, "__gcov%u_%s", counter, IDENTIFIER_POINTER (fn_name));
+  DECL_NAME (var) = get_identifier (buf);
+
+  /* Initialize assembler name so we can stream out. */
+  if (TREE_PUBLIC (var))
+    DECL_ASSEMBLER_NAME (var);    
+
+  DECL_WEAK (var) = TREE_PUBLIC (var) && DECL_WEAK (fn_decl);
+  DECL_COMDAT (var) = DECL_COMDAT (fn_decl);
+  DECL_COMDAT_GROUP (var) = DECL_COMDAT_GROUP (fn_decl);
+
+  return var;
+}
+
+/* Creates the gcov_fn_info RECORD_TYPE.  */
+
+static void
+build_fn_info_type (tree type, unsigned counters, tree gcov_info_type)
 {
-  tree type = lang_hooks.types.make_type (RECORD_TYPE);
+  tree ctr_info = lang_hooks.types.make_type (RECORD_TYPE);
   tree field, fields;
   tree array_type;
 
+  gcc_assert (counters);
+  
+  /* ctr_info::num */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
+  fields = field;
+  
+  /* ctr_info::values */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      build_pointer_type (get_gcov_type ()));
+  DECL_CHAIN (field) = fields;
+  fields = field;
+  
+  finish_builtin_struct (ctr_info, "__gcov_ctr_info", fields, NULL_TREE);
+
+  /* key */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      build_pointer_type (build_qualified_type
+					  (gcov_info_type, TYPE_QUAL_CONST)));
+  fields = field;
+  
   /* ident */
-  fields = build_decl (BUILTINS_LOCATION,
-		       FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
+  DECL_CHAIN (field) = fields;
+  fields = field;
+  
   /* lineno_checksum */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
   DECL_CHAIN (field) = fields;
   fields = field;
 
   /* cfg checksum */
-  field = build_decl (BUILTINS_LOCATION,
-                      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
   DECL_CHAIN (field) = fields;
   fields = field;
 
   array_type = build_index_type (size_int (counters - 1));
-  array_type = build_array_type (get_gcov_unsigned_t (), array_type);
+  array_type = build_array_type (ctr_info, array_type);
 
   /* counters */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, array_type);
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE, array_type);
   DECL_CHAIN (field) = fields;
   fields = field;
 
   finish_builtin_struct (type, "__gcov_fn_info", fields, NULL_TREE);
-
-  return type;
 }
 
 /* Creates a CONSTRUCTOR for a gcov_fn_info. FUNCTION is
    the function being processed and TYPE is the gcov_fn_info
-   RECORD_TYPE.  */
+   RECORD_TYPE.  KEY is the object file key. */
 
 static tree
-build_fn_info_value (const struct function_list *function, tree type)
+build_fn_info (const struct function_list *function, tree type, tree key)
 {
   tree fields = TYPE_FIELDS (type);
+  tree ctr_type;
   unsigned ix;
   VEC(constructor_elt,gc) *v1 = NULL;
   VEC(constructor_elt,gc) *v2 = NULL;
 
+  /* key */
+  CONSTRUCTOR_APPEND_ELT (v1, fields,
+			  build1 (ADDR_EXPR, TREE_TYPE (fields), key));
+  fields = DECL_CHAIN (fields);
+  
   /* ident */
   CONSTRUCTOR_APPEND_ELT (v1, fields,
 			  build_int_cstu (get_gcov_unsigned_t (),
@@ -738,240 +791,194 @@  build_fn_info_value (const struct functi
   fields = DECL_CHAIN (fields);
 
   /* counters */
+  ctr_type = TREE_TYPE (TREE_TYPE (fields));
   for (ix = 0; ix != GCOV_COUNTERS; ix++)
     if (prg_ctr_mask & (1 << ix))
-      CONSTRUCTOR_APPEND_ELT (v2, NULL,
-			      build_int_cstu (get_gcov_unsigned_t (),
-					      function->n_ctrs[ix]));
-
+      {
+	VEC(constructor_elt,gc) *ctr = NULL;
+	tree var = function->ctr_vars[ix];
+	unsigned count = 0;
+
+	if (var)
+	  count
+	    = tree_low_cst (TYPE_MAX_VALUE (TYPE_DOMAIN (TREE_TYPE (var))), 0)
+	    + 1;
+
+	CONSTRUCTOR_APPEND_ELT (ctr, TYPE_FIELDS (ctr_type),
+				build_int_cstu (get_gcov_unsigned_t (),
+						count));
+
+	if (var)
+	  CONSTRUCTOR_APPEND_ELT (ctr, DECL_CHAIN (TYPE_FIELDS (ctr_type)),
+				  build_fold_addr_expr (var));
+	
+	CONSTRUCTOR_APPEND_ELT (v2, NULL, build_constructor (ctr_type, ctr));
+      }
+  
   CONSTRUCTOR_APPEND_ELT (v1, fields,
 			  build_constructor (TREE_TYPE (fields), v2));
 
   return build_constructor (type, v1);
 }
 
-/* Creates the gcov_ctr_info RECORD_TYPE.  */
+/* Creaste gcov_info_struct.  N_FUNCS is the number of functions in
+   the trailing array.  */
 
-static tree
-build_ctr_info_type (void)
+static void
+build_info_type (tree type, unsigned n_funcs, tree fn_info_type)
 {
-  tree type = lang_hooks.types.make_type (RECORD_TYPE);
   tree field, fields = NULL_TREE;
-  tree gcov_ptr_type = build_pointer_type (get_gcov_type ());
-  tree gcov_merge_fn_type;
+  tree merge_fn_type, fn_info_array;
 
-  /* counters */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
+  gcc_assert (n_funcs);
+  
+  /* Version ident */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
   DECL_CHAIN (field) = fields;
   fields = field;
 
-  /* values */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, gcov_ptr_type);
+  /* next pointer */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      build_pointer_type (build_qualified_type
+					  (type, TYPE_QUAL_CONST)));
   DECL_CHAIN (field) = fields;
   fields = field;
 
-  /* merge */
-  gcov_merge_fn_type =
-    build_function_type_list (void_type_node,
-			      gcov_ptr_type, get_gcov_unsigned_t (),
-			      NULL_TREE);
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE,
-		      build_pointer_type (gcov_merge_fn_type));
+  /* stamp */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
   DECL_CHAIN (field) = fields;
   fields = field;
 
-  finish_builtin_struct (type, "__gcov_ctr_info", fields, NULL_TREE);
-
-  return type;
-}
-
-/* Creates a CONSTRUCTOR for a gcov_ctr_info. COUNTER is
-   the counter being processed and TYPE is the gcov_ctr_info
-   RECORD_TYPE.  */
-
-static tree
-build_ctr_info_value (unsigned int counter, tree type)
-{
-  tree fields = TYPE_FIELDS (type);
-  tree fn;
-  VEC(constructor_elt,gc) *v = NULL;
-
-  /* counters */
-  CONSTRUCTOR_APPEND_ELT (v, fields,
-			  build_int_cstu (get_gcov_unsigned_t (),
-					  prg_n_ctrs[counter]));
-  fields = DECL_CHAIN (fields);
-
-  if (prg_n_ctrs[counter])
-    {
-      tree array_type;
-
-      array_type = build_int_cstu (get_gcov_unsigned_t (),
-				   prg_n_ctrs[counter] - 1);
-      array_type = build_index_type (array_type);
-      array_type = build_array_type (TREE_TYPE (TREE_TYPE (fields)),
-				     array_type);
-
-      TREE_TYPE (tree_ctr_tables[counter]) = array_type;
-      DECL_SIZE (tree_ctr_tables[counter]) = TYPE_SIZE (array_type);
-      DECL_SIZE_UNIT (tree_ctr_tables[counter]) = TYPE_SIZE_UNIT (array_type);
-      varpool_finalize_decl (tree_ctr_tables[counter]);
-
-      CONSTRUCTOR_APPEND_ELT (v, fields,
-			      build1 (ADDR_EXPR, TREE_TYPE (fields),
-				      tree_ctr_tables[counter]));
-    }
-  else
-    CONSTRUCTOR_APPEND_ELT (v, fields, null_pointer_node);
-  fields = DECL_CHAIN (fields);
+  /* Filename */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      build_pointer_type (build_qualified_type
+					  (char_type_node, TYPE_QUAL_CONST)));
+  DECL_CHAIN (field) = fields;
+  fields = field;
 
-  fn = build_decl (BUILTINS_LOCATION,
-		   FUNCTION_DECL,
-		   get_identifier (ctr_merge_functions[counter]),
-		   TREE_TYPE (TREE_TYPE (fields)));
-  DECL_EXTERNAL (fn) = 1;
-  TREE_PUBLIC (fn) = 1;
-  DECL_ARTIFICIAL (fn) = 1;
-  TREE_NOTHROW (fn) = 1;
-  DECL_ASSEMBLER_NAME (fn);  /* Initialize assembler name so we can stream out. */
-  CONSTRUCTOR_APPEND_ELT (v, fields, build1 (ADDR_EXPR, TREE_TYPE (fields), fn));
+  /* merge fn array */
+  merge_fn_type
+    = build_function_type_list (void_type_node,
+				build_pointer_type (get_gcov_type ()),
+				get_gcov_unsigned_t (), NULL_TREE);
+  merge_fn_type
+    = build_array_type (build_pointer_type (merge_fn_type),
+			build_index_type (size_int (GCOV_COUNTERS - 1)));
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      merge_fn_type);
+  DECL_CHAIN (field) = fields;
+  fields = field;
+  
+  /* n_functions */
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      get_gcov_unsigned_t ());
+  DECL_CHAIN (field) = fields;
+  fields = field;
+  
+  /* function_info pointer array */
+  fn_info_type = build_pointer_type
+    (build_qualified_type (fn_info_type, TYPE_QUAL_CONST));
+  fn_info_array = build_index_type (size_int (n_funcs));
+  fn_info_array = build_array_type (fn_info_type, fn_info_array);
+  field = build_decl (BUILTINS_LOCATION, FIELD_DECL, NULL_TREE,
+		      fn_info_array);
+  DECL_CHAIN (field) = fields;
+  fields = field;
 
-  return build_constructor (type, v);
+  finish_builtin_struct (type, "__gcov_info", fields, NULL_TREE);
 }
 
-/* Creates the gcov_info RECORD_TYPE and initializer for it. Returns a
-   CONSTRUCTOR.  */
+/* Creates the gcov_info initializer. Returns a CONSTRUCTOR.  */
 
 static tree
-build_gcov_info (void)
+build_info (tree info_type, tree fn_type, tree key_var, unsigned n_funcs)
 {
-  unsigned n_ctr_types, ix;
-  tree type, const_type;
-  tree fn_info_type, fn_info_value = NULL_TREE;
-  tree fn_info_ptr_type;
-  tree ctr_info_type, ctr_info_ary_type, ctr_info_value = NULL_TREE;
-  tree field, fields = NULL_TREE;
+  tree info_fields = TYPE_FIELDS (info_type);
+  tree merge_fn_type, fn_info_ptr_type;
+  unsigned ix;
   tree filename_string;
   int da_file_name_len;
-  unsigned n_fns;
   const struct function_list *fn;
-  tree string_type;
   VEC(constructor_elt,gc) *v1 = NULL;
   VEC(constructor_elt,gc) *v2 = NULL;
-
-  /* Count the number of active counters.  */
-  for (n_ctr_types = 0, ix = 0; ix != GCOV_COUNTERS; ix++)
-    if (prg_ctr_mask & (1 << ix))
-      n_ctr_types++;
-
-  type = lang_hooks.types.make_type (RECORD_TYPE);
-  const_type = build_qualified_type (type, TYPE_QUAL_CONST);
+  VEC(constructor_elt,gc) *v3 = NULL;
 
   /* Version ident */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field,
-			  build_int_cstu (TREE_TYPE (field), GCOV_VERSION));
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields,
+			  build_int_cstu (TREE_TYPE (info_fields),
+					  GCOV_VERSION));
+  info_fields = DECL_CHAIN (info_fields);
 
   /* next -- NULL */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, build_pointer_type (const_type));
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field, null_pointer_node);
-
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields, null_pointer_node);
+  info_fields = DECL_CHAIN (info_fields);
+  
   /* stamp */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field,
-			  build_int_cstu (TREE_TYPE (field), local_tick));
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields,
+			  build_int_cstu (TREE_TYPE (info_fields),
+					  local_tick));
+  info_fields = DECL_CHAIN (info_fields);
 
   /* Filename */
-  string_type = build_pointer_type (build_qualified_type (char_type_node,
-						    TYPE_QUAL_CONST));
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, string_type);
-  DECL_CHAIN (field) = fields;
-  fields = field;
   da_file_name_len = strlen (da_file_name);
   filename_string = build_string (da_file_name_len + 1, da_file_name);
   TREE_TYPE (filename_string) = build_array_type
     (char_type_node, build_index_type (size_int (da_file_name_len)));
-  CONSTRUCTOR_APPEND_ELT (v1, field,
-			  build1 (ADDR_EXPR, string_type, filename_string));
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields,
+			  build1 (ADDR_EXPR, TREE_TYPE (info_fields),
+				  filename_string));
+  info_fields = DECL_CHAIN (info_fields);
 
-  /* Build the fn_info type and initializer.  */
-  fn_info_type = build_fn_info_type (n_ctr_types);
-  fn_info_ptr_type = build_pointer_type (build_qualified_type
-					 (fn_info_type, TYPE_QUAL_CONST));
-  for (fn = functions_head, n_fns = 0; fn; fn = fn->next, n_fns++)
-    CONSTRUCTOR_APPEND_ELT (v2, NULL_TREE,
-			    build_fn_info_value (fn, fn_info_type));
-
-  if (n_fns)
+  /* merge fn array -- NULL slots indicate unmeasured counters */
+  merge_fn_type = TREE_TYPE (TREE_TYPE (info_fields));
+  for (ix = 0; ix != GCOV_COUNTERS; ix++)
     {
-      tree array_type;
-
-      array_type = build_index_type (size_int (n_fns - 1));
-      array_type = build_array_type (fn_info_type, array_type);
+      tree ptr = null_pointer_node;
 
-      fn_info_value = build_constructor (array_type, v2);
-      fn_info_value = build1 (ADDR_EXPR, fn_info_ptr_type, fn_info_value);
+      if ((1u << ix) & prg_ctr_mask)
+	{
+	  tree merge_fn = build_decl (BUILTINS_LOCATION,
+				      FUNCTION_DECL,
+				      get_identifier (ctr_merge_functions[ix]),
+				      TREE_TYPE (merge_fn_type));
+	  DECL_EXTERNAL (merge_fn) = 1;
+	  TREE_PUBLIC (merge_fn) = 1;
+	  DECL_ARTIFICIAL (merge_fn) = 1;
+	  TREE_NOTHROW (merge_fn) = 1;
+	  /* Initialize assembler name so we can stream out. */
+	  DECL_ASSEMBLER_NAME (merge_fn);
+	  ptr = build1 (ADDR_EXPR, merge_fn_type, merge_fn);
+	}
+      CONSTRUCTOR_APPEND_ELT (v2, NULL, ptr);
     }
-  else
-    fn_info_value = null_pointer_node;
-
-  /* number of functions */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field,
-			  build_int_cstu (get_gcov_unsigned_t (), n_fns));
-
-  /* fn_info table */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, fn_info_ptr_type);
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field, fn_info_value);
-
-  /* counter_mask */
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, get_gcov_unsigned_t ());
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field, 
-			  build_int_cstu (get_gcov_unsigned_t (),
-					  prg_ctr_mask));
-
-  /* counters */
-  ctr_info_type = build_ctr_info_type ();
-  ctr_info_ary_type = build_index_type (size_int (n_ctr_types));
-  ctr_info_ary_type = build_array_type (ctr_info_type, ctr_info_ary_type);
-  v2 = NULL;
-  for (ix = 0; ix != GCOV_COUNTERS; ix++)
-    if (prg_ctr_mask & (1 << ix))
-      CONSTRUCTOR_APPEND_ELT (v2, NULL_TREE,
-			      build_ctr_info_value (ix, ctr_info_type));
-  ctr_info_value = build_constructor (ctr_info_ary_type, v2);
-
-  field = build_decl (BUILTINS_LOCATION,
-		      FIELD_DECL, NULL_TREE, ctr_info_ary_type);
-  DECL_CHAIN (field) = fields;
-  fields = field;
-  CONSTRUCTOR_APPEND_ELT (v1, field, ctr_info_value);
-
-  finish_builtin_struct (type, "__gcov_info", fields, NULL_TREE);
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields,
+			  build_constructor (TREE_TYPE (info_fields), v2));
+  info_fields = DECL_CHAIN (info_fields);
+
+  /* n_functions */
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields,
+			  build_int_cstu (TREE_TYPE (info_fields), n_funcs));
+  info_fields = DECL_CHAIN (info_fields);
+  
+  /* Build the fn_info type and initializer.  */
+  fn_info_ptr_type = TREE_TYPE (TREE_TYPE (info_fields));
+  
+  for (fn = functions_head; fn; fn = fn->next)
+    {
+      tree init = build_fn_info (fn, fn_type, key_var);
+      tree var = build_var (fn->fn_decl, fn_type, -1);
 
-  return build_constructor (type, v1);
+      DECL_INITIAL (var) = init;
+      varpool_finalize_decl (var);
+      
+      CONSTRUCTOR_APPEND_ELT (v3, NULL,
+			      build1 (ADDR_EXPR, fn_info_ptr_type, var));
+    }
+  CONSTRUCTOR_APPEND_ELT (v1, info_fields,
+			  build_constructor (TREE_TYPE (info_fields), v3));
+  return build_constructor (info_type, v1);
 }
 
 /* Write out the structure which libgcov uses to locate all the
@@ -982,6 +989,11 @@  static void
 create_coverage (void)
 {
   tree gcov_info, gcov_init, body, t;
+  tree gcov_info_type, gcov_fn_type;
+  unsigned n_counters = 0, n_functions  = 0;
+  struct function_list *fn;
+  struct function_list **fn_prev;
+  unsigned ix;
   char name_buf[32];
 
   no_coverage = 1; /* Disable any further coverage.  */
@@ -989,14 +1001,37 @@  create_coverage (void)
   if (!prg_ctr_mask)
     return;
 
-  t = build_gcov_info ();
+  if (cgraph_dump_file)
+    fprintf (cgraph_dump_file, "Using data file %s\n", da_file_name);
 
+  for (ix = 0; ix != GCOV_COUNTERS; ix++)
+    if ((1u << ix) & prg_ctr_mask)
+      n_counters++;
+  for (fn_prev = &functions_head; (fn = *fn_prev);)
+    if (DECL_STRUCT_FUNCTION (fn->fn_decl))
+      {
+	n_functions++;
+	fn_prev = &fn->next;
+      }
+    else
+      /* The function is not being emitted, remove from list.  */
+      *fn_prev = fn->next;
+  
+  /* Build the info and fn_info types.  These are mutually recursive.  */
+  gcov_info_type = lang_hooks.types.make_type (RECORD_TYPE);
+  gcov_fn_type = lang_hooks.types.make_type (RECORD_TYPE);
+  build_fn_info_type (gcov_fn_type, n_counters, gcov_info_type);
+  build_info_type (gcov_info_type, n_functions, gcov_fn_type);
+  
+  /* Build the gcov info var, this is referred to in its own
+     initializer.  */
   gcov_info = build_decl (BUILTINS_LOCATION,
-			  VAR_DECL, NULL_TREE, TREE_TYPE (t));
+			  VAR_DECL, NULL_TREE, gcov_info_type);
   TREE_STATIC (gcov_info) = 1;
   ASM_GENERATE_INTERNAL_LABEL (name_buf, "LPBX", 0);
   DECL_NAME (gcov_info) = get_identifier (name_buf);
-  DECL_INITIAL (gcov_info) = t;
+  DECL_INITIAL (gcov_info) = build_info (gcov_info_type, gcov_fn_type,
+					 gcov_info, n_functions);
 
   /* Build structure.  */
   varpool_finalize_decl (gcov_info);
Index: gcc/gcov-dump.c
===================================================================
--- gcc/gcov-dump.c	(revision 180739)
+++ gcc/gcov-dump.c	(working copy)
@@ -276,23 +276,28 @@  dump_file (const char *filename)
 
 static void
 tag_function (const char *filename ATTRIBUTE_UNUSED,
-	      unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+	      unsigned tag ATTRIBUTE_UNUSED, unsigned length)
 {
   unsigned long pos = gcov_position ();
 
-  printf (" ident=%u", gcov_read_unsigned ());
-  printf (", lineno_checksum=0x%08x", gcov_read_unsigned ());
-  printf (", cfg_checksum_checksum=0x%08x", gcov_read_unsigned ());
-
-  if (gcov_position () - pos < length)
+  if (!length)
+    printf (" placeholder");
+  else
     {
-      const char *name;
+      printf (" ident=%u", gcov_read_unsigned ());
+      printf (", lineno_checksum=0x%08x", gcov_read_unsigned ());
+      printf (", cfg_checksum_checksum=0x%08x", gcov_read_unsigned ());
 
-      name = gcov_read_string ();
-      printf (", `%s'", name ? name : "NULL");
-      name = gcov_read_string ();
-      printf (" %s", name ? name : "NULL");
-      printf (":%u", gcov_read_unsigned ());
+      if (gcov_position () - pos < length)
+	{
+	  const char *name;
+	  
+	  name = gcov_read_string ();
+	  printf (", `%s'", name ? name : "NULL");
+	  name = gcov_read_string ();
+	  printf (" %s", name ? name : "NULL");
+	  printf (":%u", gcov_read_unsigned ());
+	}
     }
 }