Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Jan Vrany <jan.vrany@labware.com>
To: gdb-patches@sourceware.org
Cc: Jan Vrany <jan.vrany@labware.com>
Subject: [RFC 5/9] gdb/python: introduce gdbpy_registry
Date: Mon, 27 Jan 2025 10:44:31 +0000	[thread overview]
Message-ID: <20250127104435.823519-6-jan.vrany@labware.com> (raw)
In-Reply-To: <20250127104435.823519-1-jan.vrany@labware.com>

This commit introduces new template class gdbpy_registry to simplify
Python object lifecycle management. As of now, each of the Python
object implementations contain its own (copy of) lifecycle management
code that is largely very similar. The aim of gdbpy_registry is to
factor out this code into a common (template) class in order to simplify
the code.
---
 gdb/python/python-internal.h | 200 +++++++++++++++++++++++++++++++++++
 1 file changed, 200 insertions(+)

diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index c48f260c15f..73df7f34a18 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -22,6 +22,10 @@
 
 #include "extension.h"
 #include "extension-priv.h"
+#include "registry.h"
+#include <unordered_set>
+#include <unordered_map>
+
 
 /* These WITH_* macros are defined by the CPython API checker that
    comes with the Python plugin for GCC.  See:
@@ -1145,4 +1149,200 @@ gdbpy_type_ready (PyTypeObject *type, PyObject *mod = nullptr)
 # define PyType_Ready POISONED_PyType_Ready
 #endif
 
+/* A class to manage lifecycle of Python objects for objects that are "owned" 
+   by an objfile or a gdbarch.  It keeps track of Python objects and when
+   the "owning" object (objfile or gdbarch) is about to be freed, ensures that
+   all Python objects "owned" by that object are properly invalidated.
+
+   The actuall tracking of "owned" Python objects is handled externally
+   by storage class.  Storage object is created for each owning object
+   on demand and it is deleted when owning object is about to be freed.
+
+   The storage class must provide two member types:
+     
+     * obj_type - the type of Python object whose lifecycle is managed. 
+     * val_type - the type of GDB structure the Python objects are 
+       representing.
+
+   It must also provide following methods.
+
+     void add (obj_type *obj);
+     void remove (obj_type *obj);
+     obj_type *lookup (val_type *val);
+
+   Finally it must invalidate all registered Python objects upon deletion.  */
+template <typename Storage>
+class gdbpy_registry
+{
+public:
+  using obj_type = typename Storage::obj_type;
+  using val_type = typename Storage::val_type;
+
+  /* Register Python object OBJ as being "owned" by OWNER.  When OWNER is
+     about to be freed, OBJ will be invalidated.  */
+  template <typename O>
+  void add (O *owner, obj_type *obj) const
+  {
+    get_storage (owner)->add (obj);
+  }
+
+  /* Unregister Python object OBJ.  OBJ will no longer be invalidated when
+     OWNER is about to be be freed.  */
+  template <typename O>
+  void remove (O *owner, obj_type *obj) const
+  {
+    get_storage (owner)->remove (obj);
+  }
+
+  /* Lookup pre-existing Python object for given VAL.  Return such object
+     if found, otherwise return NULL.  This method always returns new
+     reference.  */
+  template <typename O>
+  obj_type *lookup (O *owner, val_type *val) const
+  {
+    obj_type *obj = get_storage (owner)->lookup (val);
+    Py_XINCREF (obj);
+    return obj;
+  }
+
+private:
+
+  template<typename O>
+  using StorageKey = typename registry<O>::key<Storage>;
+
+  template<typename O>
+  Storage *get_storage (O *owner, const StorageKey<O> &key) const
+  {
+    Storage *r = key.get (owner);
+    if (r == nullptr)
+      {
+	r = new Storage();
+	key.set (owner, r);
+      }
+    return r;
+  }
+
+  Storage *get_storage(struct objfile* objf) const
+  {
+    return get_storage(objf, m_key_for_objf);
+  }
+
+  Storage *get_storage(struct gdbarch* arch) const
+  {
+    return get_storage(arch, m_key_for_arch);
+  }
+
+  const registry<objfile>::key<Storage> m_key_for_objf;
+  const registry<gdbarch>::key<Storage> m_key_for_arch;
+};
+
+/* Default invalidator for Python objects.  */
+template <typename P, typename V, V* P::*val_slot>
+struct gdbpy_default_invalidator
+{
+  void operator() (P *obj)
+  {
+    obj->*val_slot = nullptr;
+  }
+};
+
+/* A "storage" implementation suitable for temporary (on-demand) objects.  */
+template <typename P, 
+          typename V, 
+          V* P::*val_slot, 
+	  typename Invalidator = gdbpy_default_invalidator<P, V, val_slot>>
+class gdbpy_tracking_registry_storage
+{
+public:
+  using obj_type = P;
+  using val_type = V;
+
+  void add (obj_type *obj)
+  {
+    gdb_assert (obj != nullptr && obj->*val_slot != nullptr);
+
+    m_objects.insert (obj);    
+  }
+
+  void remove (obj_type *obj)
+  {
+    gdb_assert (obj != nullptr && obj->*val_slot != nullptr);
+
+    m_objects.erase (obj);    
+  }
+
+  obj_type *lookup (val_type *val) const
+  {
+    gdb_assert_not_reached ("Should not be used.");
+  }
+
+  ~gdbpy_tracking_registry_storage ()
+  {
+    Invalidator invalidate;
+    gdbpy_enter enter_py;
+
+    for (auto each : m_objects)
+      invalidate (each);
+    m_objects.clear ();
+  }
+
+private:
+  std::unordered_set<obj_type *> m_objects;
+};
+
+/* A "storage" implementation suitable for memoized (interned) Python objects.
+
+   This implementation retains a reference to Python object for a long as 
+   the owning object exists.  When the owning object is about to be freed,
+   registered Python objects are invalidated and reference dropped.  */
+template <typename P, 
+          typename V, 
+          V* P::*val_slot, 
+	  typename Invalidator = gdbpy_default_invalidator<P, V, val_slot>>
+class gdbpy_memoizing_registry_storage
+{
+public:
+  using obj_type = P;
+  using val_type = V;
+
+  void add (obj_type *obj)
+  {
+    gdb_assert (obj != nullptr && obj->*val_slot != nullptr);
+
+    m_objects[obj->*val_slot] = obj;
+    Py_INCREF (obj);
+  }
+
+  void remove (obj_type *obj)
+  {
+    gdb_assert_not_reached ("Should not be used.");
+  }
+
+  obj_type *lookup (val_type *val) const
+  {
+    auto result = m_objects.find (val);
+    if (result != m_objects.end ())
+      return result->second;
+    else
+      return nullptr;
+  }
+
+  ~gdbpy_memoizing_registry_storage ()
+  {
+    Invalidator invalidate;
+    gdbpy_enter enter_py;
+
+    for (auto each : m_objects)
+      {
+	invalidate (each.second);
+	Py_DECREF (each.second);
+      }
+
+    m_objects.clear ();
+  }
+
+private:
+  std::unordered_map<val_type *, obj_type *> m_objects;
+};
+
 #endif /* GDB_PYTHON_PYTHON_INTERNAL_H */
