diff mbox series

[committed] analyzer: consolidate call_string instances

Message ID 20220624174932.2654197-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer: consolidate call_string instances | expand

Commit Message

David Malcolm June 24, 2022, 5:49 p.m. UTC
ana::call_string is a wrapper around an auto_vec of callsites, leading
to non-trivial copying when copying around call_string instances, e.g.
in ana::program_point.

This patch consolidates call_string instances within the
region_model_manager: it now owns the root/empty call_string, and
each call_string instance tracks its children, lazily creating them on
demand, so that the call_string instances form a tree-like hierarchy in
memory.  Doing this requires passing the region_model_manager to the
various program_point factory methods, so that they can get at the root
call_string.

Instances of call_string become immutable (apart from their internal
cache for looking up their children); operations that previously
modified them now return the call_string for the result of the
operation.

I wasn't able to observe any performance impact of this, but it
simplifies call_string and program_point management, and thus I hope
will make it easier to improve call summarization.  In particular,
region_model_manager::log_stats will now print a hierarchical dump of
all the call_string instances used in the analysis (in -fdump-analyzer
and -fdump-analyzer-stderr).

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r13-1250-gbb8e93eb1acae3.

gcc/analyzer/ChangeLog:
	* call-string.cc: Add includes of "analyzer/analyzer.h"
	and "analyzer/analyzer-logging.h".
	(call_string::call_string): Delete copy ctor.
	(call_string::operator=): Delete.
	(call_string::operator==): Delete.
	(call_string::hash): Delete.
	(call_string::push_call): Make const, returning the resulting
	call_string.
	(call_string::pop): Delete.
	(call_string::cmp_ptr_ptr): New.
	(call_string::validate): Assert that m_parent is non-NULL, or
	m_elements is empty.
	(call_string::call_string): Move default ctor here from
	call-string.h and reimplement.  Add ctor taking a parent
	and an element.
	(call_string::~call_string): New.
	(call_string::recursive_log): New.
	* call-string.h (call_string::call_string): Move default ctor's
	defn to call-string.cc.  Delete copy ctor.  Add ctor taking a
	parent and an element.
	(call_string::operator=): Delete.
	(call_string::operator==): Delete.
	(call_string::hash): Delete.
	(call_string::push_call): Make const, returning the resulting
	call_string.
	(call_string::pop): Delete decl.
	(call_string::get_parent): New.
	(call_string::cmp_ptr_ptr): New decl.
	(call_string::get_top_of_stack): New.
	(struct call_string::hashmap_traits_t): New.
	(class call_string): Add friend class region_model_manager.  Add
	DISABLE_COPY_AND_ASSIGN.
	(call_string::~call_string): New decl.
	(call_string::recursive_log): New decl.
	(call_string::m_parent): New field.
	(call_string::m_children): New field.
	* constraint-manager.cc (selftest::test_many_constants): Pass
	model manager to program_point::origin.
	* engine.cc (exploded_graph::exploded_graph): Likewise.
	(exploded_graph::add_function_entry): Likewise for
	program_point::from_function_entry.
	(add_tainted_args_callback): Likewise.
	(exploded_graph::maybe_process_run_of_before_supernode_enodes):
	Update for change to program_point.get_call_string.
	(exploded_graph::process_node): Likewise.
	(class function_call_string_cluster): Convert m_cs from a
	call_string to a const call_string &.
	(struct function_call_string): Likewise.
	(pod_hash_traits<function_call_string>::hash): Use pointer_hash
	for m_cs.
	(pod_hash_traits<function_call_string>::equal): Update for change
	to m_cs.
	(root_cluster::add_node): Update for change to
	function_call_string.
	(viz_callgraph_node::dump_dot): Update for change to call_string.
	* exploded-graph.h (per_call_string_data::m_key): Convert to a
	reference.
	(struct eg_call_string_hash_map_traits): Delete.
	(exploded_graph::call_string_data_map_t): Remove traits class.
	* program-point.cc: Move include of "analyzer/call-string.h" to
	after "analyzer/analyzer-logging.h".
	(program_point::print): Update for conversion of m_call_string to
	a pointer.
	(program_point::to_json): Likewise.
	(program_point::push_to_call_stack): Update for immutability of
	call strings.
	(program_point::pop_from_call_stack): Likewise.
	(program_point::hash): Use pointer hashing for m_call_string.
	(program_point::get_function_at_depth): Update for change to
	m_call_string.
	(program_point::validate): Update for changes to call_string.
	(program_point::on_edge): Likewise.
	(program_point::origin): Move here from call-string.h.  Add
	region_model_manager param and use it to get empty call string.
	(program_point::from_function_entry): Likewise.
	(selftest::test_function_point_ordering): Likewise.
	(selftest::test_function_point_ordering): Likewise.
	* program-point.h (program_point::program_point): Update for
	change to m_call_string.
	(program_point::get_call_string): Likewise.
	(program_point::get_stack_depth): Likewise.
	(program_point::origin): Add region_model_manager param, and move
	defn to call-string.cc.
	(program_point::from_function_entry): Likewise.
	(program_point::empty): Drop call_string.
	(program_point::deleted): Likewise.
	(program_point::program_point): New private ctor.
	(program_point::m_call_string): Convert from call_string to const
	call_string *.
	* program-state.cc (selftest::test_program_state_merging): Update
	for call_string changes.
	(selftest::test_program_state_merging_2): Likewise.
	* region-model-manager.cc
	(region_model_manager::region_model_manager): Construct
	m_empty_call_string.
	(region_model_manager::log_stats): Log the call strings.
	* region-model.cc (assert_region_models_merge): Pass the
	region_model_manager when creating program_point instances.
	(selftest::test_state_merging): Likewise.
	(selftest::test_constraint_merging): Likewise.
	(selftest::test_widening_constraints): Likewise.
	(selftest::test_iteration_1): Likewise.
	* region-model.h (region_model_manager::get_empty_call_string):
	New.
	(region_model_manager::m_empty_call_string): New.
	* sm-signal.cc (register_signal_handler::impl_transition): Update
	for changes to call_string.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/call-string.cc          | 160 ++++++++++++++++-----------
 gcc/analyzer/call-string.h           |  90 ++++++++++++---
 gcc/analyzer/constraint-manager.cc   |   4 +-
 gcc/analyzer/engine.cc               |  39 +++----
 gcc/analyzer/exploded-graph.h        |  61 +---------
 gcc/analyzer/program-point.cc        |  63 +++++++----
 gcc/analyzer/program-point.h         |  35 +++---
 gcc/analyzer/program-state.cc        |  11 +-
 gcc/analyzer/region-model-manager.cc |   3 +
 gcc/analyzer/region-model.cc         |  16 +--
 gcc/analyzer/region-model.h          |   8 ++
 gcc/analyzer/sm-signal.cc            |   6 +-
 12 files changed, 282 insertions(+), 214 deletions(-)
