diff mbox

Symbol table 20/many: cleanup of cgraph_remove_unreachable_nodes

Message ID 20120509151515.GA1951@kam.mff.cuni.cz
State New
Headers show

Commit Message

Jan Hubicka May 9, 2012, 3:15 p.m. UTC
Hi,
this patch reworks cgraph_remove_unreachable_nodes that has grown from somewhat
hackish but simple mark&sweep routine into quite unmaintainable mess.  It is
because it needs to handle several special cases - extern inline functions,
virtual functions, clones and inline clone tree reshaping and these features
was not added in particularly nice way and their handling changed several
times. 

Things bacome even more messy when varpool walking was added with ipa-ref
infrastructure in 4.6 timeframe.

This patch rewrites the function to be similar to what lto-cgraph does.  I.e.
compute reachable symbols and the boundary.  I also made some fixes and made
the function to release function bodies that are no longer neccesary more
aggressively.

This, for about 10th time, made me to run into problems with OMP lowering.
OMP create declarations of the artificial functions early, but fills them with
bodies only much later.  It keeps the functions incomplette for a while,
with STRUCT_FUNCTION initialized but left empty.

This patch simply makes it to initialize those structures when they are
needed.

Bootstrapped/regtested x86_64-linux, will commit it shortly.

Honza

	* cgraph.h (cgraph_remove_unreachable_nodes): Rename to ...
	(symtab_remove_unreachable_nodes): ... this one.
	* ipa-cp.c (ipcp_driver): Do not remove unreachable nodes.
	* omp-low.c (create_omp_child_function): Do not initialize
	DECL_INITIAL and STRUCT_FUNCTION.
	(remove_exit_barrier): Do not try to walk not created yet
	function bodies.
	(expand_omp_taskreg, lower_omp_taskreg): Initialize
	the STRUCT_FUNCTION and DECL_INITIAL here.
	* cgraphunit.c (ipa_passes): Update.
	* cgraphclones.c (cgraph_materialize_all_clones): Update.
	* ipa-inline.c (ipa_inline): Update.
	* ipa.c: Include ipa-inline.h
	(enqueue_cgraph_node, enqueue_varpool_node): Remove.
	(enqueue_node): New function.
	(process_references): Update.
	(symtab_remove_unreachable_nodes): Cleanup.
	* passes.c (execute_todo, execute_one_pass): Update.

Comments

Jan Hubicka May 10, 2012, 8:19 p.m. UTC | #1
Hi,
after some thought, the changes into omp-low are not as obviously harmless as I
originally tought.  So i decided to handle this by separate patch.  This patch
simply makes cgraph to not release bodies of artificial functions that papers
around the problem in easier way.

Bootstrapped/regtested x86_64-linux, comitted.

Honza

	* cgraph.h (cgraph_remove_unreachable_nodes): Rename to ...
	(symtab_remove_unreachable_nodes): ... this one.
	* ipa-cp.c (ipcp_driver): Do not remove unreachable nodes.
	* cgraphunit.c (ipa_passes): Update.
	* cgraphclones.c (cgraph_materialize_all_clones): Update.
	* cgraph.c (cgraph_release_function_body): Only turn initial
	into error mark when initial was previously set.
	* ipa-inline.c (ipa_inline): Update.
	* ipa.c: Include ipa-inline.h
	(enqueue_cgraph_node, enqueue_varpool_node): Remove.
	(enqueue_node): New function.
	(process_references): Update.
	(symtab_remove_unreachable_nodes): Cleanup.
	* passes.c (execute_todo, execute_one_pass): Update.
Index: cgraph.c
===================================================================
*** cgraph.c	(revision 187335)
--- cgraph.c	(working copy)
*************** cgraph_release_function_body (struct cgr
*** 1162,1168 ****
    /* If the node is abstract and needed, then do not clear DECL_INITIAL
       of its associated function function declaration because it's
       needed to emit debug info later.  */
!   if (!node->abstract_and_needed)
      DECL_INITIAL (node->symbol.decl) = error_mark_node;
  }
  
--- 1162,1168 ----
    /* If the node is abstract and needed, then do not clear DECL_INITIAL
       of its associated function function declaration because it's
       needed to emit debug info later.  */
!   if (!node->abstract_and_needed && DECL_INITIAL (node->symbol.decl))
      DECL_INITIAL (node->symbol.decl) = error_mark_node;
  }
  
Index: cgraph.h
===================================================================
*** cgraph.h	(revision 187335)
--- cgraph.h	(working copy)
*************** int compute_call_stmt_bb_frequency (tree
*** 637,643 ****
  void record_references_in_initializer (tree, bool);
  
  /* In ipa.c  */
! bool cgraph_remove_unreachable_nodes (bool, FILE *);
  cgraph_node_set cgraph_node_set_new (void);
  cgraph_node_set_iterator cgraph_node_set_find (cgraph_node_set,
  					       struct cgraph_node *);
--- 637,643 ----
  void record_references_in_initializer (tree, bool);
  
  /* In ipa.c  */
! bool symtab_remove_unreachable_nodes (bool, FILE *);
  cgraph_node_set cgraph_node_set_new (void);
  cgraph_node_set_iterator cgraph_node_set_find (cgraph_node_set,
  					       struct cgraph_node *);
Index: ipa-cp.c
===================================================================
*** ipa-cp.c	(revision 187335)
--- ipa-cp.c	(working copy)
*************** ipcp_driver (void)
*** 2445,2451 ****
    struct cgraph_2edge_hook_list *edge_duplication_hook_holder;
    struct topo_info topo;
  
-   cgraph_remove_unreachable_nodes (true,dump_file);
    ipa_check_create_node_params ();
    ipa_check_create_edge_args ();
    grow_next_edge_clone_vector ();
--- 2445,2450 ----
Index: cgraphunit.c
===================================================================
*** cgraphunit.c	(revision 187335)
--- cgraphunit.c	(working copy)
*************** ipa_passes (void)
*** 1836,1842 ****
       because TODO is run before the subpasses.  It is important to remove
       the unreachable functions to save works at IPA level and to get LTO
       symbol tables right.  */
!   cgraph_remove_unreachable_nodes (true, cgraph_dump_file);
  
    /* If pass_all_early_optimizations was not scheduled, the state of
       the cgraph will not be properly updated.  Update it now.  */
--- 1836,1842 ----
       because TODO is run before the subpasses.  It is important to remove
       the unreachable functions to save works at IPA level and to get LTO
       symbol tables right.  */
!   symtab_remove_unreachable_nodes (true, cgraph_dump_file);
  
    /* If pass_all_early_optimizations was not scheduled, the state of
       the cgraph will not be properly updated.  Update it now.  */
*************** compile (void)
*** 1962,1968 ****
  
    /* This pass remove bodies of extern inline functions we never inlined.
       Do this later so other IPA passes see what is really going on.  */
!   cgraph_remove_unreachable_nodes (false, dump_file);
    cgraph_global_info_ready = true;
    if (cgraph_dump_file)
      {
--- 1962,1968 ----
  
    /* This pass remove bodies of extern inline functions we never inlined.
       Do this later so other IPA passes see what is really going on.  */
!   symtab_remove_unreachable_nodes (false, dump_file);
    cgraph_global_info_ready = true;
    if (cgraph_dump_file)
      {
*************** compile (void)
*** 1987,1993 ****
    cgraph_materialize_all_clones ();
    bitmap_obstack_initialize (NULL);
    execute_ipa_pass_list (all_late_ipa_passes);
!   cgraph_remove_unreachable_nodes (true, dump_file);
  #ifdef ENABLE_CHECKING
    verify_symtab ();
  #endif
--- 1987,1993 ----
    cgraph_materialize_all_clones ();
    bitmap_obstack_initialize (NULL);
    execute_ipa_pass_list (all_late_ipa_passes);
!   symtab_remove_unreachable_nodes (true, dump_file);
  #ifdef ENABLE_CHECKING
    verify_symtab ();
  #endif
Index: cgraphclones.c
===================================================================
*** cgraphclones.c	(revision 187335)
--- cgraphclones.c	(working copy)
*************** cgraph_materialize_all_clones (void)
*** 870,876 ****
  #ifdef ENABLE_CHECKING
    verify_cgraph ();
  #endif
!   cgraph_remove_unreachable_nodes (false, cgraph_dump_file);
  }
  
  #include "gt-cgraphclones.h"
--- 870,876 ----
  #ifdef ENABLE_CHECKING
    verify_cgraph ();
  #endif
!   symtab_remove_unreachable_nodes (false, cgraph_dump_file);
  }
  
  #include "gt-cgraphclones.h"
Index: ipa-inline.c
===================================================================
*** ipa-inline.c	(revision 187335)
--- ipa-inline.c	(working copy)
*************** ipa_inline (void)
*** 1717,1723 ****
      }
  
    inline_small_functions ();
