From 3bdabd98a04b4a3cdfebe162f05dcca594259893 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 23 May 2019 04:39:41 -0400
Subject: [PATCH 63/67] FIXME: 2019-05-23 work on region model

---
 gcc/analyzer/analyzer-logging.h      |   8 +
 gcc/analyzer/constraint-manager.cc   |  43 ++++-
 gcc/analyzer/constraint-manager.h    |   6 +
 gcc/analyzer/impl-4.cc               |  54 +++++--
 gcc/analyzer/region-model.cc         | 296 ++++++++++++++++++++++++++++++++++-
 gcc/analyzer/region-model.h          |  27 +++-
 gcc/analyzer/supergraph.cc           |  27 ++++
 gcc/analyzer/supergraph.h            |   1 +
 gcc/testsuite/gcc.dg/analyzer/loop.c |   1 +
 9 files changed, 443 insertions(+), 20 deletions(-)

diff --git a/gcc/analyzer/analyzer-logging.h b/gcc/analyzer/analyzer-logging.h
index e09ed8e8..8f950d9 100644
--- a/gcc/analyzer/analyzer-logging.h
+++ b/gcc/analyzer/analyzer-logging.h
@@ -55,6 +55,8 @@ class logger
     ATTRIBUTE_GCC_DIAG(3, 0);
   void exit_scope (const char *scope_name);
 
+  pretty_printer *get_printer () const { return m_pp; }
+
 private:
   int m_refcount;
   FILE *m_f_out;
@@ -160,6 +162,12 @@ class log_user
   void enter_scope (const char *scope_name);
   void exit_scope (const char *scope_name);
 
+  pretty_printer *get_logger_pp () const
+  {
+    gcc_assert (m_logger);
+    return m_logger->get_printer ();
+  }
+
  private:
   logger *m_logger;
 };
diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index c3204c2..26325e9 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -62,14 +62,15 @@ private:
 /* FIXME.  */
 
 equiv_class::equiv_class ()
-: m_constant (NULL_TREE), m_vars (), m_escaped (false)
+: m_constant (NULL_TREE), m_cst_sid (svalue_id::null ()),
+  m_vars (), m_escaped (false)
 {
 }
 
 /* FIXME.  */
 
 equiv_class::equiv_class (const equiv_class &other)
-: m_constant (other.m_constant),
+: m_constant (other.m_constant), m_cst_sid (other.m_cst_sid),
   m_vars (),
   m_escaped (other.m_escaped)
 {
@@ -136,6 +137,8 @@ equiv_class::operator== (const equiv_class &other)
   if (m_constant != other.m_constant)
     return false; // TODO: use tree equality here
 
+  // FIXME: m_cst_sid
+
   if (m_vars.length () != other.m_vars.length ())
     return false;
 
@@ -159,6 +162,7 @@ equiv_class::add (svalue_id sid, const constraint_manager &cm)
     {
       gcc_assert (m_constant == NULL_TREE);
       m_constant = cst;
+      m_cst_sid = sid;
     }
   m_vars.safe_push (sid);
 
@@ -176,7 +180,7 @@ equiv_class::add (svalue_id sid, const constraint_manager &cm)
 bool
 equiv_class::del (svalue_id sid)
 {
-  //gcc_assert (!CONSTANT_CLASS_P (v.m_tree));
+  gcc_assert (sid != m_cst_sid);
 
   int i;
   svalue_id *iv;
@@ -212,6 +216,16 @@ equiv_class::get_representative () const
     }
 }
 
+void
+equiv_class::remap_svalues (const svalue_id_map &map)
+{
+  int i;
+  svalue_id *iv;
+  FOR_EACH_VEC_ELT (m_vars, i, iv)
+    *iv = map.get_dst_for_src (*iv);
+  m_cst_sid = map.get_dst_for_src (m_cst_sid);
+}
+
 /* FIXME.  */
 
 const char *