diff mbox series

Patch

diff --git a/gcc/analyzer/call-string.cc b/gcc/analyzer/call-string.cc
index 2ccd3ccd6fb..a09f569d9d1 100644
--- a/gcc/analyzer/call-string.cc
+++ b/gcc/analyzer/call-string.cc
@@ -25,7 +25,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree.h"
 #include "options.h"
 #include "json.h"
-#include "analyzer/call-string.h"
 #include "ordered-hash-map.h"
 #include "options.h"
 #include "cgraph.h"
@@ -35,6 +34,9 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "gimple-iterator.h"
 #include "digraph.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/call-string.h"
 #include "analyzer/supergraph.h"
 
 #if ENABLE_ANALYZER
@@ -74,45 +76,6 @@  call_string::element_t::get_callee_function () const
   return m_callee->get_function ();
 }
 
-/* call_string's copy ctor.  */
-
-call_string::call_string (const call_string &other)
-: m_elements (other.m_elements.length ())
-{
-  for (const call_string::element_t &e : other.m_elements)
-    m_elements.quick_push (e);
-}
-
-/* call_string's assignment operator.  */
-
-call_string&
-call_string::operator= (const call_string &other)
-{
-  // would be much simpler if we could rely on vec<> assignment op
-  m_elements.truncate (0);
-  m_elements.reserve (other.m_elements.length (), true);
-  call_string::element_t *e;
-  int i;
-  FOR_EACH_VEC_ELT (other.m_elements, i, e)
-    m_elements.quick_push (*e);
-  return *this;
-}
-
-/* call_string's equality operator.  */
-
-bool
-call_string::operator== (const call_string &other) const
-{
-  if (m_elements.length () != other.m_elements.length ())
-    return false;
-  call_string::element_t *e;
-  int i;
-  FOR_EACH_VEC_ELT (m_elements, i, e)
-    if (*e != other.m_elements[i])
-      return false;
-  return true;
-}
-
 /* Print this to PP.  */
 
 void
@@ -160,43 +123,34 @@  call_string::to_json () const
   return arr;
 }
 
-/* Generate a hash value for this call_string.  */
+/* Get or create the call_string resulting from pushing the return
+   superedge for CALL_SEDGE onto the end of this call_string.  */
 
-hashval_t
-call_string::hash () const
-{
-  inchash::hash hstate;
-  for (const call_string::element_t &e : m_elements)
-    hstate.add_ptr (e.m_caller);
-  return hstate.end ();
-}
-
-/* Push the return superedge for CALL_SEDGE onto the end of this
-   call_string.  */
-
-void
+const call_string *
 call_string::push_call (const supergraph &sg,
-			const call_superedge *call_sedge)
+			const call_superedge *call_sedge) const
 {
   gcc_assert (call_sedge);
   const return_superedge *return_sedge = call_sedge->get_edge_for_return (sg);
   gcc_assert (return_sedge);
-  call_string::element_t e (return_sedge->m_dest, return_sedge->m_src);
-  m_elements.safe_push (e);
+  return push_call (return_sedge->m_dest, return_sedge->m_src);
 }
 
-void
+/* Get or create the call_string resulting from pushing the call
+   (caller, callee) onto the end of this call_string.  */
+
+const call_string *
 call_string::push_call (const supernode *caller,
-			const supernode *callee)
+			const supernode *callee) const
 {
   call_string::element_t e (caller, callee);
-  m_elements.safe_push (e);
-}
 