!   cgraph_remove_unreachable_nodes (true, dump_file);
    free (order);
  
    /* We already perform some inlining of functions called once during
--- 1717,1723 ----
      }
  
    inline_small_functions ();
!   symtab_remove_unreachable_nodes (true, dump_file);
    free (order);
  
    /* We already perform some inlining of functions called once during
Index: ipa.c
===================================================================
*** ipa.c	(revision 187335)
--- ipa.c	(working copy)
*************** along with GCC; see the file COPYING3.  
*** 33,38 ****
--- 33,39 ----
  #include "tree-iterator.h"
  #include "ipa-utils.h"
  #include "pointer-set.h"
+ #include "ipa-inline.h"
  
  /* Look for all functions inlined to NODE and update their inlined_to pointers
     to INLINED_TO.  */
*************** update_inlined_to_pointer (struct cgraph
*** 49,55 ****
        }
  }
  
! /* Add cgraph NODE to queue starting at FIRST.
  
     The queue is linked via AUX pointers and terminated by pointer to 1.
     We enqueue nodes at two occasions: when we find them reachable or when we find
--- 50,56 ----
        }
  }
  
! /* Add symtab NODE to queue starting at FIRST.
  
     The queue is linked via AUX pointers and terminated by pointer to 1.
     We enqueue nodes at two occasions: when we find them reachable or when we find
*************** update_inlined_to_pointer (struct cgraph
*** 58,65 ****
     reachable.  */
  
  static void
! enqueue_cgraph_node (struct cgraph_node *node, struct cgraph_node **first,
! 		     struct pointer_set_t *reachable)
  {
    /* Node is still in queue; do nothing.  */
    if (node->symbol.aux && node->symbol.aux != (void *) 2)
--- 59,66 ----
     reachable.  */
  
  static void
! enqueue_node (symtab_node node, symtab_node *first,
! 	      struct pointer_set_t *reachable)
  {
    /* Node is still in queue; do nothing.  */
    if (node->symbol.aux && node->symbol.aux != (void *) 2)
*************** enqueue_cgraph_node (struct cgraph_node 
*** 72,92 ****
    *first = node;
  }
  
- /* Add varpool NODE to queue starting at FIRST.  */
- 
- static void
- enqueue_varpool_node (struct varpool_node *node, struct varpool_node **first)
- {
-   node->symbol.aux = *first;
-   *first = node;
- }
- 
  /* Process references.  */
  
  static void
  process_references (struct ipa_ref_list *list,
! 		    struct cgraph_node **first,
! 		    struct varpool_node **first_varpool,
  		    bool before_inlining_p,
  		    struct pointer_set_t *reachable)
  {
--- 73,83 ----
    *first = node;
  }
  
  /* Process references.  */
  
  static void
  process_references (struct ipa_ref_list *list,
! 		    symtab_node *first,
  		    bool before_inlining_p,
  		    struct pointer_set_t *reachable)
  {
*************** process_references (struct ipa_ref_list 
*** 97,114 ****
        if (symtab_function_p (ref->referred))
  	{
  	  struct cgraph_node *node = ipa_ref_node (ref);
  	  if (node->analyzed
  	      && (!DECL_EXTERNAL (node->symbol.decl)
  		  || node->alias
  	          || before_inlining_p))
  	    pointer_set_insert (reachable, node);
! 	  enqueue_cgraph_node (node, first, reachable);
  	}
        else
  	{
  	  struct varpool_node *node = ipa_ref_varpool_node (ref);
! 	  if (!pointer_set_insert (reachable, node))
! 	    enqueue_varpool_node (node, first_varpool);
  	}
      }
  }
--- 88,108 ----
        if (symtab_function_p (ref->referred))
  	{
  	  struct cgraph_node *node = ipa_ref_node (ref);
+ 
  	  if (node->analyzed
  	      && (!DECL_EXTERNAL (node->symbol.decl)
  		  || node->alias
  	          || before_inlining_p))
  	    pointer_set_insert (reachable, node);
! 	  enqueue_node ((symtab_node) node, first, reachable);
  	}
        else
  	{
  	  struct varpool_node *node = ipa_ref_varpool_node (ref);
! 
! 	  if (node->analyzed)
! 	    pointer_set_insert (reachable, node);
! 	  enqueue_node ((symtab_node) node, first, reachable);
  	}
      }
  }
*************** has_addr_references_p (struct cgraph_nod
*** 162,180 ****
  }
  
  /* Perform reachability analysis and reclaim all unreachable nodes.
!    If BEFORE_INLINING_P is true this function is called before inlining
!    decisions has been made.  If BEFORE_INLINING_P is false this function also
!    removes unneeded bodies of extern inline functions.  */
  
  bool
! cgraph_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
  {
!   struct cgraph_node *first = (struct cgraph_node *) (void *) 1;
!   struct varpool_node *first_varpool = (struct varpool_node *) (void *) 1;
    struct cgraph_node *node, *next;
    struct varpool_node *vnode, *vnext;
    bool changed = false;
    struct pointer_set_t *reachable = pointer_set_create ();
  
  #ifdef ENABLE_CHECKING
    verify_symtab ();
--- 156,218 ----
  }
  
  /* Perform reachability analysis and reclaim all unreachable nodes.
! 
!    The algorithm is basically mark&sweep but with some extra refinements:
! 
!    - reachable extern inline functions needs special handling; the bodies needs
!      to stay in memory until inlining in hope that they will be inlined.
!      After inlining we release their bodies and turn them into unanalyzed
!      nodes even when they are reachable.
! 
!      BEFORE_INLINING_P specify whether we are before or after inlining.
! 
!    - virtual functions are kept in callgraph even if they seem unreachable in
!      hope calls to them will be devirtualized. 
! 
!      Again we remove them after inlining.  In late optimization some
!      devirtualization may happen, but it is not importnat since we won't inline
!      the call. In theory early opts and IPA should work out all important cases.
! 
!    - virtual clones needs bodies of their origins for later materialization;
!      this means that we want to keep the body even if the origin is unreachable
!      otherwise.  To avoid origin from sitting in the callgraph and being
!      walked by IPA passes, we turn them into unanalyzed nodes with body
!      defined.
! 
!      We maintain set of function declaration where body needs to stay in
!      body_needed_for_clonning
! 
!      Inline clones represent special case: their declaration match the
!      declaration of origin and cgraph_remove_node already knows how to
!      reshape callgraph and preserve body when offline copy of function or
!      inline clone is being removed.
! 
!    We maintain queue of both reachable symbols (i.e. defined symbols that needs
!    to stay) and symbols that are in boundary (i.e. external symbols referenced
!    by reachable symbols or origins of clones).  The queue is represented
!    as linked list by AUX pointer terminated by 1.
! 
!    A the end we keep all reachable symbols. For symbols in boundary we always
!    turn definition into a declaration, but we may keep function body around
!    based on body_needed_for_clonning
! 
!    All symbols that enter the queue have AUX pointer non-zero and are in the
!    boundary.  Pointer set REACHABLE is used to track reachable symbols.
! 
!    Every symbol can be visited twice - once as part of boundary and once
!    as real reachable symbol. enqueue_node needs to decide whether the
!    node needs to be re-queued for second processing.  For this purpose
!    we set AUX pointer of processed symbols in the boundary to constant 2.  */
  
  bool
! symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
  {
!   symtab_node first = (symtab_node) (void *) 1;
    struct cgraph_node *node, *next;
    struct varpool_node *vnode, *vnext;
    bool changed = false;
    struct pointer_set_t *reachable = pointer_set_create ();
+   struct pointer_set_t *body_needed_for_clonning = pointer_set_create ();
  
  #ifdef ENABLE_CHECKING
    verify_symtab ();
*************** cgraph_remove_unreachable_nodes (bool be
*** 191,198 ****
       This is mostly when they can be referenced externally.  Inline clones
       are special since their declarations are shared with master clone and thus
       cgraph_can_remove_if_no_direct_calls_and_refs_p should not be called on them.  */
!   FOR_EACH_FUNCTION (node)
!     if (node->analyzed && !node->global.inlined_to
  	&& (!cgraph_can_remove_if_no_direct_calls_and_refs_p (node)
  	    /* Keep around virtual functions for possible devirtualization.  */
  	    || (before_inlining_p
--- 229,236 ----
       This is mostly when they can be referenced externally.  Inline clones
       are special since their declarations are shared with master clone and thus
       cgraph_can_remove_if_no_direct_calls_and_refs_p should not be called on them.  */
!   FOR_EACH_DEFINED_FUNCTION (node)
!     if (!node->global.inlined_to
  	&& (!cgraph_can_remove_if_no_direct_calls_and_refs_p (node)
  	    /* Keep around virtual functions for possible devirtualization.  */
  	    || (before_inlining_p
*************** cgraph_remove_unreachable_nodes (bool be
*** 200,397 ****
  		&& (DECL_COMDAT (node->symbol.decl) || DECL_EXTERNAL (node->symbol.decl)))))
        {
          gcc_assert (!node->global.inlined_to);
- 	enqueue_cgraph_node (node, &first, reachable);
  	pointer_set_insert (reachable, node);
        }
      else
        gcc_assert (!node->symbol.aux);
  
    /* Mark variables that are obviously needed.  */
!   FOR_EACH_VARIABLE (vnode)
      {
!       if ((vnode->analyzed || vnode->symbol.force_output)
! 	  && !varpool_can_remove_if_no_refs (vnode))
! 	{
! 	  pointer_set_insert (reachable, vnode);
! 	  enqueue_varpool_node (vnode, &first_varpool);
! 	}
!     }
  
!   /* Perform reachability analysis.  As a special case do not consider
!      extern inline functions not inlined as live because we won't output
!      them at all. 
  
!      We maintain two worklist, one for cgraph nodes other for varpools and
!      are finished once both are empty.  */
  
!   while (first != (struct cgraph_node *) (void *) 1
!   	 || first_varpool != (struct varpool_node *) (void *) 1)
!     {
!       if (first != (struct cgraph_node *) (void *) 1)
  	{
! 	  struct cgraph_edge *e;
! 	  node = first;
! 	  first = (struct cgraph_node *) first->symbol.aux;
! 	  if (!pointer_set_contains (reachable, node))
! 	    node->symbol.aux = (void *)2;
! 	  /* If we found this node reachable, first mark on the callees
! 	     reachable too, unless they are direct calls to extern inline functions
! 	     we decided to not inline.  */
! 	  else 
  	    {
! 	      for (e = node->callees; e; e = e->next_callee)
  		{
! 		  if (node->analyzed
  		      && (!e->inline_failed
  			  || !DECL_EXTERNAL (e->callee->symbol.decl)
! 			  || node->alias
  			  || before_inlining_p))
  		    pointer_set_insert (reachable, e->callee);
! 		  enqueue_cgraph_node (e->callee, &first, reachable);
! 		}
! 	      process_references (&node->symbol.ref_list, &first,
! 				  &first_varpool, before_inlining_p,
! 				  reachable);
! 
! 	      /* If any function in a comdat group is reachable, force
! 		 all other functions in the same comdat group to be
! 		 also reachable.  */
! 	      if (node->symbol.same_comdat_group
! 		  && !node->global.inlined_to)
! 		{
! 		  for (next = cgraph (node->symbol.same_comdat_group);
! 		       next != node;
! 		       next = cgraph (next->symbol.same_comdat_group))
! 		    if (!pointer_set_insert (reachable, next))
! 		      enqueue_cgraph_node (next, &first, reachable);
  		}
  	    }
  
! 	  /* We can freely remove inline clones even if they are cloned, however if
! 	     function is clone of real clone, we must keep it around in order to
! 	     make materialize_clones produce function body with the changes
! 	     applied.  */
! 	  while (node->clone_of && !node->clone_of->symbol.aux
! 	         && !gimple_has_body_p (node->symbol.decl))
  	    {
! 	      bool noninline = node->clone_of->symbol.decl != node->symbol.decl;
! 	      node = node->clone_of;
! 	      if (noninline && !pointer_set_contains (reachable, node) && !node->symbol.aux)
  	      	{
! 		  enqueue_cgraph_node (node, &first, reachable);
  		  break;
  		}
  	    }
  	}
-       if (first_varpool != (struct varpool_node *) (void *) 1)
- 	{
- 	  vnode = first_varpool;
- 	  first_varpool = (struct varpool_node *)first_varpool->symbol.aux;
- 	  vnode->symbol.aux = NULL;
- 	  process_references (&vnode->symbol.ref_list, &first,
- 			      &first_varpool, before_inlining_p,
- 			      reachable);
- 	  /* If any function in a comdat group is reachable, force
- 	     all other functions in the same comdat group to be
- 	     also reachable.  */
- 	  if (vnode->symbol.same_comdat_group)
- 	    {
- 	      struct varpool_node *next;
- 	      for (next = varpool (vnode->symbol.same_comdat_group);
- 		   next != vnode;
- 		   next = varpool (next->symbol.same_comdat_group))
- 		if (!pointer_set_insert (reachable, next))
- 		  enqueue_varpool_node (next, &first_varpool);
- 	    }
- 	}
      }
  
!   /* Remove unreachable nodes. 
! 
!      Completely unreachable functions can be fully removed from the callgraph.
!      Extern inline functions that we decided to not inline need to become unanalyzed nodes of
!      callgraph (so we still have edges to them).  We remove function body then.
! 
!      Also we need to care functions that are unreachable but we need to keep them around
!      for later clonning.  In this case we also turn them to unanalyzed nodes, but
!      keep the body around.  */
    for (node = cgraph_first_function (); node; node = next)
      {
        next = cgraph_next_function (node);
-       if (node->symbol.aux && !pointer_set_contains (reachable, node))
-         {
- 	  cgraph_node_remove_callees (node);
- 	  ipa_remove_all_references (&node->symbol.ref_list);
- 	  node->analyzed = false;
- 	}
        if (!node->symbol.aux)
  	{
- 	  struct cgraph_edge *e;
- 	  bool found = false;
- 	  int i;
- 	  struct ipa_ref *ref;
- 
-           node->global.inlined_to = NULL;
  	  if (file)
  	    fprintf (file, " %s", cgraph_node_name (node));
! 	  /* See if there is reachable caller.  */
! 	  for (e = node->callers; e && !found; e = e->next_caller)
! 	    if (pointer_set_contains (reachable, e->caller))
! 	      found = true;
! 	  for (i = 0; (ipa_ref_list_referring_iterate (&node->symbol.ref_list,
! 						      i, ref)
! 		       && !found); i++)
! 	    if (pointer_set_contains (reachable, ref->referring))
! 	      found = true;
! 
! 	  /* If so, we need to keep node in the callgraph.  */
! 	  if (found)
! 	    {
! 	      if (node->analyzed)
! 		{
! 		  struct cgraph_node *clone;
! 
! 		  /* If there are still clones, we must keep body around.
! 		     Otherwise we can just remove the body but keep the clone.  */
! 		  for (clone = node->clones; clone;
! 		       clone = clone->next_sibling_clone)
! 		    if (clone->symbol.aux)
! 		      break;
! 		  if (!clone)
! 		    {
! 		      cgraph_release_function_body (node);
! 		      if (node->prev_sibling_clone)
! 			node->prev_sibling_clone->next_sibling_clone = node->next_sibling_clone;
! 		      else if (node->clone_of)
! 			node->clone_of->clones = node->next_sibling_clone;
! 		      if (node->next_sibling_clone)
! 			node->next_sibling_clone->prev_sibling_clone = node->prev_sibling_clone;
! 		      if (node->clone_of)
! 			node->former_clone_of = node->clone_of->symbol.decl;
! 		      node->clone_of = NULL;
! 		      node->next_sibling_clone = NULL;
! 		      node->prev_sibling_clone = NULL;
! 		    }
! 		  else
! 		    gcc_assert (!clone->symbol.in_other_partition);
! 		  node->analyzed = false;
! 		  changed = true;
! 		  cgraph_node_remove_callees (node);
! 		  ipa_remove_all_references (&node->symbol.ref_list);
! 		}
! 	    }
! 	  else
  	    {
! 	      cgraph_remove_node (node);
  	      changed = true;
  	    }
  	}
      }
    FOR_EACH_FUNCTION (node)
      {
-       /* Inline clones might be kept around so their materializing allows further
-          cloning.  If the function the clone is inlined into is removed, we need
-          to turn it into normal cone.  */
        if (node->global.inlined_to
  	  && !node->callers)
  	{
--- 238,363 ----
  		&& (DECL_COMDAT (node->symbol.decl) || DECL_EXTERNAL (node->symbol.decl)))))
        {
          gcc_assert (!node->global.inlined_to);
  	pointer_set_insert (reachable, node);
+ 	enqueue_node ((symtab_node)node, &first, reachable);
        }
      else
        gcc_assert (!node->symbol.aux);
  
    /* Mark variables that are obviously needed.  */
!   FOR_EACH_DEFINED_VARIABLE (vnode)
!     if (!varpool_can_remove_if_no_refs (vnode))
!       {
! 	pointer_set_insert (reachable, vnode);
! 	enqueue_node ((symtab_node)vnode, &first, reachable);
!       }
! 
!   /* Perform reachability analysis.  */
!   while (first != (symtab_node) (void *) 1)
      {
!       bool in_boundary_p = !pointer_set_contains (reachable, first);
!       symtab_node node = first;
  
!       first = (symtab_node)first->symbol.aux;
  
!       /* If we are processing symbol in boundary, mark its AUX pointer for
! 	 possible later re-processing in enqueue_node.  */
!       if (in_boundary_p)
! 	node->symbol.aux = (void *)2;
!       else
! 	{
! 	  /* If any symbol in a comdat group is reachable, force
! 	     all other in the same comdat group to be also reachable.  */
! 	  if (node->symbol.same_comdat_group)
! 	    {
! 	      symtab_node next;
! 	      for (next = node->symbol.same_comdat_group;
! 		   next != node;
! 		   next = next->symbol.same_comdat_group)
! 		if (!pointer_set_insert (reachable, next))
! 		  enqueue_node ((symtab_node) next, &first, reachable);
! 	    }
! 	  /* Mark references as reachable.  */
! 	  process_references (&node->symbol.ref_list, &first,
! 			      before_inlining_p, reachable);
! 	}
  
!       if (symtab_function_p (node))
  	{
! 	  struct cgraph_node *cnode = cgraph (node);
! 
! 	  /* Mark the callees reachable unless they are direct calls to extern
!  	     inline functions we decided to not inline.  */
! 	  if (!in_boundary_p)
  	    {
! 	      struct cgraph_edge *e;
! 	      for (e = cnode->callees; e; e = e->next_callee)
  		{
! 		  if (e->callee->analyzed
  		      && (!e->inline_failed
  			  || !DECL_EXTERNAL (e->callee->symbol.decl)
! 			  || cnode->alias
  			  || before_inlining_p))
  		    pointer_set_insert (reachable, e->callee);
! 		  enqueue_node ((symtab_node) e->callee, &first, reachable);
  		}
+ 
+ 	      /* When inline clone exists, mark body to be preserved so when removing
+ 		 offline copy of the function we don't kill it.  */
+ 	      if (!cnode->alias && cnode->global.inlined_to)
+ 	        pointer_set_insert (body_needed_for_clonning, cnode->symbol.decl);
  	    }
  
! 	  /* For non-inline clones, force their origins to the boundary and ensure
! 	     that body is not removed.  */
! 	  while (cnode->clone_of && !cnode->clone_of->symbol.aux
! 	         && !gimple_has_body_p (cnode->symbol.decl))
  	    {
! 	      bool noninline = cnode->clone_of->symbol.decl != cnode->symbol.decl;
! 	      cnode = cnode->clone_of;
! 	      if (noninline && !cnode->symbol.aux)
  	      	{
! 	          pointer_set_insert (body_needed_for_clonning, cnode->symbol.decl);
! 		  enqueue_node ((symtab_node)cnode, &first, reachable);
  		  break;
  		}
  	    }
  	}
      }
  
!   /* Remove unreachable functions.   */
    for (node = cgraph_first_function (); node; node = next)
      {
        next = cgraph_next_function (node);
        if (!node->symbol.aux)
  	{
  	  if (file)
  	    fprintf (file, " %s", cgraph_node_name (node));
! 	  cgraph_remove_node (node);
! 	  changed = true;
! 	}
!       else if (!pointer_set_contains (reachable, node))
!         {
! 	  if (node->analyzed)
  	    {
! 	      if (file)
! 		fprintf (file, " %s", cgraph_node_name (node));
! 	      cgraph_node_remove_callees (node);
! 	      ipa_remove_all_references (&node->symbol.ref_list);
  	      changed = true;
  	    }
+ 	  if (!pointer_set_contains (body_needed_for_clonning, node->symbol.decl)
+ 	      && !DECL_ARTIFICIAL (node->symbol.decl))
+ 	    cgraph_release_function_body (node);
+ 	  node->analyzed = false;
  	}
      }
+ 
+   /* Inline clones might be kept around so their materializing allows further
+      cloning.  If the function the clone is inlined into is removed, we need
+      to turn it into normal cone.  */
    FOR_EACH_FUNCTION (node)
      {
        if (node->global.inlined_to
  	  && !node->callers)
  	{
*************** cgraph_remove_unreachable_nodes (bool be
*** 402,426 ****
        node->symbol.aux = NULL;
      }
  
    if (file)
!     fprintf (file, "\n");
! 
!   if (file)
!     fprintf (file, "Reclaiming variables:");
    for (vnode = varpool_first_variable (); vnode; vnode = vnext)
      {
        vnext = varpool_next_variable (vnode);
!       if (!pointer_set_contains (reachable, vnode))
!         {
  	  if (file)
  	    fprintf (file, " %s", varpool_node_name (vnode));
  	  varpool_remove_node (vnode);
  	  changed = true;
  	}
      }
  
!   /* Now update address_taken flags and try to promote functions to be local.  */
  
    if (file)
      fprintf (file, "\nClearing address taken flags:");
    FOR_EACH_DEFINED_FUNCTION (node)
--- 368,405 ----
        node->symbol.aux = NULL;
      }
  
+   /* Remove unreachable variables.  */
    if (file)
!     fprintf (file, "\nReclaiming variables:");
    for (vnode = varpool_first_variable (); vnode; vnode = vnext)
      {
        vnext = varpool_next_variable (vnode);
!       if (!vnode->symbol.aux)
! 	{
  	  if (file)
  	    fprintf (file, " %s", varpool_node_name (vnode));
  	  varpool_remove_node (vnode);
  	  changed = true;
  	}
+       else if (!pointer_set_contains (reachable, vnode))
+         {
+ 	  if (vnode->analyzed)
+ 	    {
+ 	      if (file)
+ 		fprintf (file, " %s", varpool_node_name (vnode));
+ 	      changed = true;
+ 	    }
+ 	  vnode->analyzed = false;
+ 	  vnode->symbol.aux = NULL;
+ 	}
+       else
+ 	vnode->symbol.aux = NULL;
      }
  
!   pointer_set_destroy (reachable);
!   pointer_set_destroy (body_needed_for_clonning);
  
+   /* Now update address_taken flags and try to promote functions to be local.  */
    if (file)
      fprintf (file, "\nClearing address taken flags:");
    FOR_EACH_DEFINED_FUNCTION (node)
*************** cgraph_remove_unreachable_nodes (bool be
*** 444,461 ****
    if (file)
      fprintf (file, "\n");
  
-   /* Rest of transformations are undesirable at -O0.  */
-   if (!optimize)
-     return changed;
- 
  #ifdef ENABLE_CHECKING
    verify_symtab ();
  #endif
  
    /* Reclaim alias pairs for functions that have disappeared from the
       call graph.  */
    remove_unreachable_alias_pairs ();
-   pointer_set_destroy (reachable);
  
    return changed;
  }
--- 423,440 ----
    if (file)
      fprintf (file, "\n");
  
  #ifdef ENABLE_CHECKING
    verify_symtab ();
  #endif
  
+   /* If we removed something, perhaps profile could be improved.  */
+   if (changed && optimize && inline_edge_summary_vec)
+     FOR_EACH_DEFINED_FUNCTION (node)
+       cgraph_propagate_frequency (node);
+ 
    /* Reclaim alias pairs for functions that have disappeared from the
       call graph.  */
    remove_unreachable_alias_pairs ();
  
    return changed;
  }
Index: passes.c
===================================================================
*** passes.c	(revision 187335)
--- passes.c	(working copy)
*************** execute_todo (unsigned int flags)
*** 1865,1871 ****
    if (flags & TODO_remove_functions)
      {
        gcc_assert (!cfun);
!       cgraph_remove_unreachable_nodes (true, dump_file);
      }
  
    if ((flags & TODO_dump_symtab) && dump_file && !current_function_decl)
--- 1865,1871 ----
    if (flags & TODO_remove_functions)
      {
        gcc_assert (!cfun);
!       symtab_remove_unreachable_nodes (true, dump_file);
      }
  
    if ((flags & TODO_dump_symtab) && dump_file && !current_function_decl)
*************** execute_one_pass (struct opt_pass *pass)
*** 2150,2156 ****
        bool applied = false;
        do_per_function (apply_ipa_transforms, (void *)&applied);
        if (applied)
!         cgraph_remove_unreachable_nodes (true, dump_file);
        /* Restore current_pass.  */
        current_pass = pass;
      }
--- 2150,2156 ----
        bool applied = false;
        do_per_function (apply_ipa_transforms, (void *)&applied);
        if (applied)
!         symtab_remove_unreachable_nodes (true, dump_file);
        /* Restore current_pass.  */
        current_pass = pass;
      }
H.J. Lu May 11, 2012, 11:22 a.m. UTC | #2
On Thu, May 10, 2012 at 1:19 PM, Jan Hubicka <hubicka@ucw.cz> wrote:
> Hi,
> after some thought, the changes into omp-low are not as obviously harmless as I
> originally tought.  So i decided to handle this by separate patch.  This patch
> simply makes cgraph to not release bodies of artificial functions that papers
> around the problem in easier way.
>
> Bootstrapped/regtested x86_64-linux, comitted.
>
> Honza
>
>        * cgraph.h (cgraph_remove_unreachable_nodes): Rename to ...
>        (symtab_remove_unreachable_nodes): ... this one.
>        * ipa-cp.c (ipcp_driver): Do not remove unreachable nodes.
>        * cgraphunit.c (ipa_passes): Update.
>        * cgraphclones.c (cgraph_materialize_all_clones): Update.
>        * cgraph.c (cgraph_release_function_body): Only turn initial
>        into error mark when initial was previously set.
>        * ipa-inline.c (ipa_inline): Update.
>        * ipa.c: Include ipa-inline.h
>        (enqueue_cgraph_node, enqueue_varpool_node): Remove.
>        (enqueue_node): New function.
>        (process_references): Update.
>        (symtab_remove_unreachable_nodes): Cleanup.
>        * passes.c (execute_todo, execute_one_pass): Update.

This may have caused:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53321
diff mbox

Patch

Index: cgraph.h
===================================================================
--- cgraph.h	(revision 187335)
+++ cgraph.h	(working copy)
@@ -637,7 +637,7 @@  int compute_call_stmt_bb_frequency (tree
 void record_references_in_initializer (tree, bool);
 
 /* In ipa.c  */
-bool cgraph_remove_unreachable_nodes (bool, FILE *);
+bool symtab_remove_unreachable_nodes (bool, FILE *);
 cgraph_node_set cgraph_node_set_new (void);
 cgraph_node_set_iterator cgraph_node_set_find (cgraph_node_set,
 					       struct cgraph_node *);
Index: ipa-cp.c
===================================================================
--- ipa-cp.c	(revision 187335)
+++ ipa-cp.c	(working copy)
@@ -2445,7 +2445,6 @@  ipcp_driver (void)
   struct cgraph_2edge_hook_list *edge_duplication_hook_holder;
   struct topo_info topo;
 
-  cgraph_remove_unreachable_nodes (true,dump_file);
   ipa_check_create_node_params ();
   ipa_check_create_edge_args ();
   grow_next_edge_clone_vector ();
Index: omp-low.c
===================================================================
--- omp-low.c	(revision 187335)
+++ omp-low.c	(working copy)
@@ -1572,7 +1572,6 @@  create_omp_child_function (omp_context *
   DECL_UNINLINABLE (decl) = 1;
   DECL_EXTERNAL (decl) = 0;
   DECL_CONTEXT (decl) = NULL_TREE;
-  DECL_INITIAL (decl) = make_node (BLOCK);
 
   t = build_decl (DECL_SOURCE_LOCATION (decl),
 		  RESULT_DECL, NULL_TREE, void_type_node);
@@ -1605,13 +1604,6 @@  create_omp_child_function (omp_context *
       DECL_CHAIN (t) = DECL_ARGUMENTS (decl);
       DECL_ARGUMENTS (decl) = t;
     }
-
-  /* Allocate memory for the function structure.  The call to
-     allocate_struct_function clobbers CFUN, so we need to restore
-     it afterward.  */
-  push_struct_function (decl);
-  cfun->function_end_locus = gimple_location (ctx->stmt);
-  pop_cfun ();
 }
 
 
@@ -3241,28 +3233,31 @@  remove_exit_barrier (struct omp_region *
 	      unsigned ix;
 
 	      any_addressable_vars = 0;
-	      FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (child_fun), ix, decl)
-		if (TREE_ADDRESSABLE (decl))
-		  {
-		    any_addressable_vars = 1;
-		    break;
-		  }
-	      for (block = gimple_block (stmt);
-		   !any_addressable_vars
-		   && block
-		   && TREE_CODE (block) == BLOCK;
-		   block = BLOCK_SUPERCONTEXT (block))
+	      if (DECL_STRUCT_FUNCTION (child_fun))
 		{
-		  for (local_decls = BLOCK_VARS (block);
-		       local_decls;
-		       local_decls = DECL_CHAIN (local_decls))
-		    if (TREE_ADDRESSABLE (local_decls))
+		  FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (child_fun), ix, decl)
+		    if (TREE_ADDRESSABLE (decl))
 		      {
 			any_addressable_vars = 1;
 			break;
 		      }
-		  if (block == gimple_block (parallel_stmt))
-		    break;
+		  for (block = gimple_block (stmt);
+		       !any_addressable_vars
+		       && block
+		       && TREE_CODE (block) == BLOCK;
+		       block = BLOCK_SUPERCONTEXT (block))
+		    {
+		      for (local_decls = BLOCK_VARS (block);
+			   local_decls;
+			   local_decls = DECL_CHAIN (local_decls))
+			if (TREE_ADDRESSABLE (local_decls))
+			  {
+			    any_addressable_vars = 1;
+			    break;
+			  }
+		      if (block == gimple_block (parallel_stmt))
+			break;
+		    }
 		}
 	    }
 	  if (!any_addressable_vars)
@@ -3370,6 +3365,14 @@  expand_omp_taskreg (struct omp_region *r
 
   entry_stmt = last_stmt (region->entry);
   child_fn = gimple_omp_taskreg_child_fn (entry_stmt);
+
+  /* Allocate memory for the function structure.  The call to
+     allocate_struct_function clobbers CFUN, so we need to restore
+     it afterward.  */
+  push_struct_function (child_fn);
+  DECL_INITIAL (child_fn) = make_node (BLOCK);
+  pop_cfun ();
+
   child_cfun = DECL_STRUCT_FUNCTION (child_fn);
   /* If this function has been already instrumented, make sure
      the child function isn't instrumented again.  */
@@ -6685,6 +6688,14 @@  lower_omp_taskreg (gimple_stmt_iterator 
   par_bind = gimple_seq_first_stmt (gimple_omp_body (stmt));
   par_body = gimple_bind_body (par_bind);
   child_fn = ctx->cb.dst_fn;
+
+  /* Allocate memory for the function structure.  The call to
+     allocate_struct_function clobbers CFUN, so we need to restore
+     it afterward.  */
+  push_struct_function (child_fn);
+  DECL_INITIAL (child_fn) = make_node (BLOCK);
+  cfun->function_end_locus = gimple_location (ctx->stmt);
+  pop_cfun ();
   if (gimple_code (stmt) == GIMPLE_OMP_PARALLEL
       && !gimple_omp_parallel_combined_p (stmt))
     {
Index: cgraphunit.c
===================================================================
--- cgraphunit.c	(revision 187335)
+++ cgraphunit.c	(working copy)
@@ -1836,7 +1836,7 @@  ipa_passes (void)
      because TODO is run before the subpasses.  It is important to remove
      the unreachable functions to save works at IPA level and to get LTO
      symbol tables right.  */
-  cgraph_remove_unreachable_nodes (true, cgraph_dump_file);
+  symtab_remove_unreachable_nodes (true, cgraph_dump_file);
 
   /* If pass_all_early_optimizations was not scheduled, the state of
      the cgraph will not be properly updated.  Update it now.  */
@@ -1962,7 +1962,7 @@  compile (void)
 
   /* This pass remove bodies of extern inline functions we never inlined.
      Do this later so other IPA passes see what is really going on.  */
-  cgraph_remove_unreachable_nodes (false, dump_file);
+  symtab_remove_unreachable_nodes (false, dump_file);
   cgraph_global_info_ready = true;
   if (cgraph_dump_file)
     {
@@ -1987,7 +1987,7 @@  compile (void)
   cgraph_materialize_all_clones ();
   bitmap_obstack_initialize (NULL);
   execute_ipa_pass_list (all_late_ipa_passes);
-  cgraph_remove_unreachable_nodes (true, dump_file);
+  symtab_remove_unreachable_nodes (true, dump_file);
 #ifdef ENABLE_CHECKING
   verify_symtab ();
 #endif
Index: cgraphclones.c
===================================================================
--- cgraphclones.c	(revision 187335)
+++ cgraphclones.c	(working copy)
@@ -870,7 +870,7 @@  cgraph_materialize_all_clones (void)
 #ifdef ENABLE_CHECKING
   verify_cgraph ();
 #endif
-  cgraph_remove_unreachable_nodes (false, cgraph_dump_file);
+  symtab_remove_unreachable_nodes (false, cgraph_dump_file);
 }
 
 #include "gt-cgraphclones.h"
Index: ipa-inline.c
===================================================================
--- ipa-inline.c	(revision 187335)
+++ ipa-inline.c	(working copy)
@@ -1717,7 +1717,7 @@  ipa_inline (void)
     }
 
   inline_small_functions ();
-  cgraph_remove_unreachable_nodes (true, dump_file);
+  symtab_remove_unreachable_nodes (true, dump_file);
   free (order);
 
   /* We already perform some inlining of functions called once during
Index: ipa.c
===================================================================
--- ipa.c	(revision 187335)
+++ ipa.c	(working copy)
@@ -33,6 +33,7 @@  along with GCC; see the file COPYING3.  
 #include "tree-iterator.h"
 #include "ipa-utils.h"
 #include "pointer-set.h"
+#include "ipa-inline.h"
 
 /* Look for all functions inlined to NODE and update their inlined_to pointers
    to INLINED_TO.  */
@@ -49,7 +50,7 @@  update_inlined_to_pointer (struct cgraph
       }
 }
 
-/* Add cgraph NODE to queue starting at FIRST.
+/* Add symtab NODE to queue starting at FIRST.
 
    The queue is linked via AUX pointers and terminated by pointer to 1.
    We enqueue nodes at two occasions: when we find them reachable or when we find
@@ -58,8 +59,8 @@  update_inlined_to_pointer (struct cgraph
    reachable.  */
 
 static void
-enqueue_cgraph_node (struct cgraph_node *node, struct cgraph_node **first,
-		     struct pointer_set_t *reachable)
+enqueue_node (symtab_node node, symtab_node *first,
+	      struct pointer_set_t *reachable)
 {
   /* Node is still in queue; do nothing.  */
   if (node->symbol.aux && node->symbol.aux != (void *) 2)
@@ -72,21 +73,11 @@  enqueue_cgraph_node (struct cgraph_node 
   *first = node;
 }
 
-/* Add varpool NODE to queue starting at FIRST.  */
-
-static void
-enqueue_varpool_node (struct varpool_node *node, struct varpool_node **first)
-{
-  node->symbol.aux = *first;
-  *first = node;
-}
-
 /* Process references.  */
 
 static void
 process_references (struct ipa_ref_list *list,
-		    struct cgraph_node **first,
-		    struct varpool_node **first_varpool,
+		    symtab_node *first,
 		    bool before_inlining_p,
 		    struct pointer_set_t *reachable)
 {
@@ -97,18 +88,21 @@  process_references (struct ipa_ref_list 
       if (symtab_function_p (ref->referred))
 	{
 	  struct cgraph_node *node = ipa_ref_node (ref);
+
 	  if (node->analyzed
 	      && (!DECL_EXTERNAL (node->symbol.decl)
 		  || node->alias
 	          || before_inlining_p))
 	    pointer_set_insert (reachable, node);
-	  enqueue_cgraph_node (node, first, reachable);
+	  enqueue_node ((symtab_node) node, first, reachable);
 	}
       else
 	{
 	  struct varpool_node *node = ipa_ref_varpool_node (ref);
-	  if (!pointer_set_insert (reachable, node))
-	    enqueue_varpool_node (node, first_varpool);
+
+	  if (node->analyzed)
+	    pointer_set_insert (reachable, node);
+	  enqueue_node ((symtab_node) node, first, reachable);
 	}
     }
 }
@@ -162,19 +156,63 @@  has_addr_references_p (struct cgraph_nod
 }
 
 /* Perform reachability analysis and reclaim all unreachable nodes.
-   If BEFORE_INLINING_P is true this function is called before inlining
-   decisions has been made.  If BEFORE_INLINING_P is false this function also
-   removes unneeded bodies of extern inline functions.  */
+
+   The algorithm is basically mark&sweep but with some extra refinements:
+
+   - reachable extern inline functions needs special handling; the bodies needs
+     to stay in memory until inlining in hope that they will be inlined.
+     After inlining we release their bodies and turn them into unanalyzed
+     nodes even when they are reachable.
+
+     BEFORE_INLINING_P specify whether we are before or after inlining.
+
+   - virtual functions are kept in callgraph even if they seem unreachable in
+     hope calls to them will be devirtualized. 
+
+     Again we remove them after inlining.  In late optimization some
+     devirtualization may happen, but it is not importnat since we won't inline
+     the call. In theory early opts and IPA should work out all important cases.
+
+   - virtual clones needs bodies of their origins for later materialization;
+     this means that we want to keep the body even if the origin is unreachable
+     otherwise.  To avoid origin from sitting in the callgraph and being
+     walked by IPA passes, we turn them into unanalyzed nodes with body
+     defined.
+
+     We maintain set of function declaration where body needs to stay in
+     body_needed_for_clonning
+
+     Inline clones represent special case: their declaration match the
+     declaration of origin and cgraph_remove_node already knows how to
+     reshape callgraph and preserve body when offline copy of function or
+     inline clone is being removed.
+
+   We maintain queue of both reachable symbols (i.e. defined symbols that needs
+   to stay) and symbols that are in boundary (i.e. external symbols referenced
+   by reachable symbols or origins of clones).  The queue is represented
+   as linked list by AUX pointer terminated by 1.
+
+   A the end we keep all reachable symbols. For symbols in boundary we always
+   turn definition into a declaration, but we may keep function body around
+   based on body_needed_for_clonning
+
+   All symbols that enter the queue have AUX pointer non-zero and are in the
+   boundary.  Pointer set REACHABLE is used to track reachable symbols.
+
+   Every symbol can be visited twice - once as part of boundary and once
+   as real reachable symbol. enqueue_node needs to decide whether the
+   node needs to be re-queued for second processing.  For this purpose
+   we set AUX pointer of processed symbols in the boundary to constant 2.  */
 
 bool
-cgraph_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
+symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
 {
-  struct cgraph_node *first = (struct cgraph_node *) (void *) 1;
-  struct varpool_node *first_varpool = (struct varpool_node *) (void *) 1;
+  symtab_node first = (symtab_node) (void *) 1;
   struct cgraph_node *node, *next;
   struct varpool_node *vnode, *vnext;
   bool changed = false;
   struct pointer_set_t *reachable = pointer_set_create ();
+  struct pointer_set_t *body_needed_for_clonning = pointer_set_create ();
 
 #ifdef ENABLE_CHECKING
   verify_symtab ();
@@ -191,8 +229,8 @@  cgraph_remove_unreachable_nodes (bool be
      This is mostly when they can be referenced externally.  Inline clones
      are special since their declarations are shared with master clone and thus
      cgraph_can_remove_if_no_direct_calls_and_refs_p should not be called on them.  */
-  FOR_EACH_FUNCTION (node)
-    if (node->analyzed && !node->global.inlined_to
+  FOR_EACH_DEFINED_FUNCTION (node)
+    if (!node->global.inlined_to
 	&& (!cgraph_can_remove_if_no_direct_calls_and_refs_p (node)
 	    /* Keep around virtual functions for possible devirtualization.  */
 	    || (before_inlining_p
@@ -200,198 +238,125 @@  cgraph_remove_unreachable_nodes (bool be
 		&& (DECL_COMDAT (node->symbol.decl) || DECL_EXTERNAL (node->symbol.decl)))))
       {
         gcc_assert (!node->global.inlined_to);
-	enqueue_cgraph_node (node, &first, reachable);
 	pointer_set_insert (reachable, node);
+	enqueue_node ((symtab_node)node, &first, reachable);
       }
     else
       gcc_assert (!node->symbol.aux);
 
   /* Mark variables that are obviously needed.  */
-  FOR_EACH_VARIABLE (vnode)
+  FOR_EACH_DEFINED_VARIABLE (vnode)
+    if (!varpool_can_remove_if_no_refs (vnode))
+      {
+	pointer_set_insert (reachable, vnode);
+	enqueue_node ((symtab_node)vnode, &first, reachable);
+      }
+
+  /* Perform reachability analysis.  */
+  while (first != (symtab_node) (void *) 1)
     {
-      if ((vnode->analyzed || vnode->symbol.force_output)
-	  && !varpool_can_remove_if_no_refs (vnode))
-	{
-	  pointer_set_insert (reachable, vnode);
-	  enqueue_varpool_node (vnode, &first_varpool);
-	}
-    }
+      bool in_boundary_p = !pointer_set_contains (reachable, first);
+      symtab_node node = first;
 
-  /* Perform reachability analysis.  As a special case do not consider
-     extern inline functions not inlined as live because we won't output
-     them at all. 
+      first = (symtab_node)first->symbol.aux;
 
-     We maintain two worklist, one for cgraph nodes other for varpools and
-     are finished once both are empty.  */
+      /* If we are processing symbol in boundary, mark its AUX pointer for
+	 possible later re-processing in enqueue_node.  */
+      if (in_boundary_p)
+	node->symbol.aux = (void *)2;
+      else
+	{
+	  /* If any symbol in a comdat group is reachable, force
+	     all other in the same comdat group to be also reachable.  */
+	  if (node->symbol.same_comdat_group)
+	    {
+	      symtab_node next;
+	      for (next = node->symbol.same_comdat_group;
+		   next != node;
+		   next = next->symbol.same_comdat_group)
+		if (!pointer_set_insert (reachable, next))
+		  enqueue_node ((symtab_node) next, &first, reachable);
+	    }
+	  /* Mark references as reachable.  */
+	  process_references (&node->symbol.ref_list, &first,
+			      before_inlining_p, reachable);
+	}
 
-  while (first != (struct cgraph_node *) (void *) 1
-  	 || first_varpool != (struct varpool_node *) (void *) 1)
-    {
-      if (first != (struct cgraph_node *) (void *) 1)
+      if (symtab_function_p (node))
 	{
-	  struct cgraph_edge *e;
-	  node = first;
-	  first = (struct cgraph_node *) first->symbol.aux;
-	  if (!pointer_set_contains (reachable, node))
-	    node->symbol.aux = (void *)2;
-	  /* If we found this node reachable, first mark on the callees
-	     reachable too, unless they are direct calls to extern inline functions
-	     we decided to not inline.  */
-	  else 
+	  struct cgraph_node *cnode = cgraph (node);
+
+	  /* Mark the callees reachable unless they are direct calls to extern
+ 	     inline functions we decided to not inline.  */
+	  if (!in_boundary_p)
 	    {
-	      for (e = node->callees; e; e = e->next_callee)
+	      struct cgraph_edge *e;
+	      for (e = cnode->callees; e; e = e->next_callee)
 		{
-		  if (node->analyzed
+		  if (e->callee->analyzed
 		      && (!e->inline_failed
 			  || !DECL_EXTERNAL (e->callee->symbol.decl)
-			  || node->alias
+			  || cnode->alias
 			  || before_inlining_p))
 		    pointer_set_insert (reachable, e->callee);
-		  enqueue_cgraph_node (e->callee, &first, reachable);
-		}
-	      process_references (&node->symbol.ref_list, &first,
-				  &first_varpool, before_inlining_p,
-				  reachable);
-
-	      /* If any function in a comdat group is reachable, force
-		 all other functions in the same comdat group to be
-		 also reachable.  */
-	      if (node->symbol.same_comdat_group
-		  && !node->global.inlined_to)
-		{
-		  for (next = cgraph (node->symbol.same_comdat_group);
-		       next != node;
-		       next = cgraph (next->symbol.same_comdat_group))
-		    if (!pointer_set_insert (reachable, next))
-		      enqueue_cgraph_node (next, &first, reachable);
+		  enqueue_node ((symtab_node) e->callee, &first, reachable);
 		}
+
+	      /* When inline clone exists, mark body to be preserved so when removing
+		 offline copy of the function we don't kill it.  */
+	      if (!cnode->alias && cnode->global.inlined_to)
+	        pointer_set_insert (body_needed_for_clonning, cnode->symbol.decl);
 	    }
 
-	  /* We can freely remove inline clones even if they are cloned, however if
-	     function is clone of real clone, we must keep it around in order to
-	     make materialize_clones produce function body with the changes
-	     applied.  */
-	  while (node->clone_of && !node->clone_of->symbol.aux
-	         && !gimple_has_body_p (node->symbol.decl))
+	  /* For non-inline clones, force their origins to the boundary and ensure
+	     that body is not removed.  */
+	  while (cnode->clone_of && !cnode->clone_of->symbol.aux
+	         && !gimple_has_body_p (cnode->symbol.decl))
 	    {
-	      bool noninline = node->clone_of->symbol.decl != node->symbol.decl;
-	      node = node->clone_of;
-	      if (noninline && !pointer_set_contains (reachable, node) && !node->symbol.aux)
+	      bool noninline = cnode->clone_of->symbol.decl != cnode->symbol.decl;
+	      cnode = cnode->clone_of;
+	      if (noninline && !cnode->symbol.aux)
 	      	{
-		  enqueue_cgraph_node (node, &first, reachable);
+	          pointer_set_insert (body_needed_for_clonning, cnode->symbol.decl);
+		  enqueue_node ((symtab_node)cnode, &first, reachable);
 		  break;
 		}
 	    }
 	}
-      if (first_varpool != (struct varpool_node *) (void *) 1)
-	{
-	  vnode = first_varpool;
-	  first_varpool = (struct varpool_node *)first_varpool->symbol.aux;
-	  vnode->symbol.aux = NULL;
-	  process_references (&vnode->symbol.ref_list, &first,
-			      &first_varpool, before_inlining_p,
-			      reachable);
-	  /* If any function in a comdat group is reachable, force
-	     all other functions in the same comdat group to be
-	     also reachable.  */
-	  if (vnode->symbol.same_comdat_group)
-	    {
-	      struct varpool_node *next;
-	      for (next = varpool (vnode->symbol.same_comdat_group);
-		   next != vnode;
-		   next = varpool (next->symbol.same_comdat_group))
-		if (!pointer_set_insert (reachable, next))
-		  enqueue_varpool_node (next, &first_varpool);
-	    }
-	}
     }
 
-  /* Remove unreachable nodes. 
-
-     Completely unreachable functions can be fully removed from the callgraph.
-     Extern inline functions that we decided to not inline need to become unanalyzed nodes of
-     callgraph (so we still have edges to them).  We remove function body then.
-
-     Also we need to care functions that are unreachable but we need to keep them around
-     for later clonning.  In this case we also turn them to unanalyzed nodes, but
-     keep the body around.  */
+  /* Remove unreachable functions.   */
   for (node = cgraph_first_function (); node; node = next)
     {
       next = cgraph_next_function (node);
-      if (node->symbol.aux && !pointer_set_contains (reachable, node))
-        {
-	  cgraph_node_remove_callees (node);
-	  ipa_remove_all_references (&node->symbol.ref_list);
-	  node->analyzed = false;
-	}
       if (!node->symbol.aux)
 	{
-	  struct cgraph_edge *e;
-	  bool found = false;
-	  int i;
-	  struct ipa_ref *ref;
-
-          node->global.inlined_to = NULL;
 	  if (file)
 	    fprintf (file, " %s", cgraph_node_name (node));
-	  /* See if there is reachable caller.  */
-	  for (e = node->callers; e && !found; e = e->next_caller)
-	    if (pointer_set_contains (reachable, e->caller))
-	      found = true;
-	  for (i = 0; (ipa_ref_list_referring_iterate (&node->symbol.ref_list,
-						      i, ref)
-		       && !found); i++)
-	    if (pointer_set_contains (reachable, ref->referring))
-	      found = true;
-
-	  /* If so, we need to keep node in the callgraph.  */
-	  if (found)
-	    {
-	      if (node->analyzed)
-		{
-		  struct cgraph_node *clone;
-
-		  /* If there are still clones, we must keep body around.
-		     Otherwise we can just remove the body but keep the clone.  */
-		  for (clone = node->clones; clone;
-		       clone = clone->next_sibling_clone)
-		    if (clone->symbol.aux)
-		      break;
-		  if (!clone)
-		    {
-		      cgraph_release_function_body (node);
-		      if (node->prev_sibling_clone)
-			node->prev_sibling_clone->next_sibling_clone = node->next_sibling_clone;
-		      else if (node->clone_of)
-			node->clone_of->clones = node->next_sibling_clone;
-		      if (node->next_sibling_clone)
-			node->next_sibling_clone->prev_sibling_clone = node->prev_sibling_clone;
-		      if (node->clone_of)
-			node->former_clone_of = node->clone_of->symbol.decl;
-		      node->clone_of = NULL;
-		      node->next_sibling_clone = NULL;
-		      node->prev_sibling_clone = NULL;
-		    }
-		  else
-		    gcc_assert (!clone->symbol.in_other_partition);
-		  node->analyzed = false;
-		  changed = true;
-		  cgraph_node_remove_callees (node);
-		  ipa_remove_all_references (&node->symbol.ref_list);
-		}
-	    }
-	  else
+	  cgraph_remove_node (node);
+	  changed = true;
+	}
+      else if (!pointer_set_contains (reachable, node))
+        {
+	  if (node->analyzed)
 	    {
-	      cgraph_remove_node (node);
+	      if (file)
+		fprintf (file, " %s", cgraph_node_name (node));
+	      cgraph_node_remove_callees (node);
+	      ipa_remove_all_references (&node->symbol.ref_list);
 	      changed = true;
 	    }
+	  if (!pointer_set_contains (body_needed_for_clonning, node->symbol.decl))
+	    cgraph_release_function_body (node);
+	  node->analyzed = false;
 	}
     }
+
+  /* Inline clones might be kept around so their materializing allows further
+     cloning.  If the function the clone is inlined into is removed, we need
+     to turn it into normal cone.  */
   FOR_EACH_FUNCTION (node)
     {
-      /* Inline clones might be kept around so their materializing allows further
-         cloning.  If the function the clone is inlined into is removed, we need
-         to turn it into normal cone.  */
       if (node->global.inlined_to
 	  && !node->callers)
 	{
@@ -402,25 +367,38 @@  cgraph_remove_unreachable_nodes (bool be
       node->symbol.aux = NULL;
     }
 
+  /* Remove unreachable variables.  */
   if (file)
-    fprintf (file, "\n");
-
-  if (file)
-    fprintf (file, "Reclaiming variables:");
+    fprintf (file, "\nReclaiming variables:");
   for (vnode = varpool_first_variable (); vnode; vnode = vnext)
     {
       vnext = varpool_next_variable (vnode);
-      if (!pointer_set_contains (reachable, vnode))
-        {
+      if (!vnode->symbol.aux)
+	{
 	  if (file)
 	    fprintf (file, " %s", varpool_node_name (vnode));
 	  varpool_remove_node (vnode);
 	  changed = true;
 	}
+      else if (!pointer_set_contains (reachable, vnode))
+        {
+	  if (vnode->analyzed)
+	    {
+	      if (file)
+		fprintf (file, " %s", varpool_node_name (vnode));
+	      changed = true;
+	    }
+	  vnode->analyzed = false;
+	  vnode->symbol.aux = NULL;
+	}
+      else
+	vnode->symbol.aux = NULL;
     }
 
-  /* Now update address_taken flags and try to promote functions to be local.  */
+  pointer_set_destroy (reachable);
+  pointer_set_destroy (body_needed_for_clonning);
 
+  /* Now update address_taken flags and try to promote functions to be local.  */
   if (file)
     fprintf (file, "\nClearing address taken flags:");
   FOR_EACH_DEFINED_FUNCTION (node)
@@ -444,18 +422,18 @@  cgraph_remove_unreachable_nodes (bool be
   if (file)
     fprintf (file, "\n");
 
-  /* Rest of transformations are undesirable at -O0.  */
-  if (!optimize)
-    return changed;
-
 #ifdef ENABLE_CHECKING
   verify_symtab ();
 #endif
 
+  /* If we removed something, perhaps profile could be improved.  */
+  if (changed && optimize && inline_edge_summary_vec)
+    FOR_EACH_DEFINED_FUNCTION (node)
+      cgraph_propagate_frequency (node);
+
   /* Reclaim alias pairs for functions that have disappeared from the
      call graph.  */
   remove_unreachable_alias_pairs ();
-  pointer_set_destroy (reachable);
 
   return changed;
 }
Index: passes.c
===================================================================
--- passes.c	(revision 187335)
+++ passes.c	(working copy)
@@ -1865,7 +1865,7 @@  execute_todo (unsigned int flags)
   if (flags & TODO_remove_functions)
     {
       gcc_assert (!cfun);
-      cgraph_remove_unreachable_nodes (true, dump_file);
+      symtab_remove_unreachable_nodes (true, dump_file);
     }
 
   if ((flags & TODO_dump_symtab) && dump_file && !current_function_decl)
@@ -2150,7 +2150,7 @@  execute_one_pass (struct opt_pass *pass)
       bool applied = false;
       do_per_function (apply_ipa_transforms, (void *)&applied);
       if (applied)
-        cgraph_remove_unreachable_nodes (true, dump_file);
+        symtab_remove_unreachable_nodes (true, dump_file);
       /* Restore current_pass.  */
       current_pass = pass;
     }