-- 
2.45.2


  parent reply	other threads:[~2025-01-27 10:48 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-27 10:44 [RFC 0/9] Attempt to unify Python object's lifecycle Jan Vrany
2025-01-27 10:44 ` [RFC 1/9] gdb/python: preserve identity for gdb.Symtab objects Jan Vrany
2025-01-27 10:44 ` [RFC 2/9] gdb/python: preserve identity for gdb.Symbol objects Jan Vrany
2025-02-20 19:22   ` Tom Tromey
2025-01-27 10:44 ` [RFC 3/9] gdb/python: do not hold on gdb.Symtab object from gdb.Symtab_and_line Jan Vrany
2025-01-27 10:44 ` [RFC 4/9] gdb/python: preserve identity for gdb.Type objects Jan Vrany
2025-01-27 10:44 ` Jan Vrany [this message]
2025-02-20 19:28   ` [RFC 5/9] gdb/python: introduce gdbpy_registry Tom Tromey
2025-01-27 10:44 ` [RFC 6/9] gdb/python: convert gdb.Symbol to use gdbpy_registry Jan Vrany
2025-01-27 10:44 ` [RFC 7/9] gdb/python: convert gdb.Type " Jan Vrany
2025-01-27 10:44 ` [RFC 8/9] gdb/python: convert gdb.Symtab " Jan Vrany
2025-01-27 10:44 ` [RFC 9/9] gdb/python: convert gdb.Symtab_and_line " Jan Vrany
2025-02-18 11:15 ` [PING] Re: [RFC 0/9] Attempt to unify Python object's lifecycle Jan Vraný
2025-02-19 21:00 ` Simon Marchi
2025-02-20 17:50   ` Jan Vraný
2025-02-20 19:18 ` Tom Tromey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250127104435.823519-6-jan.vrany@labware.com \
    --to=jan.vrany@labware.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox