[debug-early] C++ clones and limbo DIEs
diff mbox

Message ID 54CBEB69.3000401@redhat.com
State New
Headers show

Commit Message

Aldy Hernandez Jan. 30, 2015, 8:36 p.m. UTC
On 01/28/2015 10:51 AM, Jason Merrill wrote:
> On 01/28/2015 01:29 PM, Aldy Hernandez wrote:
>> +  /* It is rather unfortunate that Cilk creates trees this late +
>> (during gimplification).  However, until this gets fixed, +
>> specially handle emitting DWARF for this new function and +
>> immediately clean up the limbo_die_list where the new function's +
>> DIE will inevitably end up.  */
>
> Why does it go on limbo_die_list at all?

Good point.  They are ending up in limbo because, during late dwarf
generation, dwarf2out_decl is clearing its context_die, as they are
nested functions:

       /* If we're a nested function, initially use a parent of NULL; if
we're
	 a plain function, this will be fixed up in decls_for_scope.  If
	 we're a method, it will be ignored, since we already have a DIE.  */
       if (decl_function_context (decl)
	  /* But if we're in terse mode, we don't care about scope.  */
	  && debug_info_level > DINFO_LEVEL_TERSE)
	context_die = NULL;

We could avoid special casing Cilk, and just allow late limbo DIEs of
nested functions, since decls_for_scope will set their ancestry
correctly shortly afterwards (that is, they don't remain in limbo long-- 
or into LTO).  I have verified this to be true with the
  patch.

>
>> I noticed dwarf2out's gen_member_die() disallows generation of
>> clones earlier, by design:
>>
>> /* Don't include clones in the member list.  */ if
>> (DECL_ABSTRACT_ORIGIN (member)) continue;
>>
> I'd still like to understand why disabling this doesn't work; I don't
>  like the special handling of clones in the front end.

The attached patch disables this, and fixes the fallout.  Instead of 
explaining what required tweaking, the `#if 0'ed out code with 
appropriate comments explains the relevant changes.  Do you agree?

Obviously, now we will get more DIEs than before (complete constructors, 
base constructors, and what have yous).  Whereas previously we only 
generated a DIE for the used ones.  We can optimize this later.

I have added some debugging code to help catch late limbo DIE creation 
as well as a few things to help the process.  We can remove them at 
merge time.

I special cased allowing late limbo type DIES that occur for template 
interactions.  No sense complicating my life with templates until we 
iron out the clones.  I will take a look at templates next.

I also got rid of my decl_abstract change to 
dwarf2out_abstract_function() (the static variable plus LTO issue) 
because it's not being triggered anymore with the attached patch.

Would you be so kind as to look at this approach?  It has no guality 
regressions, and the C++ clones seem to have correct DIEs.  I can remove 
the #if 0 code once we're in agreement.

Thanks.
Aldy
* cp/decl2.c (emit_debug_for_namespace): Add FIXME note for
	templates.
	* dbxout.c (dbx_debug_hooks): Add early_finish field.
	* sdbout.c (sdb_debug_hooks): Same.
	* vmsdbgout.c (vmsdbg_debug_hooks): Same.
	* debug.h (gcc_debug_hooks): Same.
	* dwarf2out.c (dwarf2_debug_hooks): Same.
	(new_die): Inhibit limbo dies unless generating early dwarf.
	Set tmp_created_for field.
	(gen_subprogram_die): Do not create a new DIE if we have an
	old DIE and it has DW_AT_abstract_origin set.
	Do not reset origin.
	Remove declaration assert.
	(gen_member_die): Allow DIE clones to be generated.
	(struct die_struct): Add tmp_created_for field.
	(lookup_filename): Return NULL when no file name.
	(optimize_location_lists): Abstract flushing of limbo list to...
	(dwarf2out_early_finish): ...here. New function.
	(print_die): Output tmp_created_for field.
	* toplev.c (toplev::main): Add temporary debugging aid.
	* tree.c (free_lang_data): Call debug_hooks->early_finish.

Comments

Jason Merrill Jan. 30, 2015, 10:04 p.m. UTC | #1
On 01/30/2015 03:36 PM, Aldy Hernandez wrote:
>   /* It is possible to have both DECL_ABSTRACT_P and DECLARATION be true if we
>      started to generate the abstract instance of an inline, decided to output
>      its containing class, and proceeded to emit the declaration of the inline
>      from the member list for the class.  If so, DECLARATION takes priority;
>      we'll get back to the abstract instance when done with the class.  */

This comment is out of date; in this case decl_ultimate_origin will 
return NULL_TREE, so origin is null, so we shouldn't need to deal with 
this here.

> +  /* ?? We must not reset `origin', so C++ clones get a proper
> +     DW_AT_abstract_origin tagged DIE further on.  */
> +#if 0
>    /* The class-scope declaration DIE must be the primary DIE.  */
>    if (origin && declaration && class_or_namespace_scope_p (context_die))
>      {
>        origin = NULL;
>        gcc_assert (!old_die);
>      }
> +#endif

