From 11d14915a5e507df954322b117818b0ab819a654 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 9 Apr 2020 15:12:42 -0400
Subject: [PATCH 017/237] FIXME: initial bad implementation of
 immutable_hash_map

---
 gcc/Makefile.in                    |   1 +
 gcc/analyzer/analyzer-selftests.cc |   1 +
 gcc/analyzer/analyzer-selftests.h  |   1 +
 gcc/analyzer/immutable-hash-map.cc | 143 +++++++++++++++++++++++++++++
 gcc/analyzer/immutable-hash-map.h  |  95 +++++++++++++++++++
 gcc/hash-map.h                     |   9 ++
 gcc/hash-table.h                   |  16 ++++
 7 files changed, 266 insertions(+)
 create mode 100644 gcc/analyzer/immutable-hash-map.cc
 create mode 100644 gcc/analyzer/immutable-hash-map.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index a3b3427fea3..147c111ae5c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1228,6 +1228,7 @@ ANALYZER_OBJS = \
 	analyzer/diagnostic-manager.o \
 	analyzer/engine.o \
 	analyzer/function-set.o \
+	analyzer/immutable-hash-map.o \
 	analyzer/pending-diagnostic.o \
 	analyzer/program-point.o \
 	analyzer/program-state.o \
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index 30f83c215c4..2e80069b4e7 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -53,6 +53,7 @@ run_analyzer_selftests ()
 #if ENABLE_ANALYZER
   analyzer_constraint_manager_cc_tests ();
   analyzer_function_set_cc_tests ();
+  analyzer_immutable_hash_map_cc_tests ();
   analyzer_program_point_cc_tests ();
   analyzer_program_state_cc_tests ();
   analyzer_region_model_cc_tests ();
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index cca462e4cc2..d3fe380e157 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -36,6 +36,7 @@ extern void run_analyzer_selftests ();
 extern void analyzer_checker_script_cc_tests ();
 extern void analyzer_constraint_manager_cc_tests ();
 extern void analyzer_function_set_cc_tests ();
+extern void analyzer_immutable_hash_map_cc_tests ();
 extern void analyzer_program_point_cc_tests ();
 extern void analyzer_program_state_cc_tests ();
 extern void analyzer_region_model_cc_tests ();