@@ -1073,6 +1087,8 @@ constraint_manager::purge (const purge_criteria &p)
       bool delete_ec = false;
       FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
 	{
+	  if (*pv == ec->m_cst_sid)
+	    continue;
 	  if (p.should_purge_p (*pv))
 	    {
 	      num_purged++;
@@ -1173,6 +1189,17 @@ constraint_manager::purge (const purge_criteria &p)
 /* FIXME.  */
 
 void
+constraint_manager::remap_svalues (const svalue_id_map &map)
+{
+  int i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    ec->remap_svalues (map);
+}
+
+/* FIXME.  */
+
+void
 constraint_manager::validate () const
 {
   // TODO: skip this in a release build
@@ -1184,15 +1211,15 @@ constraint_manager::validate () const
       gcc_assert (ec);
 
       int j;
-      svalue_id *pv;
-#if 0
-      FOR_EACH_VEC_ELT (ec->m_vars, j, pv)
+      svalue_id *sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
 	{
-	  gcc_assert (!CONSTANT_CLASS_P (pv->m_tree));
-	  //gcc_assert (pv->m_stack_depth <= m_stack_depth);
+	  gcc_assert (!sid->null_p ());
+	  gcc_assert (sid->as_int () < get_num_svalues ());
 	}
       if (ec->m_constant)
 	gcc_assert (CONSTANT_CLASS_P (ec->m_constant));
+#if 0
       else
 	gcc_assert (ec->m_vars.length () > 0);
 #endif
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index 2e4dcea..2e07dc3 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -88,8 +88,11 @@ public:
 
   svalue_id get_representative () const;
 
+  void remap_svalues (const svalue_id_map &map);
+
   /* An equivalence class can contain at most one constant.  */
   tree m_constant;
+  svalue_id m_cst_sid;
 
   // FIXME: this is a bad representation, probably should be a set:
   auto_vec<svalue_id> m_vars;
@@ -169,6 +172,7 @@ public:
   virtual constraint_manager *clone (region_model *) const = 0;
   virtual tree maybe_get_constant (svalue_id sid) const = 0;
   virtual svalue_id get_sid_for_constant (tree cst) const = 0;
+  virtual int get_num_svalues () const = 0;
 
   constraint_manager& operator= (const constraint_manager &other);
 
@@ -223,6 +227,8 @@ public:
 
   int purge (const purge_criteria &p);
 
+  void remap_svalues (const svalue_id_map &map);
+
   void validate () const;
 
   auto_vec<equiv_class *> m_equiv_classes;
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 623cd55..fab9026 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -2460,6 +2460,16 @@ program_state::prune_for_point (exploded_graph &eg,
     }
   eg.log ("num_sm_purged: %i", num_sm_purged);
 
+  if (new_state.m_region_model)
+    {
+#if 0
+      if (eg.get_logger ())
+	new_state.m_region_model->dump_to_pp (eg.get_logger_pp ());
+#endif
+      int num_svals_purged = new_state.m_region_model->purge_svalues ();
+      eg.log ("num_svals_purged: %i", num_svals_purged);
+    }
+
   return new_state;
 }
 
@@ -4227,7 +4237,11 @@ exploded_graph::get_or_create_node (const program_point &point,
   pruned_state.validate ();
 
   if (get_logger ())
-    log ("pruned_state: %s", pruned_state.to_str (m_ext_state).c_str ());
+    {
+      log ("pruned_state: %s", pruned_state.to_str (m_ext_state).c_str ());
+      if (pruned_state.m_region_model)
+	pruned_state.m_region_model->dump_to_pp (get_logger_pp ());
+    }
 
   stats *per_fn_stats = get_or_create_function_stats (point.get_function ());
 
@@ -4377,7 +4391,10 @@ exploded_graph::process_node (exploded_node *node)
 				 &next_state);
 
 	    if (next_state.m_region_model)
-	      next_state.m_region_model->handle_phi (lhs, src);
+	      {
+		bool is_back_edge = last_cfg_superedge->back_edge_p ();
+		next_state.m_region_model->handle_phi (lhs, src, is_back_edge);
+	      }
 	  }
 
 	if (point.m_supernode->m_stmts.length () > 0)
@@ -5443,18 +5460,18 @@ public:
     if (m_map == NULL)
       return;
 
-    pp_newline (pp);
-    pp_printf (pp, "FIXME: stmt annotation");
-    pp_newline (pp);
-
     if (stmt->code == GIMPLE_PHI)
       return;
 
+    pp_newline (pp);
+
     const supernode *supernode = m_map->m_sg.get_supernode_for_stmt (stmt);
     unsigned int stmt_idx = supernode->get_stmt_index (stmt);
     program_point before_stmt
       (program_point::before_stmt (supernode, stmt_idx, call_string ()));
 
+    auto_vec<tree> needed;
+    auto_vec<tree> not_needed;
     for (state_purge_map::iterator iter = m_map->begin ();
 	 iter != m_map->end ();
 	 ++iter)
@@ -5462,11 +5479,30 @@ public:
 	tree var = (*iter).first;
 	state_purge_per_ssa_name *per_var_data = (*iter).second;
 	if (per_var_data->needed_at_point_p (before_stmt))
-	  pp_printf (pp, "%qE needed here", var);
+	  needed.safe_push (var);
 	else
-	  pp_printf (pp, "%qE not needed here", var);
-	pp_newline (pp);
+	  not_needed.safe_push (var);
       }