So I think this block is unnecessary.

> Obviously, now we will get more DIEs than before (complete constructors, base constructors, and what have yous).  Whereas previously we only generated a DIE for the used ones.

Hmm, that's unfortunate.

What if we leave the clone skipping alone here and emit early debug 
about all reachable functions in 
symbol_table::finalize_compilation_unit, between analyze_functions() and 
compile()?

Jason
Richard Biener Feb. 1, 2015, 8:42 a.m. UTC | #2
On January 30, 2015 11:04:23 PM CET, Jason Merrill <jason@redhat.com> wrote:
>On 01/30/2015 03:36 PM, Aldy Hernandez wrote:
>>   /* It is possible to have both DECL_ABSTRACT_P and DECLARATION be
>true if we
>>      started to generate the abstract instance of an inline, decided
>to output
>>      its containing class, and proceeded to emit the declaration of
>the inline
>>      from the member list for the class.  If so, DECLARATION takes
>priority;
>>      we'll get back to the abstract instance when done with the
>class.  */
>
>This comment is out of date; in this case decl_ultimate_origin will 
>return NULL_TREE, so origin is null, so we shouldn't need to deal with 
>this here.
>
>> +  /* ?? We must not reset `origin', so C++ clones get a proper
>> +     DW_AT_abstract_origin tagged DIE further on.  */
>> +#if 0
>>    /* The class-scope declaration DIE must be the primary DIE.  */
>>    if (origin && declaration && class_or_namespace_scope_p
>(context_die))
>>      {
>>        origin = NULL;
>>        gcc_assert (!old_die);
>>      }
>> +#endif
>
>So I think this block is unnecessary.
>
>> Obviously, now we will get more DIEs than before (complete
>constructors, base constructors, and what have yous).  Whereas
>previously we only generated a DIE for the used ones.
>
>Hmm, that's unfortunate.
>
>What if we leave the clone skipping alone here and emit early debug 
>about all reachable functions in 
>symbol_table::finalize_compilation_unit, between analyze_functions()
>and 
>compile()?

I always thought a user expects debug information for all entities in the source, not only those useb by the (optimized) binary.

Richard.

>Jason
Jason Merrill Feb. 2, 2015, 1:47 a.m. UTC | #3
On 02/01/2015 03:42 AM, Richard Biener wrote:
> I always thought a user expects debug information for all entities in the source, not only those useb by the (optimized) binary.

Right, but the clones aren't in the source individually; the primary 
constructor that they are cloned from should always be in the debug 
info, but we don't need to emit anything for the clones unless they have 
a concrete instance.

Jason

Patch
diff mbox

diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c
index 691688b..70abc99 100644
--- a/gcc/cp/decl2.c
+++ b/gcc/cp/decl2.c
@@ -4346,6 +4346,9 @@  emit_debug_for_namespace (tree name_space, void* data ATTRIBUTE_UNUSED)
 
   check_global_declarations (vec, len);
 
+  /* FIXME: What does this do for templates?  I think we don't want to
+     send a template off to early_global_decl, but rather walk through
+     its specializations and emit them.  */
   for (tree t = level->names; t; t = TREE_CHAIN(t))
     debug_hooks->early_global_decl (t);
 
diff --git a/gcc/dbxout.c b/gcc/dbxout.c
index 430a2eb..202ef8a 100644
--- a/gcc/dbxout.c
+++ b/gcc/dbxout.c
@@ -359,6 +359,7 @@  const struct gcc_debug_hooks dbx_debug_hooks =
   dbxout_init,
   dbxout_finish,
   debug_nothing_void,