diff --git a/gcc/analyzer/immutable-hash-map.cc b/gcc/analyzer/immutable-hash-map.cc
new file mode 100644
index 00000000000..f9a5fd26fad
--- /dev/null
+++ b/gcc/analyzer/immutable-hash-map.cc
@@ -0,0 +1,143 @@
+/* FIXME.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "analyzer/immutable-hash-map.h"
+#include "selftest.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Construct various immutable_hash_map <const char *, int> and verify that
+   operations work correctly.  */
+
+static void
+test_map_of_strings_to_int ()
+{
+  typedef immutable_hash_map <const char *, int> map_t;
+  uniq_manager<map_t> mgr;
+
+  const char *ostrich = "ostrich";
+  const char *elephant = "elephant";
+  const char *ant = "ant";
+  const char *spider = "spider";
+  const char *millipede = "Illacme plenipes";
+  const char *eric = "half a bee";
+
+  /* A fresh hash_map should be empty.  */
+  map_t m;
+  //ASSERT_TRUE (m.is_empty ());
+  ASSERT_EQ (NULL, m.get (ostrich));
+
+  /* Populate the hash_map.  */
+  const map_t *m1 = m.put (ostrich, 2, &mgr);
+  ASSERT_NE (m1, &m);
+  
+  const map_t *m2 = m1->put (elephant, 4, &mgr);
+  ASSERT_NE (m2, m1);
+
+  const map_t *m3 = m2->put (ant, 6, &mgr);
+  ASSERT_NE (m3, m2);
+
+  const map_t *m4 = m3->put (spider, 8, &mgr);
+  ASSERT_NE (m4, m3);
+
+  const map_t *m5 = m4->put (millipede, 750, &mgr);
+  ASSERT_NE (m5, m4);
+
+  const map_t *m6 = m5->put (eric, 3, &mgr);
+  ASSERT_NE (m6, m5);
+
+  /* Verify that we can recover the stored values.  */
+  ASSERT_EQ (m6->elements (), 6);
+  ASSERT_EQ (*m6->get (ostrich), 2);
+  ASSERT_EQ (*m6->get (elephant), 4);
+  ASSERT_EQ (*m6->get (ant), 6);
+  ASSERT_EQ (*m6->get (spider), 8);
+  ASSERT_EQ (*m6->get (millipede), 750);
+  ASSERT_EQ (*m6->get (eric), 3);
+
+  /* Verify removing an item.  */
+  const map_t *m7 = m6->remove (eric, &mgr);
+#if 0
+  ASSERT_EQ (m7, m5);
+#endif
+  ASSERT_EQ (m7->elements (), 5);
+  ASSERT_EQ (m7->get (eric), NULL);
+#if 0
+  ASSERT_EQ (m7->remove (eric, &mgr), m7);
+#endif
+  ASSERT_EQ (m7->elements (), 5);
+
+#if 0
+  /* A plain char * key is hashed based on its value (address), rather
+     than the string it points to.  */
+  char *another_ant = static_cast <char *> (xcalloc (4, 1));
+  another_ant[0] = 'a';
+  another_ant[1] = 'n';
+  another_ant[2] = 't';
+  another_ant[3] = 0;
+  ASSERT_NE (ant, another_ant);
+  unsigned prev_size = m.elements ();
+  const map_t *m7 = m6->put (another_ant, 7)
+  ASSERT_NE (m6, m7);
+  ASSERT_EQ (prev_size + 1, m6->elements ());
+
+  /* Need to use string_hash or nofree_string_hash key types to hash
+     based on the string contents.  */
+  hash_map <nofree_string_hash, int> string_map;
+  ASSERT_EQ (false, string_map.put (ant, 1));
+  ASSERT_EQ (1, string_map.elements ());
+  ASSERT_EQ (true, string_map.put (another_ant, 5));
+  ASSERT_EQ (1, string_map.elements ());
+
+  free (another_ant);
+#endif
+}
+
+static void
+test_consolidation ()
+{
+  // TODO
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_immutable_hash_map_cc_tests ()
+{
+  test_map_of_strings_to_int ();
+  test_consolidation ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/immutable-hash-map.h b/gcc/analyzer/immutable-hash-map.h
new file mode 100644
index 00000000000..1362896992c
--- /dev/null
+++ b/gcc/analyzer/immutable-hash-map.h
@@ -0,0 +1,95 @@
+/* FIXME.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_IMMUTABLE_HASH_MAP_H
+#define GCC_ANALYZER_IMMUTABLE_HASH_MAP_H
+
+#include "uniq-manager.h"
+
+namespace ana {
+
+/* An immutable hash_map.
+   Setting/unsetting keys is const, and returns a different immutable_hash_map.
+   The instances are managed and owned by a manager object, which
+   consolidates them so that equal maps use the same instances.  */
+
+template <typename Key, typename Value>
+class immutable_hash_map
+{
+public:
+  typedef uniq_manager<immutable_hash_map> manager_t;
+
+  const Value *get (const Key &k) const;
+  size_t elements () const { return m_inner_map.elements (); }
+
+  const immutable_hash_map *put (const Key &k, const Value &v,
+				 manager_t *m) const;
+  const immutable_hash_map *remove (const Key &k, manager_t *m) const;
+
+  /* For use by manager_t.  */
+  hashval_t hash () const;
+  bool operator== (const immutable_hash_map &other) const
+  {
+    return m_inner_map == other.m_inner_map;
+  }
+
+private:
+  /* TODO: reimplement using trees.  */
+  typedef hash_map<Key, Value> inner_map_t;
+  typedef typename inner_map_t::iterator iterator;
+  inner_map_t m_inner_map;
+};
+
+template<typename K, typename V>
+inline const V*
+immutable_hash_map<K, V>::get (const K &k) const
+{
+  return const_cast <inner_map_t &> (m_inner_map).get (k);
+}
+
+template<typename K, typename V>
+inline const immutable_hash_map<K, V> *
+immutable_hash_map<K, V>::put (const K &k, const V &v, manager_t *m) const
+{
+  immutable_hash_map<K, V> *copy = new immutable_hash_map<K, V> (*this);
+  copy->m_inner_map.put (k, v);
+  return m->consolidate (copy);
+}
+  
+template<typename K, typename V>
+inline const immutable_hash_map<K, V> *
+immutable_hash_map<K, V>::remove (const K &k, manager_t *m) const
+{
+  immutable_hash_map<K, V> *copy = new immutable_hash_map<K, V> (*this);
+  copy->m_inner_map.remove (k);
+  return m->consolidate (copy);
+}
+
+template<typename K, typename V>
+inline hashval_t 
+immutable_hash_map<K, V>::hash () const
+{
+  // FIXME: this is a terrible hash function
+  return elements ();
+}
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_IMMUTABLE_HASH_MAP_H */
diff --git a/gcc/hash-map.h b/gcc/hash-map.h
index 5b8fd184e32..02cd769f52f 100644
--- a/gcc/hash-map.h
+++ b/gcc/hash-map.h
@@ -240,6 +240,15 @@ public:
   /* Return true when there are no elements in this hash map.  */
   bool is_empty () const { return m_table.is_empty (); }
 
+  bool operator== (const hash_map &other) const
+  {
+    return m_table == other.m_table;
+#if 0
+    if (elements () != other.elements ())
+      return false;
+#endif
+  }
+
   class iterator
   {
   public:
diff --git a/gcc/hash-table.h b/gcc/hash-table.h
index a1423c78112..90307423c50 100644
--- a/gcc/hash-table.h
+++ b/gcc/hash-table.h
@@ -399,6 +399,8 @@ public:
     return table;
   }
 
+  bool operator== (const hash_table &other) const;
+
   /* Current size (in entries) of the hash table.  */
   size_t size () const { return m_size; }
 
@@ -720,6 +722,20 @@ hash_table<Descriptor, Lazy,
   return nentries;
 }
 
+template<typename Descriptor, bool Lazy,
+	 template<typename Type> class Allocator>
+bool
+hash_table<Descriptor, Lazy,
+	   Allocator>::operator== (const hash_table &other) const
+{
+  if (m_n_elements != other.m_n_elements)
+    return false;
+  if (m_size != other.m_size)
+    return false;
+  return 0 == memcmp (m_entries, other.m_entries,
+		      m_size * sizeof (typename Descriptor::value_type));
+}
+
 /* Similar to find_slot, but without several unwanted side effects:
     - Does not call equal when it finds an existing entry.
     - Does not change the count of elements/searches/collisions in the
-- 
2.26.2