-call_string::element_t
-call_string::pop ()
-{
-  return m_elements.pop();
+  if (const call_string **slot = m_children.get (e))
+    return *slot;
+
+  call_string *result = new call_string (*this, e);
+  m_children.put (e, result);
+  return result;
 }
 
 /* Count the number of times the top-most call site appears in the
@@ -260,6 +214,16 @@  call_string::cmp (const call_string &a,
     }
 }
 
+/* Comparator for use by vec<const call_string *>::qsort.  */
+
+int
+call_string::cmp_ptr_ptr (const void *pa, const void *pb)
+{
+  const call_string *cs_a = *static_cast <const call_string * const *> (pa);
+  const call_string *cs_b = *static_cast <const call_string * const *> (pb);
+  return cmp (*cs_a, *cs_b);
+}
+
 /* Return the pointer to callee of the topmost call in the stack,
    or NULL if stack is empty.  */
 const supernode *
@@ -290,6 +254,8 @@  call_string::validate () const
   return;
 #endif
 
+  gcc_assert (m_parent || m_elements.length () == 0);
+
   /* Each entry's "caller" should be the "callee" of the previous entry.  */
   call_string::element_t *e;
   int i;
@@ -299,4 +265,68 @@  call_string::validate () const
 		  m_elements[i - 1].get_callee_function ());
 }
 
+/* ctor for the root/empty call_string.  */
+
+call_string::call_string ()
+: m_parent (NULL), m_elements ()
+{
+}
+
+/* ctor for a child call_string.  */
+
+call_string::call_string (const call_string &parent, const element_t &to_push)
+: m_parent (&parent),
+  m_elements (parent.m_elements.length () + 1)
+{
+  m_elements.splice (parent.m_elements);
+  m_elements.quick_push (to_push);
+}
+
+/* dtor for call_string: recursively delete children.  */
+
+call_string::~call_string ()
+{
+  for (auto child_iter : m_children)
+    delete child_iter.second;
+}
+
+/* Log this call_string and all its descendents recursively to LOGGER,
+   using indentation and elision to highlight the hierarchy.  */
+
+void
+call_string::recursive_log (logger *logger) const
+{
+  logger->start_log_line ();
+  pretty_printer *pp = logger->get_printer ();
+  for (unsigned i = 0; i < length (); i++)
+    pp_string (pp, "  ");
+  if (length () > 0)
+    {
+      pp_string (pp, "[");
+      /* Elide all but the final element, since they are shared with
+	 the parent call_string.  */
+      for (unsigned i = 0; i < length (); i++)
+	pp_string (pp, "..., ");
+      /* Log the final element in detail.  */
+      const element_t *e = &m_elements[m_elements.length () - 1];
+      pp_printf (pp, "(SN: %i -> SN: %i in %s)]",
+		 e->m_callee->m_index, e->m_caller->m_index,
+		 function_name (e->m_caller->m_fun));
+    }
+  else
+    pp_string (pp, "[]");
+  logger->end_log_line ();
+
+  /* Recurse into children.  */
+  {
+    auto_vec<const call_string *> children (m_children.elements ());
+    for (auto iter : m_children)
+      children.safe_push (iter.second);
+    children.qsort (call_string::cmp_ptr_ptr);
+
+    for (auto iter : children)
+      iter->recursive_log (logger);
+  }
+}
+
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/call-string.h b/gcc/analyzer/call-string.h
index 87d04a1c4cc..c3cea903277 100644
--- a/gcc/analyzer/call-string.h
+++ b/gcc/analyzer/call-string.h
@@ -36,8 +36,13 @@  class return_superedge;
    i.e. that we return to the same callsite that called us.
 
    The class stores returning calls ( which may be represented by a
-   returning superedge ). We do so because this is what we need to compare 
-   against.  */
+   returning superedge ). We do so because this is what we need to compare
+   against.
+
+   Instances of call_string are consolidated by the region_model_manager,
+   which effectively owns them: it owns the root/empty call_string, and each
+   call_string instance tracks its children, lazily creating them on demand,
+   so that the call_string instances form a tree-like hierarchy in memory.  */
 
 class call_string
 {
@@ -60,38 +65,31 @@  public:
     /* Accessors */
     function *get_caller_function () const;
     function *get_callee_function () const;
-    
+
     const supernode *m_caller;
     const supernode *m_callee;
   };
 
-  call_string () : m_elements () {}
-  call_string (const call_string &other);
-  call_string& operator= (const call_string &other);
-
-  bool operator== (const call_string &other) const;
-
   void print (pretty_printer *pp) const;
 
   json::value *to_json () const;
 
-  hashval_t hash () const;
-
   bool empty_p () const { return m_elements.is_empty (); }
 
-  void push_call (const supergraph &sg,
-		  const call_superedge *sedge);
-
-  void push_call (const supernode *src, 
-    const supernode *dest);
+  const call_string *push_call (const supergraph &sg,
+				const call_superedge *sedge) const;
 
-  element_t pop ();
+  const call_string *push_call (const supernode *src,
+				const supernode *dest) const;
+  const call_string *get_parent () const { return m_parent; }
 
   int calc_recursion_depth () const;
 
   static int cmp (const call_string &a,
 		  const call_string &b);
 
+  static int cmp_ptr_ptr (const void *, const void *);
+
   /* Accessors */
 
   const supernode *get_callee_node () const;
@@ -101,11 +99,69 @@  public:
   {
     return m_elements[idx];
   }
+  const element_t &get_top_of_stack () const
+  {
+    gcc_assert (m_elements.length () > 0);
+    return m_elements[m_elements.length () - 1];
+  }
 
   void validate () const;
 
 private:
+  struct hashmap_traits_t
+  {
+    typedef element_t key_type;
+    typedef const call_string *value_type;
+
+    static const bool maybe_mx = false;
+    static inline hashval_t hash (const key_type &k)
+    {
+      inchash::hash hstate;
+      hstate.add_ptr (k.m_caller);
+      hstate.add_ptr (k.m_callee);
+      return hstate.end ();
+    }
+    static inline bool equal_keys (const key_type &k1, const key_type &k2)
+    {
+      return k1 == k2;
+    }
+    template <typename T> static inline void remove (T &entry)
+    {
+      entry.m_key = element_t (NULL, NULL);
+    }
+    static const bool empty_zero_p = true;
+    template <typename T> static inline bool is_empty (const T &entry)
+    {
+      return entry.m_key.m_caller == NULL;
+    }
+    template <typename T> static inline bool is_deleted (const T &entry)
+    {
+      return entry.m_key.m_caller == reinterpret_cast<const supernode *> (1);
+    }
+    template <typename T> static inline void mark_empty (T &entry)
+    {
+      entry.m_key = element_t (NULL, NULL);
+      entry.m_value = NULL;
+    }
+    template <typename T> static inline void mark_deleted (T &entry)
+    {
+      entry.m_key.m_caller = reinterpret_cast<const supernode *> (1);
+    }
+  };
+
+  friend class region_model_manager;
+
+  DISABLE_COPY_AND_ASSIGN (call_string);
+
+  call_string ();
+  call_string (const call_string &parent, const element_t &to_push);
+  ~call_string ();
+
+  void recursive_log (logger *logger) const;
+
+  const call_string *m_parent;
   auto_vec<element_t> m_elements;
+  mutable hash_map<element_t, const call_string *, hashmap_traits_t> m_children;
 };
 
 } // namespace ana
diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index 02e8ce9a457..4133a134778 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -3923,10 +3923,10 @@  test_equality ()
 static void
 test_many_constants ()
 {
-  program_point point (program_point::origin ());
+  region_model_manager mgr;
+  program_point point (program_point::origin (mgr));
   tree a = build_global_decl ("a", integer_type_node);
 
-  region_model_manager mgr;
   region_model model (&mgr);
   auto_vec<tree> constants;
   for (int i = 0; i < 20; i++)
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 51dfe298230..0674c8ba3b6 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -2374,8 +2374,9 @@  exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
   m_functionless_stats (m_sg.num_nodes ()),
   m_PK_AFTER_SUPERNODE_per_snode (m_sg.num_nodes ())
 {
-  m_origin = get_or_create_node (program_point::origin (),
-				 program_state (ext_state), NULL);
+  m_origin = get_or_create_node
+    (program_point::origin (*ext_state.get_model_manager ()),
+     program_state (ext_state), NULL);
   for (int i = 0; i < m_sg.num_nodes (); i++)
     m_PK_AFTER_SUPERNODE_per_snode.quick_push (i);
 }
@@ -2526,7 +2527,9 @@  exploded_graph::add_function_entry (function *fun)
       return NULL;
     }
 
-  program_point point = program_point::from_function_entry (m_sg, fun);
+  program_point point
+    = program_point::from_function_entry (*m_ext_state.get_model_manager (),
+					  m_sg, fun);
   program_state state (m_ext_state);
   state.push_frame (m_ext_state, fun);
 
@@ -2979,7 +2982,8 @@  add_tainted_args_callback (exploded_graph *eg, tree field, tree fndecl,
   gcc_assert (fun);
 
   program_point point
-    = program_point::from_function_entry (eg->get_supergraph (), fun);
+    = program_point::from_function_entry (*ext_state.get_model_manager (),
+					  eg->get_supergraph (), fun);
   program_state state (ext_state);
   state.push_frame (ext_state, fun);
 
@@ -3341,7 +3345,7 @@  maybe_process_run_of_before_supernode_enodes (exploded_node *enode)
 
       if (point_2.get_kind () == PK_BEFORE_SUPERNODE
 	  && point_2.get_supernode () == snode
-	  && point_2.get_call_string () == point.get_call_string ())
+	  && &point_2.get_call_string () == &point.get_call_string ())
 	{
 	  enodes.safe_push (enode_2);
 	  m_worklist.take_next ();
@@ -4048,7 +4052,7 @@  exploded_graph::process_node (exploded_node *node)
 	if ((is_an_exit_block && !found_a_superedge)
 	    && (!point.get_call_string ().empty_p ()))
 	  {
-	    const call_string cs = point.get_call_string ();
+	    const call_string &cs = point.get_call_string ();
 	    program_point next_point
 	      = program_point::before_supernode (cs.get_caller_node (),
 						 NULL,
@@ -4736,7 +4740,7 @@  private:
 class function_call_string_cluster : public exploded_cluster
 {
 public:
-  function_call_string_cluster (function *fun, call_string cs)
+  function_call_string_cluster (function *fun, const call_string &cs)
   : m_fun (fun), m_cs (cs) {}
 
   ~function_call_string_cluster ()
@@ -4811,7 +4815,7 @@  public:
 
 private:
   function *m_fun;
-  call_string m_cs;
+  const call_string &m_cs;
   typedef ordered_hash_map<const supernode *, supernode_cluster *> map_t;
   map_t m_map;
 };
@@ -4820,14 +4824,15 @@  private:
 
 struct function_call_string
 {
-  function_call_string (function *fun, call_string cs)
+  function_call_string (function *fun, const call_string *cs)
   : m_fun (fun), m_cs (cs)
   {
     gcc_assert (fun);
+    gcc_assert (cs);
   }
 
   function *m_fun;
-  call_string m_cs;
+  const call_string *m_cs;
 };
 
 } // namespace ana
@@ -4842,7 +4847,8 @@  template <>
 inline hashval_t
 pod_hash_traits<function_call_string>::hash (value_type v)
 {
-  return pointer_hash <function>::hash (v.m_fun) ^ v.m_cs.hash ();
+  return (pointer_hash <function>::hash (v.m_fun)
+	  ^ pointer_hash <const call_string>::hash (v.m_cs));
 }
 
 template <>
@@ -4850,7 +4856,7 @@  inline bool
 pod_hash_traits<function_call_string>::equal (const value_type &existing,
 					      const value_type &candidate)
 {
-  return existing.m_fun == candidate.m_fun && existing.m_cs == candidate.m_cs;
+  return existing.m_fun == candidate.m_fun && &existing.m_cs == &candidate.m_cs;
 }
 template <>
 inline void
@@ -4925,7 +4931,7 @@  public:
       }
 
     const call_string &cs = en->get_point ().get_call_string ();
-    function_call_string key (fun, cs);
+    function_call_string key (fun, &cs);
     function_call_string_cluster **slot = m_map.get (key);
     if (slot)
       (*slot)->add_node (en);
@@ -4939,11 +4945,6 @@  public:
   }
 
 private:
-  /* This can't be an ordered_hash_map, as we can't store vec<call_string>,
-     since it's not a POD; vec<>::quick_push has:
-       *slot = obj;
-     and the slot isn't initialized, so the assignment op dies when cleaning up
-     un-inited *slot (within the truncate call).  */
   typedef hash_map<function_call_string, function_call_string_cluster *> map_t;
   map_t m_map;
 
@@ -5319,7 +5320,7 @@  public:
 	    FOR_EACH_VEC_ELT (args.m_eg->m_nodes, i, enode)
 	      {
 		if (enode->get_point ().get_function () == m_fun
-		    && enode->get_point ().get_call_string () == *cs)
+		    && &enode->get_point ().get_call_string () == cs)
 		  num_enodes++;
 	      }
 	    if (num_enodes > 0)
diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h
index 101f4f9a0a0..0613f558b8b 100644
--- a/gcc/analyzer/exploded-graph.h
+++ b/gcc/analyzer/exploded-graph.h
@@ -599,65 +599,10 @@  struct per_call_string_data
   : m_key (key), m_stats (num_supernodes)
   {}
 
-  const call_string m_key;
+  const call_string &m_key;
   stats m_stats;
 };
 
-/* Traits class for storing per-call_string data within
-   an exploded_graph.  */
-
-struct eg_call_string_hash_map_traits
-{
-  typedef const call_string *key_type;
-  typedef per_call_string_data *value_type;
-  typedef per_call_string_data *compare_type;
-
-  static inline hashval_t hash (const key_type &k)
-  {
-    gcc_assert (k != NULL);
-    gcc_assert (k != reinterpret_cast<key_type> (1));
-    return k->hash ();
-  }
-  static inline bool equal_keys (const key_type &k1, const key_type &k2)
-  {
-    gcc_assert (k1 != NULL);
-    gcc_assert (k2 != NULL);
-    gcc_assert (k1 != reinterpret_cast<key_type> (1));
-    gcc_assert (k2 != reinterpret_cast<key_type> (1));
-    if (k1 && k2)
-      return *k1 == *k2;
-    else
-      /* Otherwise they must both be non-NULL.  */
-      return k1 == k2;
-  }
-  template <typename T>
-  static inline void remove (T &)
-  {
-    /* empty; the nodes are handled elsewhere.  */
-  }
-  template <typename T>
-  static inline void mark_deleted (T &entry)
-  {
-    entry.m_key = reinterpret_cast<key_type> (1);
-  }
-  template <typename T>
-  static inline void mark_empty (T &entry)
-  {
-    entry.m_key = NULL;
-  }
-  template <typename T>
-  static inline bool is_deleted (const T &entry)
-  {
-    return entry.m_key == reinterpret_cast<key_type> (1);
-  }
-  template <typename T>
-  static inline bool is_empty (const T &entry)
-  {
-    return entry.m_key == NULL;
-  }
-  static const bool empty_zero_p = false;
-};
-
 /* Data about a particular function within an exploded_graph.  */
 
 struct per_function_data