+  debug_nothing_void,
   debug_nothing_int_charstar,
   debug_nothing_int_charstar,
   dbxout_start_source_file,
@@ -400,6 +401,7 @@  const struct gcc_debug_hooks xcoff_debug_hooks =
   dbxout_init,
   dbxout_finish,
   debug_nothing_void,
+  debug_nothing_void,
   debug_nothing_int_charstar,
   debug_nothing_int_charstar,
   dbxout_start_source_file,
diff --git a/gcc/debug.c b/gcc/debug.c
index 449d3a1..d0e00c0 100644
--- a/gcc/debug.c
+++ b/gcc/debug.c
@@ -27,6 +27,7 @@  const struct gcc_debug_hooks do_nothing_debug_hooks =
 {
   debug_nothing_charstar,
   debug_nothing_charstar,
+  debug_nothing_void,			/* early_finish */
   debug_nothing_void,
   debug_nothing_int_charstar,
   debug_nothing_int_charstar,
diff --git a/gcc/debug.h b/gcc/debug.h
index f9485bc..a8d3f23 100644
--- a/gcc/debug.h
+++ b/gcc/debug.h
@@ -30,6 +30,9 @@  struct gcc_debug_hooks
   /* Output debug symbols.  */
   void (* finish) (const char *main_filename);
 
+  /* Run cleanups necessary after early debug generation.  */
+  void (* early_finish) (void);
+
   /* Called from cgraph_optimize before starting to assemble
      functions/variables/toplevel asms.  */
   void (* assembly_start) (void);
diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
index 33738d9..7e1c000 100644
--- a/gcc/dwarf2out.c
+++ b/gcc/dwarf2out.c
@@ -106,6 +106,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-dfa.h"
 #include "gdb/gdb-index.h"
 #include "rtl-iter.h"
+#include "print-tree.h"
 
 static void dwarf2out_source_line (unsigned int, const char *, int, bool);
 static rtx_insn *last_var_location_insn;
@@ -2424,6 +2425,7 @@  build_cfa_aligned_loc (dw_cfa_location *cfa,
 
 static void dwarf2out_init (const char *);
 static void dwarf2out_finish (const char *);
+static void dwarf2out_early_finish (void);
 static void dwarf2out_assembly_start (void);
 static void dwarf2out_define (unsigned int, const char *);
 static void dwarf2out_undef (unsigned int, const char *);
@@ -2451,6 +2453,7 @@  const struct gcc_debug_hooks dwarf2_debug_hooks =
 {
   dwarf2out_init,
   dwarf2out_finish,
+  dwarf2out_early_finish,
   dwarf2out_assembly_start,
   dwarf2out_define,
   dwarf2out_undef,
@@ -2611,6 +2614,9 @@  typedef struct GTY((chain_circular ("%h.die_sib"), for_user)) die_struct {
   int die_mark;
   unsigned int decl_id;
   enum dwarf_tag die_tag;
+  /* No one should depend on this, as it is a temporary debugging aid
+     to indicate the DECL for which this DIE was created for.  */
+  tree tmp_created_for;
   /* Die is used and must not be pruned as unused.  */
   BOOL_BITFIELD die_perennial_p : 1;
   BOOL_BITFIELD comdat_type_p : 1; /* DIE has a type signature */
@@ -4890,6 +4896,7 @@  new_die (enum dwarf_tag tag_value, dw_die_ref parent_die, tree t)
   dw_die_ref die = ggc_cleared_alloc<die_node> ();
 
   die->die_tag = tag_value;
+  die->tmp_created_for = t;
 
   if (early_dwarf_dumping)
     die->dumped_early = true;
@@ -4900,6 +4907,30 @@  new_die (enum dwarf_tag tag_value, dw_die_ref parent_die, tree t)
     {
       limbo_die_node *limbo_node;
 
+      /* No DIEs created after early dwarf should end up in limbo,
+	 because the limbo list should not persist past LTO
+	 streaming.  */
+      if (tag_value != DW_TAG_compile_unit
+	  && !early_dwarf_dumping
+	  /* Allow nested functions to live in limbo because they will
+	     only temporarily live there, as decls_for_scope will fix
+	     them up.  */
+	  && (TREE_CODE (t) != FUNCTION_DECL
+	      || !decl_function_context (t))
+	  /* FIXME: Allow types for now.  We are getting some internal
+	     template types from inlining (building libstdc++).
+	     Templates need to be looked at.  */
+	  && !TYPE_P (t)
+	  /* FIXME: Allow late limbo DIE creation for LTO, especially
+	     in the ltrans stage, but once we implement LTO dwarf
+	     streaming, we should remove this exception.  */
+	  && !in_lto_p)
+	{
+	  fprintf (stderr, "symbol ended up in limbo too late:");
+	  debug_generic_stmt (t);
+	  gcc_unreachable ();
+	}
+
       limbo_node = ggc_cleared_alloc<limbo_die_node> ();
       limbo_node->die = die;
       limbo_node->created_for = t;
@@ -5399,6 +5430,12 @@  print_die (dw_die_ref die, FILE *outfile)
 	fprintf (outfile, ": %s", name);
       fputc (')', outfile);
     }
+  if (die->tmp_created_for
+      && DECL_P (die->tmp_created_for)
+      && CODE_CONTAINS_STRUCT
+           (TREE_CODE (die->tmp_created_for), TS_DECL_WITH_VIS))
+    fprintf (outfile, "(mangle: %s)",
+	     IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (die->tmp_created_for)));
   fputc ('\n', outfile);
   print_spaces (outfile);
   fprintf (outfile, "  abbrev id: %lu", die->die_abbrev);
@@ -18414,12 +18451,16 @@  gen_subprogram_die (tree decl, dw_die_ref context_die)
      from the member list for the class.  If so, DECLARATION takes priority;
      we'll get back to the abstract instance when done with the class.  */
 
+  /* ?? We must not reset `origin', so C++ clones get a proper
+     DW_AT_abstract_origin tagged DIE further on.  */
+#if 0
   /* The class-scope declaration DIE must be the primary DIE.  */
   if (origin && declaration && class_or_namespace_scope_p (context_die))
     {
       origin = NULL;
       gcc_assert (!old_die);
     }
+#endif
 
   /* Now that the C++ front end lazily declares artificial member fns, we
      might need to retrofit the declaration into its class.  */
@@ -18433,21 +18474,34 @@  gen_subprogram_die (tree decl, dw_die_ref context_die)
   /* An inlined instance, tag a new DIE with DW_AT_abstract_origin.  */
   if (origin != NULL)
     {
+      // ?? Make C++ clones work since they're tagged as
+      // declarations but are class scoped instead.
+#if 0
       gcc_assert (!declaration || local_scope_p (context_die));
+#endif
 
       /* Fixup die_parent for the abstract instance of a nested
 	 inline function.  */
       if (old_die && old_die->die_parent == NULL)
 	add_child_die (context_die, old_die);
 
-      subr_die = new_die (DW_TAG_subprogram, context_die, decl);
-      add_abstract_origin_attribute (subr_die, origin);
-      /*  This is where the actual code for a cloned function is.
-	  Let's emit linkage name attribute for it.  This helps
-	  debuggers to e.g, set breakpoints into
-	  constructors/destructors when the user asks "break
-	  K::K".  */
-      add_linkage_name (subr_die, decl);
+      if (old_die && get_AT_ref (old_die, DW_AT_abstract_origin))
+	{
+	  /* If we have a DW_AT_abstract_origin we have a working
+	     cached version.  */
+	  subr_die = old_die;
+	}
+      else
+	{
+	  subr_die = new_die (DW_TAG_subprogram, context_die, decl);
+	  add_abstract_origin_attribute (subr_die, origin);
+	  /*  This is where the actual code for a cloned function is.
+	      Let's emit linkage name attribute for it.  This helps
+	      debuggers to e.g, set breakpoints into
+	      constructors/destructors when the user asks "break
+	      K::K".  */
+	  add_linkage_name (subr_die, decl);
+	}
     }
   /* A cached copy, possibly from early dwarf generation.  Reuse as
      much as possible.  */
@@ -20125,9 +20179,9 @@  gen_member_die (tree type, dw_die_ref context_die)
   /* Now output info about the function members (if any).  */
   for (member = TYPE_METHODS (type); member; member = DECL_CHAIN (member))
     {
-      /* Don't include clones in the member list.  */
-      if (DECL_ABSTRACT_ORIGIN (member))
-	continue;
+      if (DECL_ABSTRACT_ORIGIN (member) && flag_dump_early_debug_stats)
+	fprintf(stderr, "generating dwarf for member clone: %s\n",
+		IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (member)));
 
       child = lookup_decl_die (member);
       if (child)
@@ -21733,6 +21787,9 @@  lookup_filename (const char *file_name)
 {
   struct dwarf_file_data * created;
 
+  if (!file_name)
+    return NULL;
+
   dwarf_file_data **slot
     = file_table->find_slot_with_hash (file_name, htab_hash_string (file_name),
 				       INSERT);
@@ -24726,10 +24783,19 @@  optimize_location_lists (dw_die_ref die)
 static void
 dwarf2out_finish (const char *filename)
 {
-  limbo_die_node *node, *next_node;
   comdat_type_node *ctnode;
   dw_die_ref main_comp_unit_die;
 
+  /* If the limbo list has anything, it should be things that were
+     created after the compilation proper.  Anything from the early
+     dwarf pass, should have parents and should never be in the limbo
+     list this late.  */
+  for (limbo_die_node *node = limbo_die_list; node; node = node->next)
+    gcc_assert (!node->die->dumped_early);
+
+  /* Flush out any latecomers to the limbo party.  */
+  dwarf2out_early_finish();
+
   /* PCH might result in DW_AT_producer string being restored from the
      header compilation, so always fill it with empty string initially
      and overwrite only here.  */
@@ -24754,55 +24820,6 @@  dwarf2out_finish (const char *filename)
 	add_comp_dir_attribute (comp_unit_die ());
     }
 
-  /* Traverse the limbo die list, and add parent/child links.  The only
-     dies without parents that should be here are concrete instances of
-     inline functions, and the comp_unit_die.  We can ignore the comp_unit_die.
-     For concrete instances, we can get the parent die from the abstract
-     instance.  */
-  for (node = limbo_die_list; node; node = next_node)
-    {
-      dw_die_ref die = node->die;
-      next_node = node->next;
-
-      if (die->die_parent == NULL)
-	{
-	  dw_die_ref origin = get_AT_ref (die, DW_AT_abstract_origin);
-
-	  if (origin && origin->die_parent)
-	    add_child_die (origin->die_parent, die);
-	  else if (is_cu_die (die))
-	    ;
-	  else if (seen_error ())
-	    /* It's OK to be confused by errors in the input.  */
-	    add_child_die (comp_unit_die (), die);
-	  else
-	    {
-	      /* In certain situations, the lexical block containing a
-		 nested function can be optimized away, which results
-		 in the nested function die being orphaned.  Likewise
-		 with the return type of that nested function.  Force
-		 this to be a child of the containing function.
-
-		 It may happen that even the containing function got fully
-		 inlined and optimized out.  In that case we are lost and
-		 assign the empty child.  This should not be big issue as
-		 the function is likely unreachable too.  */
-	      gcc_assert (node->created_for);
-
-	      if (DECL_P (node->created_for))
-		origin = get_context_die (DECL_CONTEXT (node->created_for));
-	      else if (TYPE_P (node->created_for))
-		origin = scope_die_for (node->created_for, comp_unit_die ());
-	      else
-		origin = comp_unit_die ();
-
-	      add_child_die (origin, die);
-	    }
-	}
-    }
-
-  limbo_die_list = NULL;
-
 #if ENABLE_ASSERT_CHECKING
   {
     dw_die_ref die = comp_unit_die (), c;
@@ -24850,6 +24867,7 @@  dwarf2out_finish (const char *filename)
   /* Traverse the DIE's and add add sibling attributes to those DIE's
      that have children.  */
   add_sibling_attributes (comp_unit_die ());
+  limbo_die_node *node;
   for (node = limbo_die_list; node; node = node->next)
     add_sibling_attributes (node->die);
   for (ctnode = comdat_type_list; ctnode != NULL; ctnode = ctnode->next)
@@ -25111,6 +25129,66 @@  dwarf2out_finish (const char *filename)
     output_indirect_strings ();
 }
 
+/* Perform any cleanups needed after the early debug generation pass
+   has run.  */
+
+static void
+dwarf2out_early_finish (void)
+{
+  /* Traverse the limbo die list, and add parent/child links.  The only
+     dies without parents that should be here are concrete instances of
+     inline functions, and the comp_unit_die.  We can ignore the comp_unit_die.
+     For concrete instances, we can get the parent die from the abstract
+     instance.
+
+     The point here is to flush out the limbo list so that it is empty
+     and we don't need to stream it for LTO.  */
+  limbo_die_node *node, *next_node;
+  for (node = limbo_die_list; node; node = next_node)
+    {
+      dw_die_ref die = node->die;
+      next_node = node->next;
+
+      if (die->die_parent == NULL)
+	{
+	  dw_die_ref origin = get_AT_ref (die, DW_AT_abstract_origin);
+
+	  if (origin && origin->die_parent)
+	    add_child_die (origin->die_parent, die);
+	  else if (is_cu_die (die))
+	    ;
+	  else if (seen_error ())
+	    /* It's OK to be confused by errors in the input.  */
+	    add_child_die (comp_unit_die (), die);
+	  else
+	    {
+	      /* In certain situations, the lexical block containing a
+		 nested function can be optimized away, which results
+		 in the nested function die being orphaned.  Likewise
+		 with the return type of that nested function.  Force
+		 this to be a child of the containing function.
+
+		 It may happen that even the containing function got fully
+		 inlined and optimized out.  In that case we are lost and
+		 assign the empty child.  This should not be big issue as
+		 the function is likely unreachable too.  */
+	      gcc_assert (node->created_for);
+
+	      if (DECL_P (node->created_for))
+		origin = get_context_die (DECL_CONTEXT (node->created_for));
+	      else if (TYPE_P (node->created_for))
+		origin = scope_die_for (node->created_for, comp_unit_die ());
+	      else
+		origin = comp_unit_die ();
+
+	      add_child_die (origin, die);
+	    }
+	}
+    }
+
+  limbo_die_list = NULL;
+}
+
 /* Reset all state within dwarf2out.c so that we can rerun the compiler
    within the same process.  For use by toplev::finalize.  */
 
diff --git a/gcc/sdbout.c b/gcc/sdbout.c
index d7b2d6b..43b8cf2 100644
--- a/gcc/sdbout.c
+++ b/gcc/sdbout.c
@@ -279,6 +279,7 @@  const struct gcc_debug_hooks sdb_debug_hooks =
 {
   sdbout_init,			         /* init */
   sdbout_finish,		         /* finish */
+  debug_nothing_void,			 /* early_finish */
   debug_nothing_void,			 /* assembly_start */
   debug_nothing_int_charstar,	         /* define */
   debug_nothing_int_charstar,	         /* undef */
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 42a2cdc..6b58ed2 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -2136,6 +2136,21 @@  toplev::main (int argc, char **argv)
   if (version_flag)
     print_version (stderr, "");
 
+  /* FIXME: Temporary debugging aid to know which LTO phase we are in
+     without having to pass -v to the driver and all its verbosity.  */
+  if (0)
+    {
+      fprintf(stderr, "MAIN: cc1*\n");
+      if (flag_lto)
+	fprintf(stderr, "\tMAIN: flag_lto\n");
+      if (in_lto_p)
+	fprintf(stderr, "\tMAIN: in_lto_p\n");
+      if (flag_wpa)
+	fprintf(stderr, "\tMAIN: flag_wpa\n");
+      if (flag_ltrans)
+	fprintf(stderr, "\tMAIN: flag_ltrans\n");
+    }
+
   if (help_flag)
     print_plugins_help (stderr, "");
 
diff --git a/gcc/tree.c b/gcc/tree.c
index 8743763..80b4287 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -5730,6 +5730,10 @@  free_lang_data (void)
 {
   unsigned i;
 
+  /* Clean up anything that needs cleaning up after initial debug
+     generation.  */
+  (*debug_hooks->early_finish) ();
+
   /* If we are the LTO frontend we have freed lang-specific data already.  */
   if (in_lto_p
       /* FIXME: Eventually we need to remove this so the function
diff --git a/gcc/vmsdbgout.c b/gcc/vmsdbgout.c
index 5cb66bc..6da48eb 100644
--- a/gcc/vmsdbgout.c
+++ b/gcc/vmsdbgout.c
@@ -179,6 +179,7 @@  static void vmsdbgout_abstract_function (tree);
 const struct gcc_debug_hooks vmsdbg_debug_hooks
 = {vmsdbgout_init,
    vmsdbgout_finish,
+   debug_nothing_void,
    vmsdbgout_assembly_start,
    vmsdbgout_define,
    vmsdbgout_undef,