From 939204de4d2275789bb533e0b9dcd645f73efc72 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 10 Apr 2019 13:32:47 -0400
Subject: [PATCH 190/217] FIXME: impl-3: initial work on termination

---
 gcc/analyzer/impl-3.cc | 159 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 156 insertions(+), 3 deletions(-)

diff --git a/gcc/analyzer/impl-3.cc b/gcc/analyzer/impl-3.cc
index debf327..e782156 100644
--- a/gcc/analyzer/impl-3.cc
+++ b/gcc/analyzer/impl-3.cc
@@ -487,6 +487,68 @@ public:
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* FIXME.
+
+   Information on where a path has been so far, both in terms of the
+   supergraph, but also on states of interest.
+
+   This is to ensure termination of the analysis.
+
+   The goal is to terminate early without attempting e.g. to fully unroll
+   loops, but also to maintain some precision if interesting state changes
+   are occurring.
+
+   TODO: what should this be?
+   I'm currently thinking of a map from superedge to records, recording
+   expr->state maps, so that we can see if an expr has been in the state
+   before (whilst allowing for ECs to vary on different iterations, but
+   without being too tied to the expr-to-EC mapping).  */
+
+class terminator
+{
+public:
+  struct per_edge_t
+  {
+    per_edge_t (const superedge *edge)
+    : m_edge (edge), m_count (0)
+    {}
+    per_edge_t (const per_edge_t &other)
+    : m_edge (other.m_edge), m_count (other.m_count)
+    {}
+
+    std::string to_str () const
+    {
+      return (std::string ("(")
+	      + m_edge->to_str ()
+	      + ": "
+	      + std::to_string (m_count)
+	      + ")");
+    }
+
+    const superedge *m_edge;
+    // map from expr to set of states we've seen that expr in
+    // or should it be path_var?
+    // FIXME: for now, just a count of how many times we've followed this edge
+    int m_count;
+  };
+
+  terminator ();
+  terminator (const terminator &other);
+  terminator & operator= (const terminator &other);
+
+  std::string to_str () const;
+
+  void add_edge (const superedge *edge);
+  bool terminate_edge_p (const superedge *edge);
+
+  per_edge_t &get_edge_data (const superedge *edge);
+
+  typedef hash_map <const superedge *, per_edge_t *> edge_map_t;
+  edge_map_t m_per_edge_data;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
 /* FIXME.  */
 
 class checker3_path : public diagnostic_path
@@ -507,7 +569,8 @@ public:
     m_engine (engine),
     m_links (),
     m_active_calls (),
-    m_ps ()
+    m_ps (),
+    m_terminator ()
   {}
   checker3_path (const checker3_path &other);
   checker3_path& operator= (const checker3_path &other);
@@ -638,6 +701,8 @@ public:
   /* The current state of memory.  */
   path_state m_ps;
 
+  terminator m_terminator;
+
   /* FIXME: map from EC to state (or subclass path_state?)
      No, store them in EC's m_extra_state.  */
 
@@ -774,10 +839,84 @@ public:
 
 ////////////////////////////////////////////////////////////////////////////
 
+terminator::terminator ()
+{
+}
+
+terminator::terminator (const terminator &other)
+{
+  for (edge_map_t::iterator iter = other.m_per_edge_data.begin ();
+       iter != other.m_per_edge_data.end ();
+       ++iter)
+    {
+      m_per_edge_data.put ((*iter).first,
+			   new per_edge_t (*(*iter).second));
+    }
+}
+
+terminator &
+terminator::operator= (const terminator &other)
+{
+  for (edge_map_t::iterator iter = other.m_per_edge_data.begin ();
+       iter != other.m_per_edge_data.end ();
+       ++iter)
+    {
+      m_per_edge_data.put ((*iter).first,
+			   new per_edge_t (*(*iter).second));
+    }
+  return *this;
+}
+
+std::string
+terminator::to_str () const
+{
+  std::string result ("{");
+  for (edge_map_t::iterator iter = m_per_edge_data.begin ();
+       iter != m_per_edge_data.end ();
+       ++iter)
+    {
+      if (iter != m_per_edge_data.begin ())
+	result += ", ";
+      result += (*iter).second->to_str ();
+    }
+  result += "}";
+  return result;
+}
+
+void
+terminator::add_edge (const superedge *edge)
+{
+  per_edge_t &edge_data = get_edge_data (edge);
+  edge_data.m_count++;
+}
+
+bool
+terminator::terminate_edge_p (const superedge *edge)
+{
+  per_edge_t &edge_data = get_edge_data (edge);
+  const int MAX_USES_OF_EDGE_PER_PATH = 2;
+  if (edge_data.m_count >= MAX_USES_OF_EDGE_PER_PATH)
+    return true;
+  return false;
+}
+
+terminator::per_edge_t &
+terminator::get_edge_data (const superedge *edge)
+{
+  if (per_edge_t **ptr_ptr = m_per_edge_data.get (edge))
+    return **ptr_ptr;
+  per_edge_t *data = new per_edge_t (edge);
+  m_per_edge_data.put (edge, data);
+  return *data;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 checker3_path::checker3_path (const checker3_path &other)
 : m_engine (other.m_engine),
   m_links (),
-  m_ps (other.m_ps)
+  m_ps (other.m_ps),
+  m_terminator (other.m_terminator)
 {
   unsigned i;
   {
@@ -803,6 +942,7 @@ checker3_path::operator= (const checker3_path &other)
 {
   m_engine = other.m_engine;
   m_ps = other.m_ps;
+  m_terminator = other.m_terminator;
 
   unsigned i;
   {
@@ -863,12 +1003,14 @@ checker3_path::to_str () const
     result += "]";
   }
   result += ", m_ps=" + m_ps.to_str ();
+  result += ", m_terminator=" + m_terminator.to_str ();
   return result;
 }
 
 /* Record that we've reached NODE, via EDGE.  EDGE may be NULL,
    to cover entrypoints to the analysis.
-   Update m_active_calls to reflect call and return edges.  */
+   Update m_active_calls to reflect call and return edges.
+   Update m_terminator.  */
 
 void
 checker3_path::add_link (const superedge *edge, const supernode *node)
@@ -895,6 +1037,7 @@ checker3_path::add_link (const superedge *edge, const supernode *node)
 	  }
 	  break;
 	}
+      m_terminator.add_edge (edge);
     }
   if (get_verbosity () > 1)
     {
@@ -1022,6 +1165,16 @@ engine::traverse (checker3_path path)
 	  }
 	}
 
+      if (path.m_terminator.terminate_edge_p (succ))
+	{
+	  log ("rejecting edge; seen too many times");
+	  // FIXME: but presumably we should do this upfront,
+	  // and figure out if we need to do an early-reject
+	  // of any edges; if so, we may need to relax constraints
+	  // to force an early loop exit
+	  continue;
+	}
+
       checker3_path new_path (path);
 
       /* For conditionals and switch statements, add the
-- 
1.8.5.3