+
+    tree var;
+    unsigned i;
+    pp_printf (pp, "needed here: {");
+    FOR_EACH_VEC_ELT (needed, i, var)
+      {
+	if (i > 0)
+	  pp_string (pp, ", ");
+	pp_printf (pp, "%qE", var);
+      }
+    pp_printf (pp, "}");
+    pp_newline (pp);
+    pp_printf (pp, "not needed here: {");
+    FOR_EACH_VEC_ELT (not_needed, i, var)
+      {
+	if (i > 0)
+	  pp_string (pp, ", ");
+	pp_printf (pp, "%qE", var);
+      }
+    pp_newline (pp);
   }
 
 private:
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index a4da8f8..1cdee31 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -82,6 +82,11 @@ class impl_constraint_manager : public constraint_manager
     return m_model->get_rvalue (cst);
   }
 
+  int get_num_svalues () const FINAL OVERRIDE
+  {
+    return m_model->get_num_svalues ();
+  }
+
  private:
   region_model *m_model;
 };
@@ -114,6 +119,95 @@ svalue_id::to_str () const
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* class svalue_id_map.  */
+
+svalue_id_map::svalue_id_map (int num_svalues)
+: m_src_to_dst (num_svalues),
+  m_dst_to_src (num_svalues)
+{
+  for (int i = 0; i < num_svalues; i++)
+    {
+      m_src_to_dst.quick_push (svalue_id::null ());
+      m_dst_to_src.quick_push (svalue_id::null ());
+    }
+}
+
+
+/* FIXME.  */
+
+void
+svalue_id_map::put (svalue_id src, svalue_id dst)
+{
+  m_src_to_dst[src.as_int ()] = dst;
+  m_dst_to_src[dst.as_int ()] = src;
+}
+
+/* FIXME.  */
+
+svalue_id
+svalue_id_map::get_dst_for_src (svalue_id src) const
+{
+  if (src.null_p ())
+    return src;
+  return m_src_to_dst[src.as_int ()];
+}
+
+/* FIXME.  */
+
+svalue_id
+svalue_id_map::get_src_for_dst (svalue_id dst) const
+{
+  if (dst.null_p ())
+    return dst;
+  return m_dst_to_src[dst.as_int ()];
+}
+
+/* FIXME.  */
+
+void
+svalue_id_map::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "src to dst: {");
+  unsigned i;
+  svalue_id *dst;
+  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      svalue_id src (svalue_id::from_int (i));
+      src.print (pp);
+      pp_string (pp, " -> ");
+      dst->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+
+  pp_string (pp, "dst to src: {");
+  svalue_id *src;
+  FOR_EACH_VEC_ELT (m_dst_to_src, i, src)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      svalue_id dst (svalue_id::from_int (i));
+      dst.print (pp);
+      pp_string (pp, " <- ");
+      src->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+DEBUG_FUNCTION void
+svalue_id_map::dump () const
+{
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
 void
 region_id::print (pretty_printer *pp) const
 {
@@ -702,6 +796,14 @@ region::to_str () const
 /* FIXME.  */
 
 void
+region::remap_svalues (const svalue_id_map &map)
+{
+  m_sval_id = map.get_dst_for_src (m_sval_id);
+}
+
+/* FIXME.  */
+
+void
 region::add_to_hash (inchash::hash &hstate) const
 {
   inchash::add (m_parent_rid, hstate);
@@ -1536,7 +1638,18 @@ void
 region_model::validate () const
 {
   // TODO: skip this in a release build
+
   m_constraints->validate ();
+
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    {
+      svalue_id sid = r->get_value_direct ();
+      if (!sid.null_p ())
+	gcc_assert (sid.as_int () < m_svalues.length ());
+    }
+
   // TODO: anything else?
 }
 
@@ -1811,9 +1924,24 @@ region_model::on_return (const greturn *return_stmt)
 /* FIXME.  */
 
 void
-region_model::handle_phi (tree lhs, tree rhs)
+region_model::handle_phi (tree lhs, tree rhs, bool is_back_edge)
 {
-  set_value (get_lvalue (lhs), get_rvalue (rhs));
+  svalue_id rhs_sid = get_rvalue (rhs);
+
+  if (is_back_edge && get_svalue (rhs_sid)->get_kind () != SK_UNKNOWN)
+    {
+      /* If we have a back edge, we probably have a loop.
+	 Use an unknown value, to avoid effectively unrolling the
+	 loop.
+	 To terminate, we need to avoid generating a series of
+	 models with an unbounded monotonically increasing number of
+	 redundant unknown values; hence we need to purge svalues
+	 before inserting the state into the exploded graph, to
+	 collect unused svalues.  */
+      set_to_new_unknown_value (get_lvalue (lhs), TREE_TYPE (lhs));
+    }
+  else
+    set_value (get_lvalue (lhs), rhs_sid);
 }
 
 /* FIXME.  */
@@ -2530,6 +2658,131 @@ region_model::get_region (region_id rid) const
   return m_regions[rid.as_int ()];
 }
 
+/* Helper class for region_model::purge_svalues.  */
+
+class restrict_to_used_svalues : public purge_criteria
+{
+public:
+  restrict_to_used_svalues (const sbitmap &used) : m_used (used) {}
+
+  bool should_purge_p (svalue_id sid) const FINAL OVERRIDE
+  {
+    gcc_assert (!sid.null_p ());
+    return !bitmap_bit_p (m_used, sid.as_int ());
+  }
+
+private:
+  const sbitmap &m_used;
+};
+
+/* Remove unused svalues from this model, returning the number of
+   svalues that were removed.
+
+   This is important for ensuring that the analysis of loops terminates.
+   Otherwise, we could generate a succession of models with unreferenced
+   "unknown" values, where the number of redundant unknown values could
+   grow without bounds, and each such model would be treated as distinct.  */
+
+int
+region_model::purge_svalues ()
+{
+  auto_sbitmap used (m_svalues.length ());
+
+  /* Walk the regions, marking sids that are used.  */
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    {
+      svalue_id sid = r->get_value_direct ();
+      if (!sid.null_p ())
+	bitmap_set_bit (used, sid.as_int ());
+    }
+
+  /* Now purge any constraints involving svalues we don't care about.  */
+  restrict_to_used_svalues criterion (used);
+  m_constraints->purge (criterion);
+
+  /* Mark any sids that are in constraints that survived.  */
+  {
+    equiv_class *ec;
+    FOR_EACH_VEC_ELT (m_constraints->m_equiv_classes, i, ec)
+      {
+	int j;
+	svalue_id *sid;
+	FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+	  {
+	    gcc_assert (!sid->null_p ());
+	    bitmap_set_bit (used, sid->as_int ());
+	  }
+	// FIXME: should we just do this for constants?
+	// is everything else purged?
+      }
+  }
+
+  /* Build a mapping from old-sid to new-sid so that we can preserve
+     order of the used IDs and move all redundant ones to the end.
+     Iterate though svalue IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  svalue_id_map map (m_svalues.length ());
+  int next_used_new_sid = 0;
+  int after_next_unused_new_sid = m_svalues.length ();
+  for (unsigned i = 0; i < m_svalues.length (); i++)
+    {
+      svalue_id src (svalue_id::from_int (i));
+      if (bitmap_bit_p (used, i))
+	map.put (src, svalue_id::from_int (next_used_new_sid++));
+      else
+	map.put (src, svalue_id::from_int (--after_next_unused_new_sid));
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_sid == after_next_unused_new_sid);
+
+  /* Now walk the regions and the constraints, remapping sids,
+     so that all the redundant svalues are at the end.  */
+  remap_svalues (map);
+
+  /* Drop the redundant svalues from the end of the vector.  */
+  int num_purged = 0;
+  while (m_svalues.length () > next_used_new_sid)
+    {
+      delete m_svalues.pop ();
+      num_purged++;
+    }
+
+  validate ();
+
+  return num_purged;
+}
+
+/* FIXME.  */
+
+void
+region_model::remap_svalues (const svalue_id_map &map)
+{
+  unsigned i;
+  region *r;
+  FOR_EACH_VEC_ELT (m_regions, i, r)
+    r->remap_svalues (map);
+
+  // TODO: update constraints
+  m_constraints->remap_svalues (map);
+
+  // TODO: update the m_svalues vector
+  auto_vec<svalue *> new_svalues;
+  for (int i = 0; i < m_svalues.length (); i++)
+    {
+      svalue_id dst (svalue_id::from_int (i));
+      svalue_id src = map.get_src_for_dst (dst);
+      new_svalues.safe_push (get_svalue (src));
+    }
+
+  /* Copy over the reordered vec to m_svalues.  */
+  m_svalues.truncate (0);
+  svalue *sval;
+  FOR_EACH_VEC_ELT (new_svalues, i, sval)
+    m_svalues.safe_push (sval);
+}
+
 /* FIXME.  */
 
 void
@@ -2700,6 +2953,44 @@ test_region_equality ()
   delete r1;
 }
 
+/* FIXME.  */
+
+static void
+test_purge_svalues ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+		       get_identifier ("x"),
+		       integer_type_node);
+  tree y = build_decl (UNKNOWN_LOCATION, VAR_DECL,
+		       get_identifier ("y"),
+		       integer_type_node);
+
+  region_model model;
+  model.set_to_new_unknown_value (model.get_lvalue (x), TREE_TYPE (x));
+  model.set_to_new_unknown_value (model.get_lvalue (x), TREE_TYPE (x));
+  model.set_to_new_unknown_value (model.get_lvalue (x), TREE_TYPE (x));
+  model.add_constraint (x, NE_EXPR, int_42);
+
+  model.set_value (model.get_lvalue (x), model.get_rvalue (int_42));
+  model.add_constraint (y, GT_EXPR, int_0);
+
+  /* The redundant unknown values should have been purged.  */
+  int num_purged = model.purge_svalues ();
+  ASSERT_EQ (num_purged, 3);
+
+  /* and the redundant constraint on an old, unknown value for x should
+     have been purged.  */
+  ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 2);
+
+  /* ...but we should still have x == 42.  */
+  ASSERT_EQ (model.eval_condition (x, EQ_EXPR, int_42), tristate::TS_TRUE);
+
+  /* ...and we should still have the constraint on y.  */
+  ASSERT_EQ (model.eval_condition (y, GT_EXPR, int_0), tristate::TS_TRUE);
+}
+
 #if 0
 
 /* FIXME.  */
@@ -2868,6 +3159,7 @@ analyzer_region_model_cc_tests ()
   test_unique_constants ();
   test_svalue_equality ();
   test_region_equality ();
+  test_purge_svalues ();
 #if 0
   test_assignment ();
   test_stack_frames ();
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 4a954ba..c418a06 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -77,6 +77,23 @@ private:
   int m_idx;
 };
 
+/* FIXME.  */
+
+class svalue_id_map
+{
+ public:
+  svalue_id_map (int num_svalues);
+  void put (svalue_id src, svalue_id dst);
+  svalue_id get_dst_for_src (svalue_id src) const;
+  svalue_id get_src_for_dst (svalue_id dst) const;
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+
+ private:
+  auto_vec<svalue_id> m_src_to_dst;
+  auto_vec<svalue_id> m_dst_to_src;
+};
+
 /* An ID for a region within a region_model.  Internally this is an index
    into a vector.  */
 
@@ -344,6 +361,7 @@ public:
 
   void set_value (const region_model &model, svalue_id rhs_sid);
   svalue_id get_value (region_model &model, bool non_null);
+  svalue_id get_value_direct () const { return m_sval_id; }
 
   tree get_type () const { return m_type; }
 
@@ -369,6 +387,8 @@ public:
 
   std::string to_str () const;
 
+  void remap_svalues (const svalue_id_map &map);
+
  protected:
   region (region_id parent_rid, svalue_id sval_id, tree type)
   : m_parent_rid (parent_rid), m_sval_id (sval_id), m_type (type) {}
@@ -623,7 +643,7 @@ class region_model
   void on_assignment (const gassign *stmt);
   void on_call (const gcall *stmt);
   void on_return (const greturn *stmt);
-  void handle_phi (tree lhs, tree rhs);
+  void handle_phi (tree lhs, tree rhs, bool is_back_edge);
 
   bool apply_constraints_for_edge (const superedge &edge,
 				   const gimple *last_stmt);
@@ -674,6 +694,11 @@ class region_model
 
   tree maybe_get_constant (svalue_id sid);
 
+  int purge_svalues ();
+  void remap_svalues (const svalue_id_map &map);
+
+  int get_num_svalues () const { return m_svalues.length (); }
+
   /* For selftests.  */
   constraint_manager *get_constraints ()
   {
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index 04cfa55..bf20f74 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -89,6 +89,11 @@ supergraph::supergraph (logger *logger)
     {
       function *fun = node->get_fun ();
 
+      /* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
+	 the supergraph (by doing it per-function).  */
+      auto_cfun sentinel (fun);
+      mark_dfs_back_edges ();
+
       const int start_idx = m_nodes.length ();
 
       basic_block bb;
@@ -740,6 +745,28 @@ cfg_superedge::dump_label_to_pp (pretty_printer *pp) const
     pp_printf (pp, "true");
   else if (false_value_p ())
     pp_printf (pp, "false");
+
+  /* Express edge flags as a string with " | " separator.
+     e.g. (flags "FALLTHRU | DFS_BACK").  */
+  if (get_flags ())
+    {
+      pp_string (pp, " (flags ");
+      bool seen_flag = false;
+#define DEF_EDGE_FLAG(NAME,IDX)			\
+  do {						\
+    if (get_flags () & EDGE_##NAME)			\
+      {						\
+	if (seen_flag)				\
+	  pp_string (pp, " | ");			\
+	pp_printf (pp, "%s", (#NAME));		\
+	seen_flag = true;			\
+      }						\
+  } while (0);
+#include "cfg-flags.def"
+#undef DEF_EDGE_FLAG
+      pp_string (pp, ")");
+    }
+
   /* Otherwise, no label.  */
 }
 
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index b01bc96..3b7bf70 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -393,6 +393,7 @@ class cfg_superedge : public superedge
   int get_flags () const { return m_cfg_edge->flags; }
   int true_value_p () const { return get_flags () & EDGE_TRUE_VALUE; }
   int false_value_p () const { return get_flags () & EDGE_FALSE_VALUE; }
+  int back_edge_p () const { return get_flags () & EDGE_DFS_BACK; }
 
   tree get_phi_arg (const gphi *phi) const;
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
index aeba702..936351b 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -1,4 +1,5 @@
 // FIXME:
+/* { dg-additional-options "-fanalyzer-region-model" } */
 /* { dg-additional-options "-fno-analyzer-state-purge" } */
 
 void test(void)
-- 
1.8.5.3