@@ -791,8 +736,8 @@  private:
 class exploded_graph : public digraph<eg_traits>
 {
 public:
-  typedef hash_map <const call_string *, per_call_string_data *,
-		    eg_call_string_hash_map_traits> call_string_data_map_t;
+  typedef hash_map <const call_string *,
+		    per_call_string_data *> call_string_data_map_t;
 
   exploded_graph (const supergraph &sg, logger *logger,
 		  const extrinsic_state &ext_state,
diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
index 8fa7066fea5..6c296d5ddc8 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -25,7 +25,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple-pretty-print.h"
 #include "gcc-rich-location.h"
 #include "json.h"
-#include "analyzer/call-string.h"
 #include "ordered-hash-map.h"
 #include "options.h"
 #include "cgraph.h"
@@ -37,6 +36,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "digraph.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
+#include "analyzer/call-string.h"
 #include "analyzer/supergraph.h"
 #include "analyzer/program-point.h"
 #include "sbitmap.h"
@@ -290,7 +290,7 @@  void
 program_point::print (pretty_printer *pp, const format &f) const
 {
   pp_string (pp, "callstring: ");
-  m_call_string.print (pp);
+  m_call_string->print (pp);
   f.spacer (pp);
 
   m_function_point.print (pp, f);
@@ -340,7 +340,7 @@  program_point::to_json () const
       break;
     }
 
-  point_obj->set ("call_string", m_call_string.to_json ());
+  point_obj->set ("call_string", m_call_string->to_json ());
 
   return point_obj;
 }
@@ -353,14 +353,15 @@  void
 program_point::push_to_call_stack (const supernode *caller,
 				   const supernode *callee)
 {
-  m_call_string.push_call (callee, caller);
+  m_call_string = m_call_string->push_call (callee, caller);
 }
 
 /* Pop the topmost call from the current callstack.  */
 void
 program_point::pop_from_call_stack ()
 {
-  m_call_string.pop ();
+  m_call_string = m_call_string->get_parent ();
+  gcc_assert (m_call_string);
 }
 
 /* Generate a hash value for this program_point.  */
@@ -370,7 +371,7 @@  program_point::hash () const
 {
   inchash::hash hstate;
   hstate.merge_hash (m_function_point.hash ());
-  hstate.merge_hash (m_call_string.hash ());
+  hstate.add_ptr (m_call_string);
   return hstate.end ();
 }
 
@@ -379,11 +380,11 @@  program_point::hash () const
 function *
 program_point::get_function_at_depth (unsigned depth) const
 {
-  gcc_assert (depth <= m_call_string.length ());
-  if (depth == m_call_string.length ())
+  gcc_assert (depth <= m_call_string->length ());
+  if (depth == m_call_string->length ())
     return m_function_point.get_function ();
   else
-    return m_call_string[depth].get_caller_function ();
+    return get_call_string ()[depth].get_caller_function ();
 }
 
 /* Assert that this object is sane.  */
@@ -396,12 +397,13 @@  program_point::validate () const
   return;
 #endif
 
-  m_call_string.validate ();
+  m_call_string->validate ();
   /* The "callee" of the final entry in the callstring should be the
      function of the m_function_point.  */
-  if (m_call_string.length () > 0)
-    gcc_assert (m_call_string[m_call_string.length () - 1].get_callee_function ()
-		== get_function ());
+  if (m_call_string->length () > 0)
+    gcc_assert
+      ((*m_call_string)[m_call_string->length () - 1].get_callee_function ()
+       == get_function ());
 }
 
 /* Check to see if SUCC is a valid edge to take (ensuring that we have
@@ -444,14 +446,15 @@  program_point::on_edge (exploded_graph &eg,
 	  }
 
 	/* Add the callsite to the call string.  */
-	m_call_string.push_call (eg.get_supergraph (), call_sedge);
+	m_call_string = m_call_string->push_call (eg.get_supergraph (),
+						  call_sedge);
 
 	/* Impose a maximum recursion depth and don't analyze paths
 	   that exceed it further.
 	   This is something of a blunt workaround, but it only
 	   applies to recursion (and mutual recursion), not to
 	   general call stacks.  */
-	if (m_call_string.calc_recursion_depth ()
+	if (m_call_string->calc_recursion_depth ()
 	    > param_analyzer_max_recursion_depth)
 	  {
 	    if (logger)
@@ -465,13 +468,15 @@  program_point::on_edge (exploded_graph &eg,
     case SUPEREDGE_RETURN:
       {
 	/* Require that we return to the call site in the call string.  */
-	if (m_call_string.empty_p ())
+	if (m_call_string->empty_p ())
 	  {
 	    if (logger)
 	      logger->log ("rejecting return edge: empty call string");
 	    return false;
 	  }
-	const call_string::element_t top_of_stack = m_call_string.pop ();
+	const call_string::element_t &top_of_stack
+	  = m_call_string->get_top_of_stack ();
+	m_call_string = m_call_string->get_parent ();
 	call_string::element_t current_call_string_element (succ->m_dest,
 							    succ->m_src);
 	if (top_of_stack != current_call_string_element)
@@ -669,6 +674,25 @@  function_point::get_next () const
     }
 }
 
+/* class program_point.  */
+
+program_point
+program_point::origin (const region_model_manager &mgr)
+{
+  return program_point (function_point (NULL, NULL,
+					0, PK_ORIGIN),
+			mgr.get_empty_call_string ());
+}
+
+program_point
+program_point::from_function_entry (const region_model_manager &mgr,
+				    const supergraph &sg,
+				    function *fun)
+{
+  return program_point (function_point::from_function_entry (sg, fun),
+			mgr.get_empty_call_string ());
+}
+
 /* For those program points for which there is a uniquely-defined
    successor, return it.  */
 
@@ -721,7 +745,6 @@  static void
 test_function_point_ordering ()
 {
   const supernode *snode = NULL;
-  const call_string call_string;
 
   /* Populate an array with various points within the same
      snode, in order.  */
@@ -756,9 +779,11 @@  test_function_point_ordering ()
 static void
 test_program_point_equality ()
 {
+  region_model_manager mgr;
+
   const supernode *snode = NULL;
 
-  const call_string cs;
+  const call_string &cs = mgr.get_empty_call_string ();
 
   program_point a = program_point::before_supernode (snode, NULL,
 						     cs);
diff --git a/gcc/analyzer/program-point.h b/gcc/analyzer/program-point.h
index 6084c9e3004..63f72246f69 100644
--- a/gcc/analyzer/program-point.h
+++ b/gcc/analyzer/program-point.h
@@ -174,7 +174,7 @@  public:
   program_point (const function_point &fn_point,
 		 const call_string &call_string)
   : m_function_point (fn_point),
-    m_call_string (call_string)
+    m_call_string (&call_string)
   {
   }
 
@@ -197,7 +197,7 @@  public:
   /* Accessors.  */
 
   const function_point &get_function_point () const { return m_function_point; }
-  const call_string &get_call_string () const { return m_call_string; }
+  const call_string &get_call_string () const { return *m_call_string; }
 
   const supernode *get_supernode () const
   {
@@ -242,23 +242,14 @@  public:
   {
     if (get_kind () == PK_ORIGIN)
       return 0;
-    return m_call_string.length () + 1;
+    return get_call_string ().length () + 1;
   }
 
   /* Factory functions for making various kinds of program_point.  */
-  static program_point origin ()
-  {
-    return program_point (function_point (NULL, NULL,
-					  0, PK_ORIGIN),
-			  call_string ());
-  }
-
-  static program_point from_function_entry (const supergraph &sg,
-					    function *fun)
-  {
-    return program_point (function_point::from_function_entry (sg, fun),
-			  call_string ());
-  }
+  static program_point origin (const region_model_manager &mgr);
+  static program_point from_function_entry (const region_model_manager &mgr,
+					    const supergraph &sg,
+					    function *fun);
 
   static program_point before_supernode (const supernode *supernode,
 					 const superedge *from_edge,
@@ -288,11 +279,11 @@  public:
 
   static program_point empty ()
   {
-    return program_point (function_point::empty (), call_string ());
+    return program_point (function_point::empty ());
   }
   static program_point deleted ()
   {
-    return program_point (function_point::deleted (), call_string ());
+    return program_point (function_point::deleted ());
   }
 
   bool on_edge (exploded_graph &eg, const superedge *succ);
@@ -306,8 +297,14 @@  public:
   program_point get_next () const;
 
  private:
+  program_point (const function_point &fn_point)
+  : m_function_point (fn_point),
+    m_call_string (NULL)
+  {
+  }
+
   function_point m_function_point;
-  call_string m_call_string;
+  const call_string *m_call_string;
 };
 
 } // namespace ana
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 7ad581c7fbd..295c6aeb185 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1668,12 +1668,12 @@  test_program_state_merging ()
      malloc sm-state, pointing to a region on the heap.  */
   tree p = build_global_decl ("p", ptr_type_node);
 
-  program_point point (program_point::origin ());
+  engine eng;
+  region_model_manager *mgr = eng.get_model_manager ();
+  program_point point (program_point::origin (*mgr));
   auto_delete_vec <state_machine> checkers;
   checkers.safe_push (make_malloc_state_machine (NULL));
-  engine eng;
   extrinsic_state ext_state (checkers, &eng);
-  region_model_manager *mgr = eng.get_model_manager ();
 
   program_state s0 (ext_state);
   uncertainty_t uncertainty;
@@ -1736,10 +1736,11 @@  test_program_state_merging ()
 static void
 test_program_state_merging_2 ()
 {
-  program_point point (program_point::origin ());
+  engine eng;
+  region_model_manager *mgr = eng.get_model_manager ();
+  program_point point (program_point::origin (*mgr));
   auto_delete_vec <state_machine> checkers;
   checkers.safe_push (make_signal_state_machine (NULL));
-  engine eng;
   extrinsic_state ext_state (checkers, &eng);
 
   const state_machine::state test_state_0 ("test state 0", 0);
diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc
index 3377f15feac..17713b07c30 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -68,6 +68,7 @@  namespace ana {
 
 region_model_manager::region_model_manager (logger *logger)
 : m_logger (logger),
+  m_empty_call_string (),
   m_next_region_id (0),
   m_root_region (alloc_region_id ()),
   m_stack_region (alloc_region_id (), &m_root_region),
@@ -1748,6 +1749,8 @@  void
 region_model_manager::log_stats (logger *logger, bool show_objs) const
 {
   LOG_SCOPE (logger);
+  logger->log ("call string consolidation");
+  m_empty_call_string.recursive_log (logger);
   logger->log ("svalue consolidation");
   log_uniq_map (logger, show_objs, "constant_svalue", m_constants_map);
   log_uniq_map (logger, show_objs, "unknown_svalue", m_unknowns_map);
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 6b49719d521..5bd5dc720e9 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -5460,9 +5460,9 @@  assert_region_models_merge (tree expr, tree val_a, tree val_b,
 			     region_model *out_merged_model,
 			     const svalue **out_merged_svalue)
 {
-  program_point point (program_point::origin ());
-  test_region_model_context ctxt;
   region_model_manager *mgr = out_merged_model->get_manager ();
+  program_point point (program_point::origin (*mgr));
+  test_region_model_context ctxt;
   region_model model0 (mgr);
   region_model model1 (mgr);
   if (val_a)
@@ -5511,8 +5511,8 @@  test_state_merging ()
 		       ptr_type_node);
   DECL_CONTEXT (q) = test_fndecl;
 
-  program_point point (program_point::origin ());
   region_model_manager mgr;
+  program_point point (program_point::origin (mgr));
 
   {
     region_model model0 (&mgr);
@@ -5852,7 +5852,7 @@  test_constraint_merging ()
 
   /* They should be mergeable; the merged constraints should
      be: (0 <= x < n).  */
-  program_point point (program_point::origin ());
+  program_point point (program_point::origin (mgr));
   region_model merged (&mgr);
   ASSERT_TRUE (model0.can_merge_with_p (model1, point, &merged));
 
@@ -5873,12 +5873,12 @@  test_constraint_merging ()
 static void
 test_widening_constraints ()
 {
-  program_point point (program_point::origin ());
+  region_model_manager mgr;
+  program_point point (program_point::origin (mgr));
   tree int_0 = build_int_cst (integer_type_node, 0);
   tree int_m1 = build_int_cst (integer_type_node, -1);
   tree int_1 = build_int_cst (integer_type_node, 1);
   tree int_256 = build_int_cst (integer_type_node, 256);
-  region_model_manager mgr;
   test_region_model_context ctxt;
   const svalue *int_0_sval = mgr.get_or_create_constant_svalue (int_0);
   const svalue *int_1_sval = mgr.get_or_create_constant_svalue (int_1);
@@ -5988,7 +5988,8 @@  test_widening_constraints ()
 static void
 test_iteration_1 ()
 {
-  program_point point (program_point::origin ());
+  region_model_manager mgr;
+  program_point point (program_point::origin (mgr));
 
   tree int_0 = build_int_cst (integer_type_node, 0);
   tree int_1 = build_int_cst (integer_type_node, 1);
@@ -5996,7 +5997,6 @@  test_iteration_1 ()
   tree int_257 = build_int_cst (integer_type_node, 257);
   tree i = build_global_decl ("i", integer_type_node);
 
-  region_model_manager mgr;
   test_region_model_context ctxt;
 
   /* model0: i: 0.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 1bfa56a8cd2..129aad2945f 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -244,6 +244,12 @@  public:
   region_model_manager (logger *logger = NULL);
   ~region_model_manager ();
 
+  /* call_string consolidation.  */
+  const call_string &get_empty_call_string () const
+  {
+    return m_empty_call_string;
+  }
+
   /* svalue consolidation.  */
   const svalue *get_or_create_constant_svalue (tree cst_expr);
   const svalue *get_or_create_int_cst (tree type, poly_int64);
@@ -381,6 +387,8 @@  private:
 
   logger *m_logger;
 
+  const call_string m_empty_call_string;
+
   unsigned m_next_region_id;
   root_region m_root_region;
   stack_region m_stack_region;
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index 1f48a096e5d..b601f450513 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -266,11 +266,13 @@  public:
     function *handler_fun = DECL_STRUCT_FUNCTION (m_fndecl);
     if (!handler_fun)
       return;
+    const extrinsic_state &ext_state = eg->get_ext_state ();
     program_point entering_handler
-      = program_point::from_function_entry (eg->get_supergraph (),
+      = program_point::from_function_entry (*ext_state.get_model_manager (),
+					    eg->get_supergraph (),
 					    handler_fun);
 
-    program_state state_entering_handler (eg->get_ext_state ());
+    program_state state_entering_handler (ext_state);
     update_model_for_signal_handler (state_entering_handler.m_region_model,
 				     handler_fun);
     state_entering_handler.m_checker_states[sm_idx]->set_global_state