public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 00/11] Various thread lists optimizations
@ 2021-06-22 16:56 Simon Marchi
  2021-06-22 16:56 ` [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter Simon Marchi
                   ` (11 more replies)
  0 siblings, 12 replies; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches

This series contains various optimizations written after profiling
ROCm-GDB [1].  A typical ROCm program may have thousands of threads
(waves), so everything that iterates often on the whole thread list pops
up on the profile report.

Conceptually, the optimizations are:

 - maintain a ptid -> thread map in each inferior, to speed up the
   thread lookups per ptid with find_thread_ptid
 - make all_matching_threads_iterator a bit smarter, depending on the
   filter_ptid passed, it can avoid iterating on all threads.  When the
   filter_ptid points to a specific thread, it can use find_thread_ptid
   to directly find the thread of interest, which is fast thanks to the
   previous point
 - maintain a per-process-target list of threads that are resumed and
   have a pending event.  This helps speed up two hot path cases: when
   we check if we want to re-enable commit-resumed, and when we want to
   fetch a pending event.

Patches up to and including patch 6 are groundwork.  Notably, patch 2
(thanks to Pedro) adds an intrusive_list type, which allows using
intrusive linked lists in a very C++-y way without writing a ton of
boilerplate.  This list type is useful for the patch that maintains a
list of resumed threads with pending events, but we also converted the
per-inferior thread list, the inferior list and the thread
step-over-list to use it.  It helped iron out a few bugs and ensure the
list works well for our purposes.

When debugging a ROCm test program whose threads continuously hit a
condition breakpoint evaluation to false, the speedup observed with
ROCm-GDB is around 5x (a run takes about 30 seconds before and 6 seconds
after).

With x86-64 / Linux, it's less noticeable, because the time we spend in
syscalls to fetch events and resume threads still dominates.  Still,
when trying a program with 1000 threads hitting 100 times each a false
conditional breakpoint, it goes from ~20 seconds to ~16 seconds.  But I
don't really expect anything that noticeable in everyday use cases
though.

[1] https://github.com/ROCm-Developer-Tools/ROCgdb

Pedro Alves (2):
  gdb: introduce intrusive_list, make thread_info use it
  gdb: make inferior_list use intrusive_list

Simon Marchi (9):
  gdb: introduce iterator_range, remove next_adapter
  gdb: use intrusive list for step-over chain
  gdb: add setter / getter for thread_info resumed state
  gdb: make thread_info::suspend private, add getters / setters
  gdb: maintain per-process-target list of resumed threads with pending
    wait status
  gdb: optimize check for resumed threads with pending wait status in
    maybe_set_commit_resumed_all_targets
  gdb: optimize selection of resumed thread with pending event
  gdb: maintain ptid -> thread map, optimize find_thread_ptid
  gdb: optimize all_matching_threads_iterator

 gdb/Makefile.in                            |   1 +
 gdb/ada-tasks.c                            |   4 +-
 gdb/breakpoint.c                           |   7 +-
 gdb/breakpoint.h                           |  10 +-
 gdb/elf-none-tdep.c                        |   2 +-
 gdb/fbsd-tdep.c                            |   6 +-
 gdb/gcore.c                                |   4 +-
 gdb/gdb-gdb.py.in                          |  91 ++-
 gdb/gdb_bfd.h                              |   4 +-
 gdb/gdbthread.h                            | 192 ++++-
 gdb/infcmd.c                               |  33 +-
 gdb/inferior-iter.h                        |  94 +--
 gdb/inferior.c                             | 105 ++-
 gdb/inferior.h                             |  34 +-
 gdb/inflow.c                               |   2 +-
 gdb/infrun.c                               | 476 ++++++------
 gdb/infrun.h                               |   4 +-
 gdb/linux-fork.c                           |   3 +-
 gdb/linux-nat.c                            |  12 +-
 gdb/linux-tdep.c                           |   2 +-
 gdb/objfiles.h                             |   6 +-
 gdb/process-stratum-target.c               |  77 ++
 gdb/process-stratum-target.h               |  26 +
 gdb/progspace.c                            |  11 +-
 gdb/progspace.h                            |  45 +-
 gdb/psymtab.h                              |   2 +-
 gdb/python/py-inferior.c                   |   2 +-
 gdb/record-btrace.c                        |   3 +-
 gdb/record-full.c                          |   3 +-
 gdb/regcache.c                             |   6 +-
 gdb/remote.c                               |  68 +-
 gdb/scoped-mock-context.h                  |  15 +-
 gdb/solist.h                               |   2 +
 gdb/symtab.h                               |  15 +-
 gdb/thread-iter.c                          | 147 +++-
 gdb/thread-iter.h                          |  61 +-
 gdb/thread.c                               | 216 +++---
 gdb/top.h                                  |   6 +-
 gdb/unittests/intrusive_list-selftests.c   | 818 +++++++++++++++++++++
 gdbsupport/intrusive_list.h                | 586 +++++++++++++++
 gdbsupport/iterator-range.h                |  60 ++
 gdbsupport/next-iterator.h                 |  32 +-
 gdbsupport/reference-to-pointer-iterator.h |  82 +++
 43 files changed, 2574 insertions(+), 801 deletions(-)
 create mode 100644 gdb/unittests/intrusive_list-selftests.c
 create mode 100644 gdbsupport/intrusive_list.h
 create mode 100644 gdbsupport/iterator-range.h
 create mode 100644 gdbsupport/reference-to-pointer-iterator.h

-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
@ 2021-06-22 16:56 ` Simon Marchi
  2021-07-05 15:41   ` Pedro Alves
  2021-06-22 16:56 ` [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it Simon Marchi
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches

I was always a bit confused by next_adapter, because it kind of mixes
the element type and the iterator type.  In reality, it is not much more
than a class that wraps two iterators (begin and end).  However, it
assumes that:

 - you can construct the begin iterator by passing a pointer to the
   first element of the iterable
 - you can default-construct iterator to make the end iterator

I think that by generalizing it a little bit, we can re-use it at more
places.

Rename it to "iterator_range".  I think it describes a bit better: it's
a range made by wrapping a begin and end iterator.  Move it to its own
file, since it's not related to next_iterator anymore.

iterator_range has two constructors.  The variadic one, where arguments
are forwarded to construct the underlying begin iterator.  The end
iterator is constructed through default construction.  This is a
generalization of what we have today.

There is another constructor which receives already constructed begin
and end iterators, useful if the end iterator can't be obtained by
default-construction.  Or, if you wanted to make a range that does not
end at the end of the container, you could pass any iterator as the
"end".

This generalization allows removing some "range" classes, like
all_inferiors_range.  These classes existed only to pass some arguments
when constructing the begin iterator.  With iterator_range, those same
arguments are passed to the iterator_range constructed and then
forwarded to the constructed begin iterator.

There is a small functional difference in how iterator_range works
compared to next_adapter.  next_adapter stored the pointer it received
as argument and constructeur an iterator in the `begin` method.
iterator_range constructs the begin iterator and stores it as a member.
Its `begin` method returns a copy of that iterator.

With just iterator_range, uses of next_adapter<foo> would be replaced
with:

  using foo_iterator = next_iterator<foo>;
  using foo_range = iterator_range<foo_iterator>;

However, I added a `next_range` wrapper as a direct replacement for
next_adapter<foo>.  IMO, next_range is a slightly better name than
next_adapter.

The rest of the changes are applications of this new class.

gdbsupport/ChangeLog:

	* next-iterator.h (class next_adapter): Remove.
	* iterator-range.h: New.

gdb/ChangeLog:

	* breakpoint.h (bp_locations_range): Remove.
	(bp_location_range): New.
	(struct breakpoint) <locations>: Adjust type.
	(breakpoint_range): Use iterator_range.
	(tracepoint_range): Use iterator_range.
	* breakpoint.c (breakpoint::locations): Adjust return type.
	* gdb_bfd.h (gdb_bfd_section_range): Use iterator_range.
	* gdbthread.h (all_threads_safe): Pass argument to
	all_threads_safe_range.
	* inferior-iter.h (all_inferiors_range): Use iterator_range.
	(all_inferiors_safe_range): Use iterator_range.
	(all_non_exited_inferiors_range): Use iterator_range.
	* inferior.h (all_inferiors, all_non_exited_inferiors): Pass
	inferior_list as argument.
	* objfiles.h (struct objfile) <compunits_range>: Remove.
	<compunits>: Return compunit_symtab_range.
	* progspace.h (unwrapping_objfile_iterator)
	<unwrapping_objfile_iterator>: Take parameter by value.
	(unwrapping_objfile_range): Use iterator_range.
	(struct program_space) <objfiles_range>: Define with "using".
	<objfiles>: Adjust.
	<objfiles_safe_range>: Define with "using".
	<objfiles_safe>: Adjust.
	<solibs>: Return so_list_range, define here.
	* progspace.c (program_space::solibs): Remove.
	* psymtab.h (class psymtab_storage) <partial_symtab_iterator>:
	New.
	<partial_symtab_range>: Use iterator_range.
	* solist.h (so_list_range): New.
	* symtab.h (compunit_symtab_range):
	New.
	(symtab_range): New.
	(compunit_filetabs): Change to a function.
	* thread-iter.h (inf_threads_range,
	inf_non_exited_threads_range, safe_inf_threads_range,
	all_threads_safe_range): Use iterator_range.
	* top.h (ui_range): New.
	(all_uis): Use ui_range.

Change-Id: Ib7a9d2a3547f45f01aa1c6b24536ba159db9b854
---
 gdb/breakpoint.c            |  4 +--
 gdb/breakpoint.h            | 10 +++---
 gdb/gdb_bfd.h               |  4 +--
 gdb/gdbthread.h             |  2 +-
 gdb/inferior-iter.h         | 69 +++++--------------------------------
 gdb/inferior.h              |  4 +--
 gdb/objfiles.h              |  6 ++--
 gdb/progspace.c             |  8 -----
 gdb/progspace.h             | 45 ++++++++----------------
 gdb/psymtab.h               |  2 +-
 gdb/solist.h                |  2 ++
 gdb/symtab.h                | 15 ++++----
 gdb/thread-iter.h           | 27 +++------------
 gdb/top.h                   |  6 ++--
 gdbsupport/iterator-range.h | 60 ++++++++++++++++++++++++++++++++
 gdbsupport/next-iterator.h  | 32 +++--------------
 16 files changed, 124 insertions(+), 172 deletions(-)
 create mode 100644 gdbsupport/iterator-range.h

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 0595c6f8cbd4..7d7e299ad5ff 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -12282,9 +12282,9 @@ breakpoint::~breakpoint ()
 
 /* See breakpoint.h.  */
 
-bp_locations_range breakpoint::locations ()
+bp_location_range breakpoint::locations ()
 {
-  return bp_locations_range (this->loc);
+  return bp_location_range (this->loc);
 }
 
 static struct bp_location *
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index e40504f14ed3..fe68730f1651 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -30,6 +30,8 @@
 #include "gdbsupport/array-view.h"
 #include "gdbsupport/filtered-iterator.h"
 #include "gdbsupport/function-view.h"
+#include "gdbsupport/next-iterator.h"
+#include "gdbsupport/iterator-range.h"
 #include "gdbsupport/refcounted-object.h"
 #include "gdbsupport/safe-iterator.h"
 #include "cli/cli-script.h"
@@ -706,7 +708,7 @@ extern bool target_exact_watchpoints;
 
 /* bp_location linked list range.  */
 
-using bp_locations_range = next_adapter<bp_location>;
+using bp_location_range = next_range<bp_location>;
 
 /* Note that the ->silent field is not currently used by any commands
    (though the code is in there if it was to be, and set_raw_breakpoint
@@ -721,7 +723,7 @@ struct breakpoint
   virtual ~breakpoint ();
 
   /* Return a range of this breakpoint's locations.  */
-  bp_locations_range locations ();
+  bp_location_range locations ();
 
   /* Methods associated with this breakpoint.  */
   const breakpoint_ops *ops = NULL;
@@ -1715,7 +1717,7 @@ using breakpoint_iterator = next_iterator<breakpoint>;
 
 /* Breakpoint linked list range.  */
 
-using breakpoint_range = next_adapter<breakpoint, breakpoint_iterator>;
+using breakpoint_range = iterator_range<breakpoint_iterator>;
 
 /* Return a range to iterate over all breakpoints.  */
 
@@ -1746,7 +1748,7 @@ using tracepoint_iterator
 
 /* Breakpoint linked list range, filtering to only keep tracepoints.  */
 
-using tracepoint_range = next_adapter<breakpoint, tracepoint_iterator>;
+using tracepoint_range = iterator_range<tracepoint_iterator>;
 
 /* Return a range to iterate over all tracepoints.  */
 
diff --git a/gdb/gdb_bfd.h b/gdb/gdb_bfd.h
index d366fc0b1195..156c2760f11c 100644
--- a/gdb/gdb_bfd.h
+++ b/gdb/gdb_bfd.h
@@ -23,6 +23,7 @@
 #include "registry.h"
 #include "gdbsupport/byte-vector.h"
 #include "gdbsupport/gdb_ref_ptr.h"
+#include "gdbsupport/iterator-range.h"
 #include "gdbsupport/next-iterator.h"
 
 DECLARE_REGISTRY (bfd);
@@ -208,8 +209,7 @@ gdb_bfd_ref_ptr gdb_bfd_open_from_target_memory (CORE_ADDR addr, ULONGEST size,
        ... use SECT ...
  */
 
-using gdb_bfd_section_iterator = next_iterator<asection>;
-using gdb_bfd_section_range = next_adapter<asection, gdb_bfd_section_iterator>;
+using gdb_bfd_section_range = next_range<asection>;
 
 static inline gdb_bfd_section_range
 gdb_bfd_sections (bfd *abfd)
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index eef37f79e6ad..f19c88f9bb4a 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -567,7 +567,7 @@ all_non_exited_threads (process_stratum_target *proc_target = nullptr,
 inline all_threads_safe_range
 all_threads_safe ()
 {
-  return {};
+  return all_threads_safe_range (all_threads_iterator::begin_t {});
 }
 
 extern int thread_count (process_stratum_target *proc_target);
diff --git a/gdb/inferior-iter.h b/gdb/inferior-iter.h
index 2ae0a0957d93..f999150a7b55 100644
--- a/gdb/inferior-iter.h
+++ b/gdb/inferior-iter.h
@@ -90,6 +90,11 @@ class all_inferiors_iterator
   inferior *m_inf;
 };
 
+/* A range adapter that makes it possible to iterate over all
+   inferiors with range-for.  */
+
+using all_inferiors_range = iterator_range<all_inferiors_iterator>;
+
 /* Filter for filtered_iterator.  Filters out exited inferiors.  */
 
 struct exited_inferior_filter
@@ -106,21 +111,10 @@ using all_non_exited_inferiors_iterator
   = filtered_iterator<all_inferiors_iterator, exited_inferior_filter>;
 
 /* A range adapter that makes it possible to iterate over all
-   inferiors with range-for.  */
-struct all_inferiors_range
-{
-  all_inferiors_range (process_stratum_target *proc_target = nullptr)
-    : m_filter_target (proc_target)
-  {}
+   non-exited inferiors with range-for.  */
 
-  all_inferiors_iterator begin () const
-  { return all_inferiors_iterator (m_filter_target, inferior_list); }
-  all_inferiors_iterator end () const
-  { return all_inferiors_iterator (); }
-
-private:
-  process_stratum_target *m_filter_target;
-};
+using all_non_exited_inferiors_range
+  = iterator_range<all_non_exited_inferiors_iterator>;
 
 /* Iterate over all inferiors, safely.  */
 
@@ -131,51 +125,6 @@ using all_inferiors_safe_iterator
    inferiors with range-for "safely".  I.e., it is safe to delete the
    currently-iterated inferior.  */
 
-struct all_inferiors_safe_range
-{
-  explicit all_inferiors_safe_range (process_stratum_target *filter_target)
-    : m_filter_target (filter_target)
-  {}
-
-  all_inferiors_safe_range ()
-    : m_filter_target (nullptr)
-  {}
-
-  all_inferiors_safe_iterator begin () const
-  {
-    return (all_inferiors_safe_iterator
-	    (all_inferiors_iterator (m_filter_target, inferior_list)));
-  }
-
-  all_inferiors_safe_iterator end () const
-  { return all_inferiors_safe_iterator (); }
-
-private:
-  /* The filter.  */
-  process_stratum_target *m_filter_target;
-};
-
-/* A range adapter that makes it possible to iterate over all
-   non-exited inferiors with range-for.  */
-
-struct all_non_exited_inferiors_range
-{
-  explicit all_non_exited_inferiors_range (process_stratum_target *filter_target)
-    : m_filter_target (filter_target)
-  {}
-
-  all_non_exited_inferiors_range ()
-    : m_filter_target (nullptr)
-  {}
-
-  all_non_exited_inferiors_iterator begin () const
-  { return all_non_exited_inferiors_iterator (m_filter_target, inferior_list); }
-  all_non_exited_inferiors_iterator end () const
-  { return all_non_exited_inferiors_iterator (); }
-
-private:
-  /* The filter.  */
-  process_stratum_target *m_filter_target;
-};
+using all_inferiors_safe_range = iterator_range<all_inferiors_safe_iterator>;
 
 #endif /* !defined (INFERIOR_ITER_H) */
diff --git a/gdb/inferior.h b/gdb/inferior.h
index f61b5889e858..c63990aabe0e 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -681,7 +681,7 @@ all_inferiors_safe ()
 inline all_inferiors_range
 all_inferiors (process_stratum_target *proc_target = nullptr)
 {
-  return all_inferiors_range (proc_target);
+  return all_inferiors_range (proc_target, inferior_list);
 }
 
 /* Return a range that can be used to walk over all inferiors with PID
@@ -690,7 +690,7 @@ all_inferiors (process_stratum_target *proc_target = nullptr)
 inline all_non_exited_inferiors_range
 all_non_exited_inferiors (process_stratum_target *proc_target = nullptr)
 {
-  return all_non_exited_inferiors_range (proc_target);
+  return all_non_exited_inferiors_range (proc_target, inferior_list);
 }
 
 /* Prune away automatically added inferiors that aren't required
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 5a8a782a6462..91557b89f6b5 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -476,14 +476,12 @@ struct objfile
 
   DISABLE_COPY_AND_ASSIGN (objfile);
 
-  typedef next_adapter<struct compunit_symtab> compunits_range;
-
   /* A range adapter that makes it possible to iterate over all
      compunits in one objfile.  */
 
-  compunits_range compunits ()
+  compunit_symtab_range compunits ()
   {
-    return compunits_range (compunit_symtabs);
+    return compunit_symtab_range (compunit_symtabs);
   }
 
   /* A range adapter that makes it possible to iterate over all
diff --git a/gdb/progspace.c b/gdb/progspace.c
index ebbc784dcfbb..e3cc6929a236 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -215,14 +215,6 @@ program_space::remove_objfile (struct objfile *objfile)
 
 /* See progspace.h.  */
 
-next_adapter<struct so_list>
-program_space::solibs () const
-{
-  return next_adapter<struct so_list> (this->so_list);
-}
-
-/* See progspace.h.  */
-
 void
 program_space::exec_close ()
 {
diff --git a/gdb/progspace.h b/gdb/progspace.h
index 790684743d87..fb348ca7539a 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -25,6 +25,7 @@
 #include "gdb_bfd.h"
 #include "gdbsupport/gdb_vecs.h"
 #include "registry.h"
+#include "solist.h"
 #include "gdbsupport/next-iterator.h"
 #include "gdbsupport/safe-iterator.h"
 #include <list>
@@ -59,8 +60,8 @@ class unwrapping_objfile_iterator
   typedef typename objfile_list::iterator::iterator_category iterator_category;
   typedef typename objfile_list::iterator::difference_type difference_type;
 
-  unwrapping_objfile_iterator (const objfile_list::iterator &iter)
-    : m_iter (iter)
+  unwrapping_objfile_iterator (objfile_list::iterator iter)
+    : m_iter (std::move (iter))
   {
   }
 
@@ -89,29 +90,7 @@ class unwrapping_objfile_iterator
 
 /* A range that returns unwrapping_objfile_iterators.  */
 
-struct unwrapping_objfile_range
-{
-  typedef unwrapping_objfile_iterator iterator;
-
-  unwrapping_objfile_range (objfile_list &ol)
-    : m_list (ol)
-  {
-  }
-
-  iterator begin () const
-  {
-    return iterator (m_list.begin ());
-  }
-
-  iterator end () const
-  {
-    return iterator (m_list.end ());
-  }
-
-private:
-
-  objfile_list &m_list;
-};
+using unwrapping_objfile_range = iterator_range<unwrapping_objfile_iterator>;
 
 /* A program space represents a symbolic view of an address space.
    Roughly speaking, it holds all the data associated with a
@@ -222,7 +201,7 @@ struct program_space
      a program space.  */
   ~program_space ();
 
-  typedef unwrapping_objfile_range objfiles_range;
+  using objfiles_range = unwrapping_objfile_range;
 
   /* Return an iterable object that can be used to iterate over all
      objfiles.  The basic use is in a foreach, like:
@@ -230,10 +209,12 @@ struct program_space
      for (objfile *objf : pspace->objfiles ()) { ... }  */
   objfiles_range objfiles ()
   {
-    return unwrapping_objfile_range (objfiles_list);
+    return objfiles_range
+      (unwrapping_objfile_iterator (objfiles_list.begin ()),
+       unwrapping_objfile_iterator (objfiles_list.end ()));
   }
 
-  typedef basic_safe_range<objfiles_range> objfiles_safe_range;
+  using objfiles_safe_range = basic_safe_range<objfiles_range>;
 
   /* An iterable object that can be used to iterate over all objfiles.
      The basic use is in a foreach, like:
@@ -244,7 +225,10 @@ struct program_space
      deleted during iteration.  */
   objfiles_safe_range objfiles_safe ()
   {
-    return objfiles_safe_range (objfiles_list);
+    return objfiles_safe_range
+      (objfiles_range
+	 (unwrapping_objfile_iterator (objfiles_list.begin ()),
+	  unwrapping_objfile_iterator (objfiles_list.end ())));
   }
 
   /* Add OBJFILE to the list of objfiles, putting it just before
@@ -270,7 +254,8 @@ struct program_space
      program space.  Use it like:
 
      for (so_list *so : pspace->solibs ()) { ... }  */
-  next_adapter<struct so_list> solibs () const;
+  so_list_range solibs () const
+  { return so_list_range (this->so_list); }
 
   /* Close and clear exec_bfd.  If we end up with no target sections
      to read memory from, this unpushes the exec_ops target.  */
diff --git a/gdb/psymtab.h b/gdb/psymtab.h
index 522ccf3a12a4..7cd3e95b179b 100644
--- a/gdb/psymtab.h
+++ b/gdb/psymtab.h
@@ -104,7 +104,7 @@ class psymtab_storage
 
   void install_psymtab (partial_symtab *pst);
 
-  typedef next_adapter<struct partial_symtab> partial_symtab_range;
+  using partial_symtab_range = next_range<partial_symtab>;
 
   /* A range adapter that makes it possible to iterate over all
      psymtabs in one objfile.  */
diff --git a/gdb/solist.h b/gdb/solist.h
index d44e6f4048cc..6033751dd695 100644
--- a/gdb/solist.h
+++ b/gdb/solist.h
@@ -163,6 +163,8 @@ struct target_so_ops
   void (*handle_event) (void);
 };
 
+using so_list_range = next_range<so_list>;
+
 /* Free the memory associated with a (so_list *).  */
 void free_so (struct so_list *so);
 
diff --git a/gdb/symtab.h b/gdb/symtab.h
index a5d0168faf08..759cf4d772c3 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -33,6 +33,7 @@
 #include "gdbsupport/gdb_optional.h"
 #include "gdbsupport/gdb_string_view.h"
 #include "gdbsupport/next-iterator.h"
+#include "gdbsupport/iterator-range.h"
 #include "completer.h"
 #include "gdb-demangle.h"
 
@@ -1521,6 +1522,8 @@ struct compunit_symtab
   struct compunit_symtab *user;
 };
 
+using compunit_symtab_range = next_range<compunit_symtab>;
+
 #define COMPUNIT_OBJFILE(cust) ((cust)->objfile)
 #define COMPUNIT_FILETABS(cust) ((cust)->filetabs)
 #define COMPUNIT_DEBUGFORMAT(cust) ((cust)->debugformat)
@@ -1536,13 +1539,13 @@ struct compunit_symtab
 /* A range adapter to allowing iterating over all the file tables
    within a compunit.  */
 
-struct compunit_filetabs : public next_adapter<struct symtab>
+using symtab_range = next_range<symtab>;
+
+static inline symtab_range
+compunit_filetabs (compunit_symtab *cu)
 {
-  compunit_filetabs (struct compunit_symtab *cu)
-    : next_adapter<struct symtab> (cu->filetabs)
-  {
-  }
-};
+  return symtab_range (cu->filetabs);
+}
 
 /* Return the primary symtab of CUST.  */
 
diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
index 853098620e8e..098af0f3241b 100644
--- a/gdb/thread-iter.h
+++ b/gdb/thread-iter.h
@@ -170,43 +170,24 @@ using safe_inf_threads_iterator
 /* A range adapter that makes it possible to iterate over all threads
    of an inferior with range-for.  */
 
-using inf_threads_range
-  = next_adapter<thread_info, inf_threads_iterator>;
+using inf_threads_range = iterator_range<inf_threads_iterator>;
 
 /* A range adapter that makes it possible to iterate over all
    non-exited threads of an inferior with range-for.  */
 
 using inf_non_exited_threads_range
-  = next_adapter<thread_info, inf_non_exited_threads_iterator>;
+  = iterator_range<inf_non_exited_threads_iterator>;
 
 /* A range adapter that makes it possible to iterate over all threads
    of an inferior with range-for, safely.  */
 
-using safe_inf_threads_range
-  = next_adapter<thread_info, safe_inf_threads_iterator>;
-
-/* A range adapter that makes it possible to iterate over all threads
-   of all inferiors with range-for.  */
-
-struct all_threads_range
-{
-  all_threads_iterator begin () const
-  { return all_threads_iterator (all_threads_iterator::begin_t {}); }
-  all_threads_iterator end () const
-  { return all_threads_iterator (); }
-};
+using safe_inf_threads_range = iterator_range<safe_inf_threads_iterator>;
 
 /* A range adapter that makes it possible to iterate over all threads
    with range-for "safely".  I.e., it is safe to delete the
    currently-iterated thread.  */
 
-struct all_threads_safe_range
-{
-  all_threads_safe_iterator begin () const
-  { return all_threads_safe_iterator (all_threads_iterator::begin_t {}); }
-  all_threads_safe_iterator end () const
-  { return all_threads_safe_iterator (); }
-};
+using all_threads_safe_range = iterator_range<all_threads_safe_iterator>;
 
 /* A range adapter that makes it possible to iterate over all threads
    that match a PTID filter with range-for.  */
diff --git a/gdb/top.h b/gdb/top.h
index 56bd06c698be..ef88ca024e4e 100644
--- a/gdb/top.h
+++ b/gdb/top.h
@@ -204,11 +204,13 @@ class switch_thru_all_uis
 #define SWITCH_THRU_ALL_UIS()		\
   for (switch_thru_all_uis stau_state; !stau_state.done (); stau_state.next ())
 
+using ui_range = next_range<ui>;
+
 /* An adapter that can be used to traverse over all UIs.  */
 static inline
-next_adapter<ui> all_uis ()
+ui_range all_uis ()
 {
-  return next_adapter<ui> (ui_list);
+  return ui_range (ui_list);
 }
 
 /* Register the UI's input file descriptor in the event loop.  */
diff --git a/gdbsupport/iterator-range.h b/gdbsupport/iterator-range.h
new file mode 100644
index 000000000000..2a718e6c83a1
--- /dev/null
+++ b/gdbsupport/iterator-range.h
@@ -0,0 +1,60 @@
+/* A range adapter that wraps begin / end iterators.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program 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 of the License, or
+   (at your option) any later version.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDBSUPPORT_ITERATOR_RANGE_H
+#define GDBSUPPORT_ITERATOR_RANGE_H
+
+/* A wrapper that allows using ranged for-loops on a range described by two
+   iterators.  */
+
+template <typename IteratorType>
+struct iterator_range
+{
+  using iterator = IteratorType;
+
+  /* Create an iterator_range using BEGIN as the begin iterator.
+
+     Assume that the end iterator can be default-constructed.  */
+  template <typename... Args>
+  iterator_range (Args &&...args)
+    : m_begin (std::forward<Args> (args)...)
+  {}
+
+  /* Create an iterator range using explicit BEGIN and END iterators.  */
+  template <typename... Args>
+  iterator_range (IteratorType begin, IteratorType end)
+    : m_begin (std::move (begin)), m_end (std::move (end))
+  {}
+
+  /* Need these as the variadic constructor would be a better match
+     otherwise.  */
+  iterator_range (iterator_range &) = default;
+  iterator_range (const iterator_range &) = default;
+  iterator_range (iterator_range &&) = default;
+
+  IteratorType begin () const
+  { return m_begin; }
+
+  IteratorType end () const
+  { return m_end; }
+
+private:
+  IteratorType m_begin, m_end;
+};
+
+#endif /* GDBSUPPORT_ITERATOR_RANGE_H */
diff --git a/gdbsupport/next-iterator.h b/gdbsupport/next-iterator.h
index 501752499846..72b241ea2b71 100644
--- a/gdbsupport/next-iterator.h
+++ b/gdbsupport/next-iterator.h
@@ -19,6 +19,8 @@
 #ifndef COMMON_NEXT_ITERATOR_H
 #define COMMON_NEXT_ITERATOR_H
 
+#include "gdbsupport/iterator-range.h"
+
 /* An iterator that uses the 'next' field of a type to iterate.  This
    can be used with various GDB types that are stored as linked
    lists.  */
@@ -70,33 +72,9 @@ struct next_iterator
   T *m_item;
 };
 
-/* A range adapter that allows iterating over a linked list.  */
-
-template<typename T, typename Iterator = next_iterator<T>>
-class next_adapter
-{
-public:
-
-  explicit next_adapter (T *item)
-    : m_item (item)
-  {
-  }
-
-  using iterator = Iterator;
-
-  iterator begin () const
-  {
-    return iterator (m_item);
-  }
-
-  iterator end () const
-  {
-    return iterator ();
-  }
+/* A convenience wrapper to make a range type around a next_iterator.  */
 
-private:
-
-  T *m_item;
-};
+template <typename T>
+using next_range = iterator_range<next_iterator<T>>;
 
 #endif /* COMMON_NEXT_ITERATOR_H */
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
  2021-06-22 16:56 ` [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter Simon Marchi
@ 2021-06-22 16:56 ` Simon Marchi
  2021-06-22 23:13   ` Lancelot SIX
  2021-07-05 15:44   ` Pedro Alves
  2021-06-22 16:56 ` [PATCH 03/11] gdb: make inferior_list use intrusive_list Simon Marchi
                   ` (9 subsequent siblings)
  11 siblings, 2 replies; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches; +Cc: Pedro Alves, Simon Marchi

From: Pedro Alves <pedro@palves.net>

GDB currently has several objects that are put in a singly linked list,
by having the object's type have a "next" pointer directly.  For
example, struct thread_info and struct inferior.  Because these are
simply-linked lists, and we don't keep track of a "tail" pointer, when
we want to append a new element on the list, we need to walk the whole
list to find the current tail.  It would be nice to get rid of that
walk.  Removing elements from such lists also requires a walk, to find
the "previous" position relative to the element being removed.  To
eliminate the need for that walk, we could make those lists
doubly-linked, by adding a "prev" pointer alongside "next".  It would be
nice to avoid the boilerplace associated with maintaining such a list
manually, though.  That is what the new intrusive_list type addresses.

With an intrusive list, it's also possible to move items out of the
list without destroying them, which is interesting in our case for
example for threads, when we exit them, but can't destroy them
immediately.  We currently keep exited threads on the thread list, but
we could change that which would simplify some things.

Note that with std::list, element removal is O(N).  I.e., with
std::list, we need to walk the list to find the iterator pointing to
the position to remove.  However, we could store a list iterator
inside the object as soon as we put the object in the list, to address
it, because std::list iterators are not invalidated when other
elements are added/removed.  However, if you need to put the same
object in more than one list, then std::list<object> doesn't work.
You need to instead use std::list<object *>, which is less efficient
for requiring extra memory allocations.  For an example of an object
in multiple lists, see the step_over_next/step_over_prev fields in
thread_info:

  /* Step-over chain.  A thread is in the step-over queue if these are
     non-NULL.  If only a single thread is in the chain, then these
     fields point to self.  */
  struct thread_info *step_over_prev = NULL;
  struct thread_info *step_over_next = NULL;

The new intrusive_list type gives us the advantages of an intrusive
linked list, while avoiding the boilerplate associated with manually
maintaining it.

intrusive_list's API follows the standard container interface, and thus
std::list's interface.  It is based the API of Boost's intrusive list,
here:

 https://www.boost.org/doc/libs/1_73_0/doc/html/boost/intrusive/list.html

Our implementation is relatively simple, while Boost's is complicated
and intertwined due to a lot of customization options, which our version
doesn't have.

The easiest way to use an intrusive_list is to make the list's element
type inherit from intrusive_node.  This adds a prev/next pointers to
the element type.  However, to support putting the same object in more
than one list, intrusive_list supports putting the "node" info as a
field member, so you can have more than one such nodes, one per list.

As a first guinea pig, this patch makes the per-inferior thread list use
intrusive_list using the base class method.

Unlike Boost's implementation, ours is not a circular list.  An earlier
version of the patch was circular: the instrusive_list type included an
intrusive_list_node "head".  In this design, a node contained pointers
to the previous and next nodes, not the previous and next elements.
This wasn't great for when debugging GDB with GDB, as it was difficult
to get from a pointer to the node to a pointer to the element.  With the
design proposed in this patch, nodes contain pointers to the previous
and next elements, making it easy to traverse the list by hand and
inspect each element.

The intrusive_list object contains pointers to the first and last
elements of the list.  They are nullptr if the list is empty.
Each element's node contains a pointer to the previous and next
elements.  The first element's previous pointer is nullptr and the last
element's next pointer is nullptr.  Therefore, if there's a single
element in the list, both its previous and next pointers are nullptr.
To differentiate such an element from an element that is not linked into
a list, the previous and next pointers contain a special value (-1) when
the node is not linked.  This is necessary to be able to reliably tell
if a given node is currently linked or not.

A begin() iterator points to the first item in the list.  An end()
iterator contains nullptr.  This makes iteration until end naturally
work, as advancing past the last element will make the iterator contain
nullptr, making it equal to the end iterator.  If the list is empty,
a begin() iterator will contain nullptr from the start, and therefore be
immediately equal to the end.

Iterating on an intrusive_list yields references to objects (e.g.
`thread_info&`).  The rest of GDB currently expects iterators and ranges
to yield pointers (e.g. `thread_info*`).  To bridge the gap, add the
reference_to_pointer_iterator type.  It is used to define
inf_threads_iterator.

Add a Python pretty-printer, to help inspecting intrusive lists when
debugging GDB with GDB.  Here's an example of the output:

    (top-gdb) p current_inferior_.m_obj.thread_list
    $1 = intrusive list of thread_info = {0x61700002c000, 0x617000069080, 0x617000069400, 0x61700006d680, 0x61700006eb80}

It's not possible with current master, but with this patch [1] that I
hope will be merged eventually, it's possible to index the list and
access the pretty-printed value's children:

    (top-gdb) p current_inferior_.m_obj.thread_list[1]
    $2 = (thread_info *) 0x617000069080
    (top-gdb) p current_inferior_.m_obj.thread_list[1].ptid
    $3 = {
      m_pid = 406499,
      m_lwp = 406503,
      m_tid = 0
    }

Even though iterating the list in C++ yields references, the Python
pretty-printer yields pointers.  The reason for this is that the output
of printing the thread list above would be unreadable, IMO, if each
thread_info object was printed in-line, since they contain so much
information.  I think it's more useful to print pointers, and let the
user drill down as needed.

[1] https://sourceware.org/pipermail/gdb-patches/2021-April/178050.html

YYYY-MM-DD  Pedro Alves  <pedro@palves.net>
YYYY-MM-DD  Simon Marchi  <simon.marchi@efficios.com>

gdbsupport/ChangeLog:

	* intrusive_list.h: New.
	* filtered-iterator.h (class filtered_iterator): Add
	constructor.
	* safe-iterator.h (class basic_safe_iterator): Add defaulted
	constructors.

gdb/ChangeLog:

	* Makefile.in (SELFTESTS_SRCS): Add
	unittests/intrusive_list-selftests.c.
	* gdbthread.h (class thread_info): Inherit from
	intrusive_list_node.
	<next>: Remove.
	(set_thread_exited): New declaration.
	* thread.c (set_thread_exited): Make non-static.
	(init_thread_list): Use clear_thread_list.
	(new_thread): Adjust.
	(delete_thread_1): Adjust.
	(first_thread_of_inferior): Adjust.
	(update_threads_executing): Adjust.
	* inferior.h (class inferior) <thread_list>: Change type to
	intrusive_list.
	<threads, non_exited_threads, threads_safe>: Adjust.
	<clear_thread_list>: New.
	* inferior.c (inferior::clear_thread_list): New.
	(delete_inferior): Use clear_thread_list.
	(exit_inferior_1): Use clear_thread_list.
	* scoped-mock-context.h (struct scoped_mock_context)
	<restore_thread_list>: Remove.
	<scoped_mock_context>: Insert mock thread in mock inferior's
	thread list.
	* thread-iter.h (inf_threads_iterator, inf_threads_range,
	inf_non_exited_threads_range, safe_inf_threads_range): Change type.
	(inf_threads_iterator): Define using next_iterator_intrusive.
	* thread-iter.c (all_threads_iterator::all_threads_iterator):
	Adjust.
	(all_threads_iterator::advance): Adjust.
	(all_matching_threads_iterator::all_matching_threads_iterator):
	Adjust.
	(all_matching_threads_iterator::advance): Adjust.
	* unittests/intrusive_list-selftests.c: New.

Co-Authored-By: Simon Marchi <simon.marchi@efficios.com>
Change-Id: I3412a14dc77f25876d742dab8f44e0ba7c7586c0
---
 gdb/Makefile.in                            |   1 +
 gdb/gdb-gdb.py.in                          |  91 ++-
 gdb/gdbthread.h                            |  13 +-
 gdb/inferior.c                             |  24 +-
 gdb/inferior.h                             |  14 +-
 gdb/scoped-mock-context.h                  |   4 +-
 gdb/thread-iter.c                          |  53 +-
 gdb/thread-iter.h                          |   5 +-
 gdb/thread.c                               |  61 +-
 gdb/unittests/intrusive_list-selftests.c   | 734 +++++++++++++++++++++
 gdbsupport/intrusive_list.h                | 559 ++++++++++++++++
 gdbsupport/reference-to-pointer-iterator.h |  79 +++
 12 files changed, 1554 insertions(+), 84 deletions(-)
 create mode 100644 gdb/unittests/intrusive_list-selftests.c
 create mode 100644 gdbsupport/intrusive_list.h
 create mode 100644 gdbsupport/reference-to-pointer-iterator.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 1bc97885536e..b405950783c2 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -449,6 +449,7 @@ SELFTESTS_SRCS = \
 	unittests/function-view-selftests.c \
 	unittests/gdb_tilde_expand-selftests.c \
 	unittests/gmp-utils-selftests.c \
+	unittests/intrusive_list-selftests.c \
 	unittests/lookup_name_info-selftests.c \
 	unittests/memory-map-selftests.c \
 	unittests/memrange-selftests.c \
diff --git a/gdb/gdb-gdb.py.in b/gdb/gdb-gdb.py.in
index af9fcfedc2f3..7ff91bd779f9 100644
--- a/gdb/gdb-gdb.py.in
+++ b/gdb/gdb-gdb.py.in
@@ -276,16 +276,101 @@ class CoreAddrPrettyPrinter:
         return hex(int(self._val))
 
 
+class IntrusiveListPrinter:
+    """Print a struct intrusive_list."""
+
+    def __init__(self, val):
+        self._val = val
+
+        # Type of linked items.
+        self._item_type = self._val.type.template_argument(0)
+        self._node_ptr_type = gdb.lookup_type(
+            f"intrusive_list_node<{self._item_type.tag}>"
+        ).pointer()
+
+        # Type of value -> node converter.
+        self._conv_type = self._val.type.template_argument(1)
+
+        if self._uses_member_node():
+            # The second template argument of intrusive_member_node is a member
+            # pointer value.  Its value is the offset of the node member in the
+            # enclosing type.
+            member_node_ptr = self._conv_type.template_argument(1)
+            member_node_ptr = member_node_ptr.cast(gdb.lookup_type("int"))
+            self._member_node_offset = int(member_node_ptr)
+
+            # This is only needed in _as_node_ptr if using a member node.  Look it
+            # up here so we only do it once.
+            self._char_ptr_type = gdb.lookup_type("char").pointer()
+
+    def display_hint(self):
+        return "array"
+
+    # Return True if the list items use a node as a member.  Return False if
+    # they use a node as a base class.
+    def _uses_member_node(self):
+        if self._conv_type.name.startswith("intrusive_member_node<"):
+            return True
+        elif self._conv_type.name.startswith("intrusive_base_node<"):
+            return False
+        else:
+            raise RuntimeError(
+                f"Unexpected intrusive_list value -> node converter type: {self._conv_type.name}"
+            )
+
+    def to_string(self):
+        s = f"intrusive list of {self._item_type}"
+
+        if self._uses_member_node():
+            node_member = self._conv_type.template_argument(1)
+            s += f", linked through {node_member}"
+
+        return s
+
+    # Given ELEM_PTR, a pointer to a list element, return a pointer to the
+    # corresponding intrusive_list_node.
+    def _as_node_ptr(self, elem_ptr):
+        assert elem_ptr.type.code == gdb.TYPE_CODE_PTR
+
+        if self._uses_member_node():
+            # Node as a memer: add the member node offset from to the element's
+            # address to get the member node's address.
+            elem_char_ptr = elem_ptr.cast(self._char_ptr_type)
+            node_char_ptr = elem_char_ptr + self._member_node_offset
+            return node_char_ptr.cast(self._node_ptr_type)
+        else:
+            # Node as a base: just casting from node pointer to item pointer
+            # will adjust the pointer value.
+            return elem_ptr.cast(self._node_ptr_type)
+
+    # Generator that yields one tuple per list item.
+    def _children_generator(self):
+        elem_ptr = self._val["m_front"]
+        idx = 0
+        while elem_ptr != 0:
+            yield (str(idx), elem_ptr)
+            node_ptr = self._as_node_ptr(elem_ptr)
+            elem_ptr = node_ptr["next"]
+            idx += 1
+
+    def children(self):
+        return self._children_generator()
+
+
 def type_lookup_function(val):
     """A routine that returns the correct pretty printer for VAL
     if appropriate.  Returns None otherwise.
     """
-    if val.type.tag == "type":
+    tag = val.type.tag
+    name = val.type.name
+    if tag == "type":
         return StructTypePrettyPrinter(val)
-    elif val.type.tag == "main_type":
+    elif tag == "main_type":
         return StructMainTypePrettyPrinter(val)
-    elif val.type.name == "CORE_ADDR":
+    elif name == "CORE_ADDR":
         return CoreAddrPrettyPrinter(val)
+    elif tag is not None and tag.startswith("intrusive_list<"):
+        return IntrusiveListPrinter(val)
     return None
 
 
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index f19c88f9bb4a..0e28b1de9ff0 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -33,6 +33,7 @@ struct symtab;
 #include "gdbsupport/common-gdbthread.h"
 #include "gdbsupport/forward-scope-exit.h"
 #include "displaced-stepping.h"
+#include "gdbsupport/intrusive_list.h"
 
 struct inferior;
 struct process_stratum_target;
@@ -222,9 +223,12 @@ struct private_thread_info
    delete_thread).  All other thread references are considered weak
    references.  Placing a thread in the thread list is an implicit
    strong reference, and is thus not accounted for in the thread's
-   refcount.  */
+   refcount.
 
-class thread_info : public refcounted_object
+   The intrusive_list_node base links threads in a per-inferior list.  */
+
+class thread_info : public refcounted_object,
+		    public intrusive_list_node<thread_info>
 {
 public:
   explicit thread_info (inferior *inf, ptid_t ptid);
@@ -235,7 +239,6 @@ class thread_info : public refcounted_object
   /* Mark this thread as running and notify observers.  */
   void set_running (bool running);
 
-  struct thread_info *next = NULL;
   ptid_t ptid;			/* "Actual process id";
 				    In fact, this may be overloaded with 
 				    kernel thread id, etc.  */
@@ -435,6 +438,10 @@ extern void delete_thread (struct thread_info *thread);
    this thread belonged to has already exited, for example.  */
 extern void delete_thread_silent (struct thread_info *thread);
 
+/* Mark the thread exited, but don't delete it or remove it from the
+   inferior thread list.  */
+extern void set_thread_exited (thread_info *tp, bool silent);
+
 /* Delete a step_resume_breakpoint from the thread database.  */
 extern void delete_step_resume_breakpoint (struct thread_info *);
 
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 059839ec9626..693b196556d5 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -163,6 +163,19 @@ add_inferior (int pid)
   return inf;
 }
 
+/* See inferior.h.  */
+
+void
+inferior::clear_thread_list (bool silent)
+{
+  thread_list.clear_and_dispose ([=] (thread_info *thr)
+    {
+      set_thread_exited (thr, silent);
+      if (thr->deletable ())
+	delete thr;
+    });
+}
+
 void
 delete_inferior (struct inferior *todel)
 {
@@ -177,8 +190,7 @@ delete_inferior (struct inferior *todel)
   if (!inf)
     return;
 
-  for (thread_info *tp : inf->threads_safe ())
-    delete_thread_silent (tp);
+  inf->clear_thread_list (true);
 
   if (infprev)
     infprev->next = inf->next;
@@ -209,13 +221,7 @@ exit_inferior_1 (struct inferior *inftoex, int silent)
   if (!inf)
     return;
 
-  for (thread_info *tp : inf->threads_safe ())
-    {
-      if (silent)
-	delete_thread_silent (tp);
-      else
-	delete_thread (tp);
-    }
+  inf->clear_thread_list (silent);
 
   gdb::observers::inferior_exit.notify (inf);
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index c63990aabe0e..2ae9f9a5f9c4 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -390,8 +390,8 @@ class inferior : public refcounted_object
   /* Pointer to next inferior in singly-linked list of inferiors.  */
   struct inferior *next = NULL;
 
-  /* This inferior's thread list.  */
-  thread_info *thread_list = nullptr;
+  /* This inferior's thread list, sorted by creation order.  */
+  intrusive_list<thread_info> thread_list;
 
   /* Returns a range adapter covering the inferior's threads,
      including exited threads.  Used like this:
@@ -400,7 +400,7 @@ class inferior : public refcounted_object
 	 { .... }
   */
   inf_threads_range threads ()
-  { return inf_threads_range (this->thread_list); }
+  { return inf_threads_range (this->thread_list.begin ()); }
 
   /* Returns a range adapter covering the inferior's non-exited
      threads.  Used like this:
@@ -409,7 +409,7 @@ class inferior : public refcounted_object
 	 { .... }
   */
   inf_non_exited_threads_range non_exited_threads ()
-  { return inf_non_exited_threads_range (this->thread_list); }
+  { return inf_non_exited_threads_range (this->thread_list.begin ()); }
 
   /* Like inferior::threads(), but returns a range adapter that can be
      used with range-for, safely.  I.e., it is safe to delete the
@@ -420,7 +420,11 @@ class inferior : public refcounted_object
 	 delete f;
   */
   inline safe_inf_threads_range threads_safe ()
-  { return safe_inf_threads_range (this->thread_list); }
+  { return safe_inf_threads_range (this->thread_list.begin ()); }
+
+  /* Delete all threads in the thread list.  If SILENT, exit threads
+     silently.  */
+  void clear_thread_list (bool silent);
 
   /* Continuations-related methods.  A continuation is an std::function
      to be called to finish the execution of a command when running
diff --git a/gdb/scoped-mock-context.h b/gdb/scoped-mock-context.h
index 8d295ba1bbe6..37ffe5117423 100644
--- a/gdb/scoped-mock-context.h
+++ b/gdb/scoped-mock-context.h
@@ -44,9 +44,6 @@ struct scoped_mock_context
 
   scoped_restore_current_pspace_and_thread restore_pspace_thread;
 
-  scoped_restore_tmpl<thread_info *> restore_thread_list
-    {&mock_inferior.thread_list, &mock_thread};
-
   /* Add the mock inferior to the inferior list so that look ups by
      target+ptid can find it.  */
   scoped_restore_tmpl<inferior *> restore_inferior_list
@@ -54,6 +51,7 @@ struct scoped_mock_context
 
   explicit scoped_mock_context (gdbarch *gdbarch)
   {
+    mock_inferior.thread_list.push_back (mock_thread);
     mock_inferior.gdbarch = gdbarch;
     mock_inferior.aspace = mock_pspace.aspace;
     mock_inferior.pspace = &mock_pspace;
diff --git a/gdb/thread-iter.c b/gdb/thread-iter.c
index 012ca5fab090..a1cdd0206bd4 100644
--- a/gdb/thread-iter.c
+++ b/gdb/thread-iter.c
@@ -27,8 +27,15 @@ all_threads_iterator::all_threads_iterator (begin_t)
 {
   /* Advance M_INF/M_THR to the first thread's position.  */
   for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
-    if ((m_thr = m_inf->thread_list) != NULL)
-      return;
+    {
+      auto thr_iter = m_inf->thread_list.begin ();
+      if (thr_iter != m_inf->thread_list.end ())
+	{
+	  m_thr = &*thr_iter;
+	  return;
+	}
+    }
+  m_thr = nullptr;
 }
 
 /* See thread-iter.h.  */
@@ -36,6 +43,8 @@ all_threads_iterator::all_threads_iterator (begin_t)
 void
 all_threads_iterator::advance ()
 {
+  intrusive_list<thread_info>::iterator thr_iter (m_thr);
+
   /* The loop below is written in the natural way as-if we'd always
      start at the beginning of the inferior list.  This fast forwards
      the algorithm to the actual current position.  */
@@ -43,14 +52,17 @@ all_threads_iterator::advance ()
 
   for (; m_inf != NULL; m_inf = m_inf->next)
     {
-      m_thr = m_inf->thread_list;
-      while (m_thr != NULL)
+      thr_iter = m_inf->thread_list.begin ();
+      while (thr_iter != m_inf->thread_list.end ())
 	{
+	  m_thr = &*thr_iter;
 	  return;
 	start:
-	  m_thr = m_thr->next;
+	  ++thr_iter;
 	}
     }
+
+  m_thr = nullptr;
 }
 
 /* See thread-iter.h.  */
@@ -74,12 +86,18 @@ all_matching_threads_iterator::all_matching_threads_iterator
   gdb_assert ((filter_target == nullptr && filter_ptid == minus_one_ptid)
 	      || filter_target->stratum () == process_stratum);
 
-  m_thr = nullptr;
   for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
     if (m_inf_matches ())
-      for (m_thr = m_inf->thread_list; m_thr != NULL; m_thr = m_thr->next)
-	if (m_thr->ptid.matches (m_filter_ptid))
-	  return;
+      for (auto thr_iter = m_inf->thread_list.begin ();
+	   thr_iter != m_inf->thread_list.end ();
+	   ++thr_iter)
+	if (thr_iter->ptid.matches (m_filter_ptid))
+	  {
+	    m_thr = &*thr_iter;
+	    return;
+	  }
+
+  m_thr = nullptr;
 }
 
 /* See thread-iter.h.  */
@@ -87,6 +105,8 @@ all_matching_threads_iterator::all_matching_threads_iterator
 void
 all_matching_threads_iterator::advance ()
 {
+  intrusive_list<thread_info>::iterator thr_iter (m_thr);
+
   /* The loop below is written in the natural way as-if we'd always
      start at the beginning of the inferior list.  This fast forwards
      the algorithm to the actual current position.  */
@@ -95,13 +115,18 @@ all_matching_threads_iterator::advance ()
   for (; m_inf != NULL; m_inf = m_inf->next)
     if (m_inf_matches ())
       {
-	m_thr = m_inf->thread_list;
-	while (m_thr != NULL)
+	thr_iter = m_inf->thread_list.begin ();
+	while (thr_iter != m_inf->thread_list.end ())
 	  {
-	    if (m_thr->ptid.matches (m_filter_ptid))
-	      return;
+	    if (thr_iter->ptid.matches (m_filter_ptid))
+	      {
+		m_thr = &*thr_iter;
+		return;
+	      }
 	  start:
-	    m_thr = m_thr->next;
+	    ++thr_iter;
 	  }
       }
+
+  m_thr = nullptr;
 }
diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
index 098af0f3241b..2e43034550e8 100644
--- a/gdb/thread-iter.h
+++ b/gdb/thread-iter.h
@@ -20,13 +20,16 @@
 #define THREAD_ITER_H
 
 #include "gdbsupport/filtered-iterator.h"
+#include "gdbsupport/iterator-range.h"
 #include "gdbsupport/next-iterator.h"
+#include "gdbsupport/reference-to-pointer-iterator.h"
 #include "gdbsupport/safe-iterator.h"
 
 /* A forward iterator that iterates over a given inferior's
    threads.  */
 
-using inf_threads_iterator = next_iterator<thread_info>;
+using inf_threads_iterator
+  = reference_to_pointer_iterator<intrusive_list<thread_info>::iterator>;
 
 /* A forward iterator that iterates over all threads of all
    inferiors.  */
diff --git a/gdb/thread.c b/gdb/thread.c
index f850f05ad48e..89f51c01c993 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -177,9 +177,9 @@ clear_thread_inferior_resources (struct thread_info *tp)
   clear_inline_frame_state (tp);
 }
 
-/* Set the TP's state as exited.  */
+/* See gdbthread.h.  */
 
-static void
+void
 set_thread_exited (thread_info *tp, bool silent)
 {
   /* Dead threads don't need to step-over.  Remove from chain.  */
@@ -203,17 +203,8 @@ init_thread_list (void)
 {
   highest_thread_num = 0;
 
-  for (thread_info *tp : all_threads_safe ())
-    {
-      inferior *inf = tp->inf;
-
-      if (tp->deletable ())
-	delete tp;
-      else
-	set_thread_exited (tp, 1);
-
-      inf->thread_list = NULL;
-    }
+  for (inferior *inf : all_inferiors ())
+    inf->clear_thread_list (true);
 }
 
 /* Allocate a new thread of inferior INF with target id PTID and add
@@ -224,21 +215,7 @@ new_thread (struct inferior *inf, ptid_t ptid)
 {
   thread_info *tp = new thread_info (inf, ptid);
 
-  if (inf->thread_list == NULL)
-    inf->thread_list = tp;
-  else
-    {
-      struct thread_info *last;
-
-      for (last = inf->thread_list; last->next != NULL; last = last->next)
-	gdb_assert (ptid != last->ptid
-		    || last->state == THREAD_EXITED);
-
-      gdb_assert (ptid != last->ptid
-		  || last->state == THREAD_EXITED);
-
-      last->next = tp;
-    }
+  inf->thread_list.push_back (*tp);
 
   return tp;
 }
@@ -462,29 +439,18 @@ delete_thread_1 (thread_info *thr, bool silent)
 {
   gdb_assert (thr != nullptr);
 
-  struct thread_info *tp, *tpprev = NULL;
-
-  for (tp = thr->inf->thread_list; tp; tpprev = tp, tp = tp->next)
-    if (tp == thr)
-      break;
+  set_thread_exited (thr, silent);
 
-  if (!tp)
-    return;
-
-  set_thread_exited (tp, silent);
-
-  if (!tp->deletable ())
+  if (!thr->deletable ())
     {
        /* Will be really deleted some other time.  */
        return;
      }
 
-  if (tpprev)
-    tpprev->next = tp->next;
-  else
-    tp->inf->thread_list = tp->next;
+  auto it = thr->inf->thread_list.iterator_to (*thr);
+  thr->inf->thread_list.erase (it);
 
-  delete tp;
+  delete thr;
 }
 
 /* See gdbthread.h.  */
@@ -629,7 +595,10 @@ in_thread_list (process_stratum_target *targ, ptid_t ptid)
 thread_info *
 first_thread_of_inferior (inferior *inf)
 {
-  return inf->thread_list;
+  if (inf->thread_list.empty ())
+    return nullptr;
+
+  return &inf->thread_list.front ();
 }
 
 thread_info *
@@ -2018,7 +1987,7 @@ update_threads_executing (void)
 
       /* If the process has no threads, then it must be we have a
 	 process-exit event pending.  */
-      if (inf->thread_list == NULL)
+      if (inf->thread_list.empty ())
 	{
 	  targ->threads_executing = true;
 	  return;
diff --git a/gdb/unittests/intrusive_list-selftests.c b/gdb/unittests/intrusive_list-selftests.c
new file mode 100644
index 000000000000..3ccff54b5ff9
--- /dev/null
+++ b/gdb/unittests/intrusive_list-selftests.c
@@ -0,0 +1,734 @@
+/* Tests fpr intrusive double linked list for GDB, the GNU debugger.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program 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 of the License, or
+   (at your option) any later version.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+
+#include "gdbsupport/intrusive_list.h"
+#include "gdbsupport/selftest.h"
+#include <unordered_set>
+
+/* An item type using intrusive_list_node by inheriting from it and its
+   corresponding list type.  Put another base before intrusive_list_node
+   so that a pointer to the node != a pointer to the item.  */
+
+struct other_base
+{
+  int n = 1;
+};
+
+struct item_with_base : public other_base,
+			public intrusive_list_node<item_with_base>
+{
+  item_with_base (const char *name)
+    : name (name)
+  {}
+
+  const char *const name;
+};
+
+using item_with_base_list = intrusive_list<item_with_base>;
+
+/* An item type using intrusive_list_node as a field and its corresponding
+   list type.  Put the other field before the node, so that a pointer to the
+   node != a pointer to the item.  */
+
+struct item_with_member
+{
+  item_with_member (const char *name)
+    : name (name)
+  {}
+
+  const char *const name;
+  intrusive_list_node<item_with_member> node;
+};
+
+using item_with_member_node
+  = intrusive_member_node<item_with_member, &item_with_member::node>;
+using item_with_member_list
+  = intrusive_list<item_with_member, item_with_member_node>;
+
+/* To run all tests using both the base and member methods, all tests are
+   declared in this templated class, which is instantiated once for each
+   list type.  */
+
+template <typename ListType>
+struct intrusive_list_test
+{
+  using item_type = typename ListType::value_type;
+
+  /* Verify that LIST contains exactly the items in EXPECTED.
+
+     Traverse the list forward and backwards to exercise all links.  */
+
+  static void
+  verify_items (const ListType &list,
+		gdb::array_view<const typename ListType::value_type *> expected)
+  {
+    int i = 0;
+
+    for (typename ListType::iterator it = list.begin ();
+	 it != list.end ();
+	 ++it)
+      {
+	const item_type &item = *it;
+
+	gdb_assert (i < expected.size ());
+	gdb_assert (&item == expected[i]);
+
+	++i;
+      }
+
+    gdb_assert (i == expected.size ());
+
+    for (typename ListType::reverse_iterator it = list.rbegin ();
+	 it != list.rend ();
+	 ++it)
+      {
+	const item_type &item = *it;
+
+	--i;
+
+	gdb_assert (i >= 0);
+	gdb_assert (&item == expected[i]);
+      }
+
+    gdb_assert (i == 0);
+  }
+
+  static void
+  test_move_constructor ()
+  {
+    {
+      /* Other list is not empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      ListType list2 (std::move (list1));
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a, &b, &c};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Other list contains 1 element.  */
+      item_type a ("a");
+      ListType list1;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+
+      ListType list2 (std::move (list1));
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Other list is empty.  */
+      ListType list1;
+      std::vector<const item_type *> expected;
+
+      ListType list2 (std::move (list1));
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+  }
+
+  static void
+  test_move_assignment ()
+  {
+    {
+      /* Both lists are not empty.  */
+      item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      list2.push_back (d);
+      list2.push_back (e);
+
+      list2 = std::move (list1);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a, &b, &c};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* rhs list is empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list2.push_back (a);
+      list2.push_back (b);
+      list2.push_back (c);
+
+      list2 = std::move (list1);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* lhs list is empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      list2 = std::move (list1);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a, &b, &c};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Both lists contain 1 item.  */
+      item_type a ("a"), b ("b");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list2.push_back (b);
+
+      list2 = std::move (list1);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Both lists are empty.  */
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list2 = std::move (list1);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+  }
+
+  static void
+  test_swap ()
+  {
+    {
+      /* Two non-empty lists.  */
+      item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      list2.push_back (d);
+      list2.push_back (e);
+
+      std::swap (list1, list2);
+
+      expected = {&d, &e};
+      verify_items (list1, expected);
+
+      expected = {&a, &b, &c};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Other is empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      std::swap (list1, list2);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a, &b, &c};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* *this is empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list2.push_back (a);
+      list2.push_back (b);
+      list2.push_back (c);
+
+      std::swap (list1, list2);
+
+      expected = {&a, &b, &c};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Both lists empty.  */
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      std::swap (list1, list2);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Swap one element twice.  */
+      item_type a ("a");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+
+      std::swap (list1, list2);
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {&a};
+      verify_items (list2, expected);
+
+      std::swap (list1, list2);
+
+      expected = {&a};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+  }
+
+  static void
+  test_front_back ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    const ListType &clist = list;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    gdb_assert (&list.front () == &a);
+    gdb_assert (&clist.front () == &a);
+    gdb_assert (&list.back () == &c);
+    gdb_assert (&clist.back () == &c);
+  }
+
+  static void
+  test_push_front ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+
+    expected = {};
+    verify_items (list, expected);
+
+    list.push_front (a);
+    expected = {&a};
+    verify_items (list, expected);
+
+    list.push_front (b);
+    expected = {&b, &a};
+    verify_items (list, expected);
+
+    list.push_front (c);
+    expected = {&c, &b, &a};
+    verify_items (list, expected);
+  }
+
+  static void
+  test_push_back ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+
+    expected = {};
+    verify_items (list, expected);
+
+    list.push_back (a);
+    expected = {&a};
+    verify_items (list, expected);
+
+    list.push_back (b);
+    expected = {&a, &b};
+    verify_items (list, expected);
+
+    list.push_back (c);
+    expected = {&a, &b, &c};
+    verify_items (list, expected);
+  }
+
+  static void
+  test_insert ()
+  {
+    std::vector<const item_type *> expected;
+
+    {
+      /* Insert at beginning.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list;
+
+
+      list.insert (list.begin (), a);
+      expected = {&a};
+      verify_items (list, expected);
+
+      list.insert (list.begin (), b);
+      expected = {&b, &a};
+      verify_items (list, expected);
+
+      list.insert (list.begin (), c);
+      expected = {&c, &b, &a};
+      verify_items (list, expected);
+    }
+
+    {
+      /* Insert at end.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list;
+
+
+      list.insert (list.end (), a);
+      expected = {&a};
+      verify_items (list, expected);
+
+      list.insert (list.end (), b);
+      expected = {&a, &b};
+      verify_items (list, expected);
+
+      list.insert (list.end (), c);
+      expected = {&a, &b, &c};
+      verify_items (list, expected);
+    }
+
+    {
+      /* Insert in the middle.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list;
+
+      list.push_back (a);
+      list.push_back (b);
+
+      list.insert (list.iterator_to (b), c);
+      expected = {&a, &c, &b};
+      verify_items (list, expected);
+    }
+
+    {
+      /* Insert in empty list. */
+      item_type a ("a");
+      ListType list;
+
+      list.insert (list.end (), a);
+      expected = {&a};
+      verify_items (list, expected);
+    }
+  }
+
+  static void
+  test_pop_front ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    list.pop_front ();
+    expected = {&b, &c};
+    verify_items (list, expected);
+
+    list.pop_front ();
+    expected = {&c};
+    verify_items (list, expected);
+
+    list.pop_front ();
+    expected = {};
+    verify_items (list, expected);
+  }
+
+  static void
+  test_pop_back ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    list.pop_back();
+    expected = {&a, &b};
+    verify_items (list, expected);
+
+    list.pop_back ();
+    expected = {&a};
+    verify_items (list, expected);
+
+    list.pop_back ();
+    expected = {};
+    verify_items (list, expected);
+  }
+
+  static void
+  test_erase ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    list.erase (list.iterator_to (b));
+    expected = {&a, &c};
+    verify_items (list, expected);
+
+    list.erase (list.iterator_to (c));
+    expected = {&a};
+    verify_items (list, expected);
+
+    list.erase (list.iterator_to (a));
+    expected = {};
+    verify_items (list, expected);
+  }
+
+  static void
+  test_clear ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    list.clear ();
+    expected = {};
+    verify_items (list, expected);
+
+    /* Verify idempotency.  */
+    list.clear ();
+    expected = {};
+    verify_items (list, expected);
+  }
+
+  static void
+  test_clear_and_dispose ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    std::vector<const item_type *> expected;
+    std::unordered_set<const item_type *> disposer_seen;
+    int disposer_calls = 0;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    auto disposer = [&] (const item_type *item)
+      {
+	disposer_seen.insert (item);
+	disposer_calls++;
+      };
+    list.clear_and_dispose (disposer);
+
+    expected = {};
+    verify_items (list, expected);
+    gdb_assert (disposer_calls == 3);
+    gdb_assert (disposer_seen.find (&a) != disposer_seen.end ());
+    gdb_assert (disposer_seen.find (&b) != disposer_seen.end ());
+    gdb_assert (disposer_seen.find (&c) != disposer_seen.end ());
+
+    /* Verify idempotency.  */
+    list.clear_and_dispose (disposer);
+    gdb_assert (disposer_calls == 3);
+  }
+
+  static void
+  test_empty ()
+  {
+    item_type a ("a");
+    ListType list;
+
+    gdb_assert (list.empty ());
+    list.push_back (a);
+    gdb_assert (!list.empty ());
+    list.erase (list.iterator_to (a));
+    gdb_assert (list.empty ());
+  }
+
+  static void
+  test_begin_end ()
+  {
+    item_type a ("a"), b ("b"), c ("c");
+    ListType list;
+    const ListType &clist = list;
+
+    list.push_back (a);
+    list.push_back (b);
+    list.push_back (c);
+
+    gdb_assert (&*list.begin () == &a);
+    gdb_assert (&*list.cbegin () == &a);
+    gdb_assert (&*clist.begin () == &a);
+    gdb_assert (&*list.rbegin () == &c);
+    gdb_assert (&*list.crbegin () == &c);
+    gdb_assert (&*clist.rbegin () == &c);
+
+    /* At least check that they compile.  */
+    list.end ();
+    list.cend ();
+    clist.end ();
+    list.rend ();
+    list.crend ();
+    clist.end ();
+  }
+};
+
+template <typename ListType>
+static void
+test_intrusive_list ()
+{
+  intrusive_list_test<ListType> tests;
+
+  tests.test_move_constructor ();
+  tests.test_move_assignment ();
+  tests.test_swap ();
+  tests.test_front_back ();
+  tests.test_push_front ();
+  tests.test_push_back ();
+  tests.test_insert ();
+  tests.test_pop_front ();
+  tests.test_pop_back ();
+  tests.test_erase ();
+  tests.test_clear ();
+  tests.test_clear_and_dispose ();
+  tests.test_empty ();
+  tests.test_begin_end ();
+}
+
+static void
+test_node_is_linked ()
+{
+  {
+    item_with_base a ("a");
+    item_with_base_list list;
+
+    gdb_assert (!a.is_linked ());
+    list.push_back (a);
+    gdb_assert (a.is_linked ());
+    list.pop_back ();
+    gdb_assert (!a.is_linked ());
+  }
+
+  {
+    item_with_member a ("a");
+    item_with_member_list list;
+
+    gdb_assert (!a.node.is_linked ());
+    list.push_back (a);
+    gdb_assert (a.node.is_linked ());
+    list.pop_back ();
+    gdb_assert (!a.node.is_linked ());
+  }
+}
+
+static void
+test_intrusive_list ()
+{
+  test_intrusive_list<item_with_base_list> ();
+  test_intrusive_list<item_with_member_list> ();
+  test_node_is_linked ();
+}
+
+void _initialize_intrusive_list_selftests ();
+void
+_initialize_intrusive_list_selftests ()
+{
+  selftests::register_test
+    ("intrusive_list", test_intrusive_list);
+}
diff --git a/gdbsupport/intrusive_list.h b/gdbsupport/intrusive_list.h
new file mode 100644
index 000000000000..8e98e5b2c1a5
--- /dev/null
+++ b/gdbsupport/intrusive_list.h
@@ -0,0 +1,559 @@
+/* Intrusive double linked list for GDB, the GNU debugger.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program 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 of the License, or
+   (at your option) any later version.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDBSUPPORT_INTRUSIVE_LIST_H
+#define GDBSUPPORT_INTRUSIVE_LIST_H
+
+#define UNLINKED_VALUE ((T *) -1)
+
+/* A list node.  The elements put in an intrusive_list either inherit
+   from this, or have a field of this type.  */
+template<typename T>
+struct intrusive_list_node
+{
+  bool is_linked () const
+  {
+    return next != UNLINKED_VALUE;
+  }
+
+  T *next = UNLINKED_VALUE;
+  T *prev = UNLINKED_VALUE;
+};
+
+/* Follows a couple types used by intrusive_list as template parameter to find
+   the intrusive_list_node for a given element.  One for lists where the
+   elements inherit intrusive_list_node, and another for elements that keep the
+   node as member field.  */
+
+/* For element types that inherit from intrusive_list_node.  */
+
+template<typename T>
+struct intrusive_base_node
+{
+  static intrusive_list_node<T> *as_node (T *elem)
+  { return elem; }
+};
+
+/* For element types that keep the node as member field.  */
+
+template<typename T, intrusive_list_node<T> T::*MemberNode>
+struct intrusive_member_node
+{
+  static intrusive_list_node<T> *as_node (T *elem)
+  { return &(elem->*MemberNode); }
+};
+
+/* Common code for forward and reverse iterators.  */
+
+template<typename T, typename AsNode, typename SelfType>
+struct intrusive_list_base_iterator
+{
+  using self_type = SelfType;
+  using iterator_category = std::bidirectional_iterator_tag;
+  using value_type = T;
+  using pointer = T *;
+  using const_pointer = const T *;
+  using reference = T &;
+  using const_reference = const T &;
+  using difference_type = ptrdiff_t;
+  using size_type = size_t;
+  using node_type = intrusive_list_node<T>;
+
+  /* Create an iterator pointing to ELEM.  */
+  explicit intrusive_list_base_iterator (T *elem)
+    : m_elem (elem)
+  {}
+
+  /* Create a past-the-end iterator.  */
+  intrusive_list_base_iterator ()
+    : m_elem (nullptr)
+  {}
+
+  reference operator* () const
+  { return *m_elem; }
+
+  pointer operator-> () const
+  { return m_elem; }
+
+  bool operator== (const self_type &other) const
+  { return m_elem == other.m_elem; }
+
+  bool operator!= (const self_type &other) const
+  { return m_elem != other.m_elem; }
+
+protected:
+  static node_type *as_node (T *elem)
+  { return AsNode::as_node (elem); }
+
+  /* A past-end-the iterator points to the list's head.  */
+  pointer m_elem;
+};
+
+/* Forward iterator for an intrusive_list.  */
+
+template<typename T, typename AsNode = intrusive_base_node<T>>
+struct intrusive_list_iterator
+  : public intrusive_list_base_iterator
+	     <T, AsNode, intrusive_list_iterator<T, AsNode>>
+{
+  using base = intrusive_list_base_iterator
+		 <T, AsNode, intrusive_list_iterator<T, AsNode>>;
+  using self_type = typename base::self_type;
+  using node_type = typename base::node_type;
+
+  /* Inherit constructor and M_NODE visibility from base.  */
+  using base::base;
+  using base::m_elem;
+
+  self_type &operator++ ()
+  {
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->next;
+    return *this;
+  }
+
+  self_type operator++ (int)
+  {
+    self_type temp = *this;
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->next;
+    return temp;
+  }
+
+  self_type &operator-- ()
+  {
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->prev;
+    return *this;
+  }
+
+  self_type operator-- (int)
+  {
+    self_type temp = *this;
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->prev;
+    return temp;
+  }
+};
+
+/* Reverse iterator for an intrusive_list.  */
+
+template<typename T, typename AsNode = intrusive_base_node<T>>
+struct intrusive_list_reverse_iterator
+  : public intrusive_list_base_iterator
+	     <T, AsNode, intrusive_list_reverse_iterator<T, AsNode>>
+{
+  using base = intrusive_list_base_iterator
+		 <T, AsNode, intrusive_list_reverse_iterator<T, AsNode>>;
+  using self_type = typename base::self_type;
+
+  /* Inherit constructor and M_NODE visibility from base.  */
+  using base::base;
+  using base::m_elem;
+  using node_type = typename base::node_type;
+
+  self_type &operator++ ()
+  {
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->prev;
+    return *this;
+  }
+
+  self_type operator++ (int)
+  {
+    self_type temp = *this;
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->prev;
+    return temp;
+  }
+
+  self_type &operator-- ()
+  {
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->next;
+    return *this;
+  }
+
+  self_type operator-- (int)
+  {
+    self_type temp = *this;
+    node_type *node = this->as_node (m_elem);
+    m_elem = node->next;
+    return temp;
+  }
+};
+
+/* An intrusive double-linked list.
+
+   T is the type of the elements to link.  The type T must either:
+
+    - inherit from intrusive_list_node<T>
+    - have an intrusive_list_node<T> member
+
+   AsNode is a type with an as_node static method used to get a node from an
+   element.  If elements inherit from intrusive_list_node<T>, use the default
+   intrusive_base_node<T>.  If elements have an intrusive_list_node<T> member,
+   use:
+
+     instrusive_member_node<T, &T::member>
+
+   where `member` is the name of the member.  */
+
+template <typename T, typename AsNode = intrusive_base_node<T>>
+class intrusive_list
+{
+public:
+  using value_type = T;
+  using pointer = T *;
+  using const_pointer = const T *;
+  using reference = T &;
+  using const_reference = const T &;
+  using difference_type = ptrdiff_t;
+  using size_type = size_t;
+  using iterator = intrusive_list_iterator<T, AsNode>;
+  using reverse_iterator = intrusive_list_reverse_iterator<T, AsNode>;
+  using const_iterator = const intrusive_list_iterator<T, AsNode>;
+  using const_reverse_iterator
+    = const intrusive_list_reverse_iterator<T, AsNode>;
+  using node_type = intrusive_list_node<T>;
+
+  intrusive_list () = default;
+
+  ~intrusive_list ()
+  {
+    clear ();
+  }
+
+  intrusive_list (intrusive_list &&other)
+    : m_front (other.m_front),
+      m_back (other.m_back)
+  {
+    other.m_front = nullptr;
+    other.m_back = nullptr;
+  }
+
+  intrusive_list &operator= (intrusive_list &&other)
+  {
+    m_front = other.m_front;
+    m_back = other.m_back;
+    other.m_front = nullptr;
+    other.m_back = nullptr;
+
+    return *this;
+  }
+
+  void swap (intrusive_list &other)
+  {
+    std::swap (m_front, other.m_front);
+    std::swap (m_back, other.m_back);
+  }
+
+  iterator iterator_to (reference value)
+  {
+    return iterator (&value);
+  }
+
+  const_iterator iterator_to (const_reference value)
+  {
+    return const_iterator (&value);
+  }
+
+  reference front ()
+  {
+    gdb_assert (!this->empty ());
+    return *m_front;
+  }
+
+  const_reference front () const
+  {
+    gdb_assert (!this->empty ());
+    return *m_front;
+  }
+
+  reference back ()
+  {
+    gdb_assert (!this->empty ());
+    return *m_back;
+  }
+
+  const_reference back () const
+  {
+    gdb_assert (!this->empty ());
+    return *m_back;
+  }
+
+  void push_front (reference elem)
+  {
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+
+    gdb_assert (elem_node->next == UNLINKED_VALUE);
+    gdb_assert (elem_node->prev == UNLINKED_VALUE);
+
+    if (this->empty ())
+      this->push_empty (elem);
+    else
+      this->push_front_non_empty (elem);
+  }
+
+  void push_back (reference elem)
+  {
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+
+    gdb_assert (elem_node->next == UNLINKED_VALUE);
+    gdb_assert (elem_node->prev == UNLINKED_VALUE);
+
+    if (this->empty ())
+      this->push_empty (elem);
+    else
+      this->push_back_non_empty (elem);
+  }
+
+  /* Inserts ELEM before POS.  */
+  void insert (const_iterator pos, reference elem)
+  {
+    if (this->empty ())
+      return this->push_empty (elem);
+
+    if (pos == this->begin ())
+      return this->push_front_non_empty (elem);
+
+    if (pos == this->end ())
+      return this->push_back_non_empty (elem);
+
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+    T *pos_elem = &*pos;
+    intrusive_list_node<T> *pos_node = as_node (pos_elem);
+    T *prev_elem = pos_node->prev;
+    intrusive_list_node<T> *prev_node = as_node (prev_elem);
+
+    gdb_assert (elem_node->next == UNLINKED_VALUE);
+    gdb_assert (elem_node->prev == UNLINKED_VALUE);
+
+    elem_node->prev = prev_elem;
+    prev_node->next = &elem;
+    elem_node->next = pos_elem;
+    pos_node->prev = &elem;
+  }
+
+  void pop_front ()
+  {
+    gdb_assert (!this->empty ());
+    erase_element (*m_front);
+  }
+
+  void pop_back ()
+  {
+    gdb_assert (!this->empty ());
+    erase_element (*m_back);
+  }
+
+private:
+  /* Push ELEM in the list, knowing the list is empty.  */
+  void push_empty (T &elem)
+  {
+    gdb_assert (this->empty ());
+
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+
+    gdb_assert (elem_node->next == UNLINKED_VALUE);
+    gdb_assert (elem_node->prev == UNLINKED_VALUE);
+
+    m_front = &elem;
+    m_back = &elem;
+    elem_node->prev = nullptr;
+    elem_node->next = nullptr;
+  }
+
+  /* Push ELEM at the front of the list, knowing the list is not empty.  */
+  void push_front_non_empty (T &elem)
+  {
+    gdb_assert (!this->empty ());
+
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+    intrusive_list_node<T> *front_node = as_node (m_front);
+
+    gdb_assert (elem_node->next == UNLINKED_VALUE);
+    gdb_assert (elem_node->prev == UNLINKED_VALUE);
+
+    elem_node->next = m_front;
+    front_node->prev = &elem;
+    elem_node->prev = nullptr;
+    m_front = &elem;
+  }
+
+  /* Push ELEM at the back of the list, knowing the list is not empty.  */
+  void push_back_non_empty (T &elem)
+  {
+    gdb_assert (!this->empty ());
+
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+    intrusive_list_node<T> *back_node = as_node (m_back);
+
+    gdb_assert (elem_node->next == UNLINKED_VALUE);
+    gdb_assert (elem_node->prev == UNLINKED_VALUE);
+
+    elem_node->prev = m_back;
+    back_node->next = &elem;
+    elem_node->next = nullptr;
+    m_back = &elem;
+  }
+
+  void erase_element (T &elem)
+  {
+    intrusive_list_node<T> *elem_node = as_node (&elem);
+
+    gdb_assert (elem_node->prev != UNLINKED_VALUE);
+    gdb_assert (elem_node->next != UNLINKED_VALUE);
+
+    if (m_front == &elem)
+      {
+	gdb_assert (elem_node->prev == nullptr);
+	m_front = elem_node->next;
+      }
+    else
+      {
+	gdb_assert (elem_node->prev != nullptr);
+	intrusive_list_node<T> *prev_node = as_node (elem_node->prev);
+	prev_node->next = elem_node->next;
+      }
+
+    if (m_back == &elem)
+      {
+	gdb_assert (elem_node->next == nullptr);
+	m_back = elem_node->prev;
+      }
+    else
+      {
+	gdb_assert (elem_node->next != nullptr);
+	intrusive_list_node<T> *next_node = as_node (elem_node->next);
+	next_node->prev = elem_node->prev;
+      }
+
+    elem_node->next = UNLINKED_VALUE;
+    elem_node->prev = UNLINKED_VALUE;
+  }
+
+public:
+  /* Remove the element pointed by I from the list.  The element
+     pointed by I is not destroyed.  */
+  iterator erase (const_iterator i)
+  {
+    iterator ret = i;
+    ++ret;
+
+    erase_element (*i);
+
+    return ret;
+  }
+
+  /* Erase all the elements.  The elements are not destroyed.  */
+  void clear ()
+  {
+    while (!this->empty ())
+      pop_front ();
+  }
+
+  /* Erase all the elements.  Disposer::operator()(pointer) is called
+     for each of the removed elements.  */
+  template<typename Disposer>
+  void clear_and_dispose (Disposer disposer)
+  {
+    while (!this->empty ())
+      {
+	pointer p = &front ();
+	pop_front ();
+	disposer (p);
+      }
+  }
+
+  bool empty () const
+  {
+    return m_front == nullptr;
+  }
+
+  iterator begin () noexcept
+  {
+    return iterator (m_front);
+  }
+
+  const_iterator begin () const noexcept
+  {
+    return const_iterator (m_front);
+  }
+
+  const_iterator cbegin () const noexcept
+  {
+    return const_iterator (m_front);
+  }
+
+  iterator end () noexcept
+  {
+    return {};
+  }
+
+  const_iterator end () const noexcept
+  {
+    return {};
+  }
+
+  const_iterator cend () const noexcept
+  {
+    return {};
+  }
+
+  reverse_iterator rbegin () noexcept
+  {
+    return reverse_iterator (m_back);
+  }
+
+  const_reverse_iterator rbegin () const noexcept
+  {
+    return const_reverse_iterator (m_back);
+  }
+
+  const_reverse_iterator crbegin () const noexcept
+  {
+    return const_reverse_iterator (m_back);
+  }
+
+  reverse_iterator rend () noexcept
+  {
+    return {};
+  }
+
+  const_reverse_iterator rend () const noexcept
+  {
+    return {};
+  }
+
+  const_reverse_iterator crend () const noexcept
+  {
+    return {};
+  }
+
+private:
+  static node_type *as_node (T *elem)
+  {
+    return AsNode::as_node (elem);
+  }
+
+  T *m_front = nullptr;
+  T *m_back = nullptr;
+};
+
+#endif /* GDBSUPPORT_INTRUSIVE_LIST_H */
diff --git a/gdbsupport/reference-to-pointer-iterator.h b/gdbsupport/reference-to-pointer-iterator.h
new file mode 100644
index 000000000000..7303fa4a04ae
--- /dev/null
+++ b/gdbsupport/reference-to-pointer-iterator.h
@@ -0,0 +1,79 @@
+/* An iterator wrapper that yields pointers instead of references.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program 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 of the License, or
+   (at your option) any later version.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H
+#define GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H
+
+/* Wrap an iterator that yields references to objects so that it yields
+   pointers to objects instead.
+
+   This is useful for example to bridge the gap between iterators on intrusive
+   lists, which yield references, and the rest of GDB, which for legacy reasons
+   expects to iterate on pointers.  */
+
+template <typename IteratorType>
+struct reference_to_pointer_iterator
+{
+  using self_type = reference_to_pointer_iterator;
+  using value_type = typename IteratorType::value_type *;
+  using reference = typename IteratorType::value_type *&;
+  using pointer = typename IteratorType::value_type **;
+  using iterator_category = typename IteratorType::iterator_category;
+  using difference_type = typename IteratorType::difference_type;
+
+  /* Construct a reference_to_pointer_iterator, passing args to the underyling
+     iterator.  */
+  template <typename... Args>
+  reference_to_pointer_iterator (Args &&...args)
+    : m_it (std::forward<Args> (args)...)
+  {}
+
+  /* Create a past-the-end iterator.
+
+     Assumes that default-constructing an underlying iterator creates a
+     past-the-end iterator.  */
+  reference_to_pointer_iterator ()
+  {}
+
+  /* Need these as the variadic constructor would be a better match
+     otherwise.  */
+  reference_to_pointer_iterator (reference_to_pointer_iterator &) = default;
+  reference_to_pointer_iterator (const reference_to_pointer_iterator &) = default;
+  reference_to_pointer_iterator (reference_to_pointer_iterator &&) = default;
+
+  value_type operator* () const
+  { return &*m_it; }
+
+  self_type &operator++ ()
+  {
+    ++m_it;
+    return *this;
+  }
+
+  bool operator== (const self_type &other) const
+  { return m_it == other.m_it; }
+
+  bool operator!= (const self_type &other) const
+  { return m_it != other.m_it; }
+
+private:
+  /* The underlying iterator.  */
+  IteratorType m_it;
+};
+
+#endif /* GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H */
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 03/11] gdb: make inferior_list use intrusive_list
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
  2021-06-22 16:56 ` [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter Simon Marchi
  2021-06-22 16:56 ` [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it Simon Marchi
@ 2021-06-22 16:56 ` Simon Marchi
  2021-07-05 15:44   ` Pedro Alves
  2021-06-22 16:56 ` [PATCH 04/11] gdb: use intrusive list for step-over chain Simon Marchi
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches; +Cc: Pedro Alves, Simon Marchi

From: Pedro Alves <pedro@palves.net>

Change inferior_list, the global list of inferiors, to use
intrusive_list.  I think most other changes are somewhat obvious
fallouts from this change.

There is a small change in behavior in scoped_mock_context.  Before this
patch, constructing a scoped_mock_context would replace the whole
inferior list with only the new mock inferior.  Tests using two
scoped_mock_contexts therefore needed to manually link the two inferiors
together, as the second scoped_mock_context would bump the first mock
inferior from the thread list.  With this patch, a scoped_mock_context
adds its mock inferior to the inferior list on construction, and removes
it on destruction.  This means that tests run with mock inferiors in the
inferior list in addition to any pre-existing inferiors (there is always
at least one).  There is no possible pid clash problem, since each
scoped mock inferior uses its own process target, and pids are per
process target.

YYYY-MM-DD  Pedro Alves  <pedro@palves.net>
YYYY-MM-DD  Simon Marchi  <simon.marchi@efficios.com>

gdb/ChangeLog:

	* inferior.h (class inferior): Inherit from intrusive_list_node.
	<next>: Remove.
	(inferior_list): Change type to intrusive_list.
	* inferior.c (inferior_list): Change type to intrusive_list.
	(add_inferior_silent): Adjust.
	(delete_inferior): Adjust.
	(exit_inferior_1): Adjust.
	(prune_inferiors): Adjust.
	* inferior-iter.h (class all_inferiors_iterator)
	<all_inferiors_iterator>: Accept intrusive_list parameter.
	<m_inf>: Remove.
	<m_inf_iter>: New.
	<operator*, operator!=, m_inf_matches>: Adjust.
	* ada-tasks.c (ada_tasks_new_objfile_observer): Adjust.
	* infrun.c (do_target_wait): Adjust.
	* progspace.c (update_address_spaces): Adjust.
	* regcache.c (regcache_thread_ptid_changed): Don't set next.
	* scoped-mock-context.h (struct scoped_mock_context): Adjust.
	* thread-iter.c (all_threads_iterator::all_threads_iterator):
	Adjust.
	(all_threads_iterator::advance): Adjust.
	(all_matching_threads_iterator::all_matching_threads_iterator):
	Adjust.
	(all_matching_threads_iterator::advance): Adjust.
	* thread.c (show_inferior_qualified_tids): Adjust.

Co-Authored-By: Simon Marchi <simon.marchi@efficios.com>
Change-Id: I7eb6a8f867d4dcf8b8cd2dcffd118f7270756018
---
 gdb/ada-tasks.c           |  4 +--
 gdb/inferior-iter.h       | 25 ++++++++------
 gdb/inferior.c            | 63 +++++++-----------------------------
 gdb/inferior.h            |  9 +++---
 gdb/infrun.c              | 30 ++++++++++-------
 gdb/progspace.c           |  3 +-
 gdb/regcache.c            |  1 -
 gdb/scoped-mock-context.h | 10 +++---
 gdb/thread-iter.c         | 68 +++++++++++++++++++++++----------------
 gdb/thread.c              |  6 +++-
 10 files changed, 103 insertions(+), 116 deletions(-)

diff --git a/gdb/ada-tasks.c b/gdb/ada-tasks.c
index a9c6b5eb1b3a..80a72216f96e 100644
--- a/gdb/ada-tasks.c
+++ b/gdb/ada-tasks.c
@@ -1444,8 +1444,6 @@ ada_tasks_normal_stop_observer (struct bpstats *unused_args, int unused_args2)
 static void
 ada_tasks_new_objfile_observer (struct objfile *objfile)
 {
-  struct inferior *inf;
-
   /* Invalidate the relevant data in our program-space data.  */
 
   if (objfile == NULL)
@@ -1468,7 +1466,7 @@ ada_tasks_new_objfile_observer (struct objfile *objfile)
      If all objfiles are being cleared (OBJFILE is NULL), then
      clear the caches for all inferiors.  */
 
-  for (inf = inferior_list; inf != NULL; inf = inf->next)
+  for (inferior *inf : all_inferiors ())
     if (objfile == NULL || inf->pspace == objfile->pspace)
       ada_tasks_invalidate_inferior_data (inf);
 }
diff --git a/gdb/inferior-iter.h b/gdb/inferior-iter.h
index f999150a7b55..1701465eaf83 100644
--- a/gdb/inferior-iter.h
+++ b/gdb/inferior-iter.h
@@ -36,18 +36,21 @@ class all_inferiors_iterator
   typedef int difference_type;
 
   /* Create an iterator pointing at HEAD.  */
-  all_inferiors_iterator (process_stratum_target *proc_target, inferior *head)
-    : m_proc_target (proc_target)
+  all_inferiors_iterator (process_stratum_target *proc_target,
+			  const intrusive_list<inferior> &list)
+    : m_proc_target (proc_target), m_inf_iter (list.begin ())
   {
+    intrusive_list<inferior>::iterator end;
+
     /* Advance M_INF to the first inferior's position.  */
-    for (m_inf = head; m_inf != NULL; m_inf = m_inf->next)
+    for (; m_inf_iter != end; ++m_inf_iter)
       if (m_inf_matches ())
 	return;
   }
 
   /* Create a one-past-end iterator.  */
   all_inferiors_iterator ()
-    : m_proc_target (nullptr), m_inf (nullptr)
+    : m_proc_target (nullptr)
   {}
 
   all_inferiors_iterator &operator++ ()
@@ -57,37 +60,39 @@ class all_inferiors_iterator
   }
 
   inferior *operator* () const
-  { return m_inf; }
+  { return &*m_inf_iter; }
 
   bool operator!= (const all_inferiors_iterator &other) const
-  { return m_inf != other.m_inf; }
+  { return m_inf_iter != other.m_inf_iter; }
 
 private:
   /* Advance to next inferior, skipping filtered inferiors.  */
   void advance ()
   {
+    intrusive_list<inferior>::iterator end;
+
     /* The loop below is written in the natural way as-if we'd always
        start at the beginning of the inferior list.  This
        fast-forwards the algorithm to the actual current position.  */
     goto start;
 
-    while (m_inf != NULL)
+    while (m_inf_iter != end)
       {
 	if (m_inf_matches ())
 	  return;
       start:
-	m_inf = m_inf->next;
+	++m_inf_iter;
       }
   }
 
   bool m_inf_matches ()
   {
     return (m_proc_target == nullptr
-	    || m_proc_target == m_inf->process_target ());
+	    || m_proc_target == m_inf_iter->process_target ());
   }
 
   process_stratum_target *m_proc_target;
-  inferior *m_inf;
+  intrusive_list<inferior>::iterator m_inf_iter;
 };
 
 /* A range adapter that makes it possible to iterate over all
diff --git a/gdb/inferior.c b/gdb/inferior.c
index 693b196556d5..f1b0bdde554b 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -41,7 +41,7 @@
 
 DEFINE_REGISTRY (inferior, REGISTRY_ACCESS_FIELD)
 
-struct inferior *inferior_list = NULL;
+intrusive_list<inferior> inferior_list;
 static int highest_inferior_num;
 
 /* See inferior.h.  */
@@ -126,16 +126,7 @@ add_inferior_silent (int pid)
 {
   inferior *inf = new inferior (pid);
 
-  if (inferior_list == NULL)
-    inferior_list = inf;
-  else
-    {
-      inferior *last;
-
-      for (last = inferior_list; last->next != NULL; last = last->next)
-	;
-      last->next = inf;
-    }
+  inferior_list.push_back (*inf);
 
   gdb::observers::inferior_added.notify (inf);
 
@@ -177,25 +168,12 @@ inferior::clear_thread_list (bool silent)
 }
 
 void
-delete_inferior (struct inferior *todel)
+delete_inferior (struct inferior *inf)
 {
-  struct inferior *inf, *infprev;
-
-  infprev = NULL;
-
-  for (inf = inferior_list; inf; infprev = inf, inf = inf->next)
-    if (inf == todel)
-      break;
-
-  if (!inf)
-    return;
-
   inf->clear_thread_list (true);
 
-  if (infprev)
-    infprev->next = inf->next;
-  else
-    inferior_list = inf->next;
+  auto it = inferior_list.iterator_to (*inf);
+  inferior_list.erase (it);
 
   gdb::observers::inferior_removed.notify (inf);
 
@@ -210,17 +188,8 @@ delete_inferior (struct inferior *todel)
    exit of its threads.  */
 
 static void
-exit_inferior_1 (struct inferior *inftoex, int silent)
+exit_inferior_1 (struct inferior *inf, int silent)
 {
-  struct inferior *inf;
-
-  for (inf = inferior_list; inf; inf = inf->next)
-    if (inf == inftoex)
-      break;
-
-  if (!inf)
-    return;
-
   inf->clear_thread_list (silent);
 
   gdb::observers::inferior_exit.notify (inf);
@@ -388,22 +357,14 @@ have_live_inferiors (void)
 void
 prune_inferiors (void)
 {
-  inferior *ss;
-
-  ss = inferior_list;
-  while (ss)
+  for (inferior *inf : all_inferiors_safe ())
     {
-      if (!ss->deletable ()
-	  || !ss->removable
-	  || ss->pid != 0)
-	{
-	  ss = ss->next;
-	  continue;
-	}
+      if (!inf->deletable ()
+	  || !inf->removable
+	  || inf->pid != 0)
+	continue;
 
-      inferior *ss_next = ss->next;
-      delete_inferior (ss);
-      ss = ss_next;
+      delete_inferior (inf);
     }
 }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 2ae9f9a5f9c4..830dec3ebbaa 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -55,6 +55,7 @@ struct thread_info;
 #include "gdbsupport/refcounted-object.h"
 #include "gdbsupport/forward-scope-exit.h"
 #include "gdbsupport/gdb_unique_ptr.h"
+#include "gdbsupport/intrusive_list.h"
 
 #include "gdbsupport/common-inferior.h"
 #include "gdbthread.h"
@@ -339,7 +340,8 @@ extern void switch_to_inferior_no_thread (inferior *inf);
    listed exactly once in the inferior list, so placing an inferior in
    the inferior list is an implicit, not counted strong reference.  */
 
-class inferior : public refcounted_object
+class inferior : public refcounted_object,
+		 public intrusive_list_node<inferior>
 {
 public:
   explicit inferior (int pid);
@@ -387,9 +389,6 @@ class inferior : public refcounted_object
   bool has_execution ()
   { return target_has_execution (this); }
 
-  /* Pointer to next inferior in singly-linked list of inferiors.  */
-  struct inferior *next = NULL;
-
   /* This inferior's thread list, sorted by creation order.  */
   intrusive_list<thread_info> thread_list;
 
@@ -653,7 +652,7 @@ class scoped_restore_current_inferior
 
 /* Traverse all inferiors.  */
 
-extern struct inferior *inferior_list;
+extern intrusive_list<inferior> inferior_list;
 
 /* Pull in the internals of the inferiors ranges and iterators.  Must
    be done after struct inferior is defined.  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 4bd21fde5907..3f7e80216b82 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3733,18 +3733,28 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs,
      reported the stop to the user, polling for events.  */
   scoped_restore_current_thread restore_thread;
 
-  int inf_num = selected->num;
-  for (inferior *inf = selected; inf != NULL; inf = inf->next)
-    if (inferior_matches (inf))
-      if (do_wait (inf))
+  intrusive_list_iterator<inferior> start
+    = inferior_list.iterator_to (*selected);
+
+  for (intrusive_list_iterator<inferior> it = start;
+       it != inferior_list.end ();
+       ++it)
+    {
+      inferior *inf = &*it;
+
+      if (inferior_matches (inf) && do_wait (inf))
 	return true;
+    }
 
-  for (inferior *inf = inferior_list;
-       inf != NULL && inf->num < inf_num;
-       inf = inf->next)
-    if (inferior_matches (inf))
-      if (do_wait (inf))
+  for (intrusive_list_iterator<inferior> it = inferior_list.begin ();
+       it != start;
+       ++it)
+    {
+      inferior *inf = &*it;
+
+      if (inferior_matches (inf) && do_wait (inf))
 	return true;
+    }
 
   ecs->ws.kind = TARGET_WAITKIND_IGNORE;
   return false;
@@ -9455,7 +9465,6 @@ infrun_thread_ptid_changed ()
 
     scoped_mock_context<test_target_ops> target1 (arch);
     scoped_mock_context<test_target_ops> target2 (arch);
-    target2.mock_inferior.next = &target1.mock_inferior;
 
     ptid_t old_ptid (111, 222);
     ptid_t new_ptid (111, 333);
@@ -9480,7 +9489,6 @@ infrun_thread_ptid_changed ()
 
     scoped_mock_context<test_target_ops> target1 (arch);
     scoped_mock_context<test_target_ops> target2 (arch);
-    target2.mock_inferior.next = &target1.mock_inferior;
 
     ptid_t old_ptid (111, 222);
     ptid_t new_ptid (111, 333);
diff --git a/gdb/progspace.c b/gdb/progspace.c
index e3cc6929a236..7080bf8ee270 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -404,7 +404,6 @@ void
 update_address_spaces (void)
 {
   int shared_aspace = gdbarch_has_shared_address_space (target_gdbarch ());
-  struct inferior *inf;
 
   init_address_spaces ();
 
@@ -423,7 +422,7 @@ update_address_spaces (void)
 	pspace->aspace = new_address_space ();
       }
 
-  for (inf = inferior_list; inf; inf = inf->next)
+  for (inferior *inf : all_inferiors ())
     if (gdbarch_has_global_solist (target_gdbarch ()))
       inf->aspace = maybe_new_address_space ();
     else
diff --git a/gdb/regcache.c b/gdb/regcache.c
index fde0c612975a..21fa25d31553 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -2038,7 +2038,6 @@ regcache_thread_ptid_changed ()
   /* Prepare two targets with one thread each, with the same ptid.  */
   scoped_mock_context<test_target_ops> target1 (arch);
   scoped_mock_context<test_target_ops> target2 (arch);
-  target2.mock_inferior.next = &target1.mock_inferior;
 
   ptid_t old_ptid (111, 222);
   ptid_t new_ptid (111, 333);
diff --git a/gdb/scoped-mock-context.h b/gdb/scoped-mock-context.h
index 37ffe5117423..ba3b81ed12a5 100644
--- a/gdb/scoped-mock-context.h
+++ b/gdb/scoped-mock-context.h
@@ -44,13 +44,12 @@ struct scoped_mock_context
 
   scoped_restore_current_pspace_and_thread restore_pspace_thread;
 
-  /* Add the mock inferior to the inferior list so that look ups by
-     target+ptid can find it.  */
-  scoped_restore_tmpl<inferior *> restore_inferior_list
-    {&inferior_list, &mock_inferior};
-
   explicit scoped_mock_context (gdbarch *gdbarch)
   {
+    /* Add the mock inferior to the inferior list so that look ups by
+       target+ptid can find it.  */
+    inferior_list.push_back (mock_inferior);
+
     mock_inferior.thread_list.push_back (mock_thread);
     mock_inferior.gdbarch = gdbarch;
     mock_inferior.aspace = mock_pspace.aspace;
@@ -70,6 +69,7 @@ struct scoped_mock_context
 
   ~scoped_mock_context ()
   {
+    inferior_list.erase (inferior_list.iterator_to (mock_inferior));
     pop_all_targets_at_and_above (process_stratum);
   }
 };
diff --git a/gdb/thread-iter.c b/gdb/thread-iter.c
index a1cdd0206bd4..31b7a36eaada 100644
--- a/gdb/thread-iter.c
+++ b/gdb/thread-iter.c
@@ -26,15 +26,18 @@
 all_threads_iterator::all_threads_iterator (begin_t)
 {
   /* Advance M_INF/M_THR to the first thread's position.  */
-  for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
+
+  for (inferior &inf : inferior_list)
     {
-      auto thr_iter = m_inf->thread_list.begin ();
-      if (thr_iter != m_inf->thread_list.end ())
+      auto thr_iter = inf.thread_list.begin ();
+      if (thr_iter != inf.thread_list.end ())
 	{
+	  m_inf = &inf;
 	  m_thr = &*thr_iter;
 	  return;
 	}
     }
+  m_inf = nullptr;
   m_thr = nullptr;
 }
 
@@ -43,6 +46,7 @@ all_threads_iterator::all_threads_iterator (begin_t)
 void
 all_threads_iterator::advance ()
 {
+  intrusive_list<inferior>::iterator inf_iter (m_inf);
   intrusive_list<thread_info>::iterator thr_iter (m_thr);
 
   /* The loop below is written in the natural way as-if we'd always
@@ -50,8 +54,9 @@ all_threads_iterator::advance ()
      the algorithm to the actual current position.  */
   goto start;
 
-  for (; m_inf != NULL; m_inf = m_inf->next)
+  for (; inf_iter != inferior_list.end (); ++inf_iter)
     {
+      m_inf = &*inf_iter;
       thr_iter = m_inf->thread_list.begin ();
       while (thr_iter != m_inf->thread_list.end ())
 	{
@@ -86,16 +91,21 @@ all_matching_threads_iterator::all_matching_threads_iterator
   gdb_assert ((filter_target == nullptr && filter_ptid == minus_one_ptid)
 	      || filter_target->stratum () == process_stratum);
 
-  for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
-    if (m_inf_matches ())
-      for (auto thr_iter = m_inf->thread_list.begin ();
-	   thr_iter != m_inf->thread_list.end ();
-	   ++thr_iter)
-	if (thr_iter->ptid.matches (m_filter_ptid))
+  for (inferior &inf : inferior_list)
+    {
+      m_inf = &inf;
+      if (m_inf_matches ())
+	for (auto thr_iter = m_inf->thread_list.begin ();
+	     thr_iter != m_inf->thread_list.end ();
+	     ++thr_iter)
 	  {
-	    m_thr = &*thr_iter;
-	    return;
+	    if (thr_iter->ptid.matches (m_filter_ptid))
+	      {
+		m_thr = &*thr_iter;
+		return;
+	      }
 	  }
+    }
 
   m_thr = nullptr;
 }
@@ -105,6 +115,7 @@ all_matching_threads_iterator::all_matching_threads_iterator
 void
 all_matching_threads_iterator::advance ()
 {
+  intrusive_list<inferior>::iterator inf_iter (m_inf);
   intrusive_list<thread_info>::iterator thr_iter (m_thr);
 
   /* The loop below is written in the natural way as-if we'd always
@@ -112,21 +123,24 @@ all_matching_threads_iterator::advance ()
      the algorithm to the actual current position.  */
   goto start;
 
-  for (; m_inf != NULL; m_inf = m_inf->next)
-    if (m_inf_matches ())
-      {
-	thr_iter = m_inf->thread_list.begin ();
-	while (thr_iter != m_inf->thread_list.end ())
-	  {
-	    if (thr_iter->ptid.matches (m_filter_ptid))
-	      {
-		m_thr = &*thr_iter;
-		return;
-	      }
-	  start:
-	    ++thr_iter;
-	  }
-      }
+  for (; inf_iter != inferior_list.end (); ++inf_iter)
+    {
+      m_inf = &*inf_iter;
+      if (m_inf_matches ())
+	{
+	  thr_iter = m_inf->thread_list.begin ();
+	  while (thr_iter != m_inf->thread_list.end ())
+	    {
+	      if (thr_iter->ptid.matches (m_filter_ptid))
+		{
+		  m_thr = &*thr_iter;
+		  return;
+		}
+	    start:
+	      ++thr_iter;
+	    }
+	}
+    }
 
   m_thr = nullptr;
 }
diff --git a/gdb/thread.c b/gdb/thread.c
index 89f51c01c993..506e93cf4016 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1395,7 +1395,11 @@ show_thread_that_caused_stop (void)
 int
 show_inferior_qualified_tids (void)
 {
-  return (inferior_list->next != NULL || inferior_list->num != 1);
+  auto inf = inferior_list.begin ();
+  if (inf->num != 1)
+    return true;
+  ++inf;
+  return inf != inferior_list.end ();
 }
 
 /* See gdbthread.h.  */
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 04/11] gdb: use intrusive list for step-over chain
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (2 preceding siblings ...)
  2021-06-22 16:56 ` [PATCH 03/11] gdb: make inferior_list use intrusive_list Simon Marchi
@ 2021-06-22 16:56 ` Simon Marchi
  2021-07-05 15:45   ` Pedro Alves
  2021-06-22 16:56 ` [PATCH 05/11] gdb: add setter / getter for thread_info resumed state Simon Marchi
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches

The threads that need a step-over are currently linked using an
hand-written intrusive doubly-linked list, so that seems a very good
candidate for intrusive_list, convert it.

For this, we have a use case of appending a list to another one (in
start_step_over).  Based on the std::list and Boost APIs, add a splice
method.  However, only support splicing the other list at the end of the
`this` list, since that's all we need.

gdb/ChangeLog:

	* gdbthread.h (class thread_info) <step_over_next,
	step_over_prev>: Remove.
	<step_over_list_node>: New.
	(thread_step_over_list_node, thread_step_over_list): New.
	(global_thread_step_over_chain_enqueue): Change parameter type.
	(thread_step_over_chain_remove): Remove.
	(thread_step_over_chain_next): Remove.
	(global_thread_step_over_chain_next): Remove.
	(thread_step_over_chain_length): Change parameter type.
	* thread.c (set_thread_exited): Adjust.
	(step_over_chain_enqueue): Remove.
	(thread_step_over_chain_remove): Remove.
	(thread_step_over_chain_next): Remove.
	(global_thread_step_over_chain_next): Remove.
	(thread_is_in_step_over_chain): Change parameter type.
	(thread_step_over_chain_length): Change parameter type.
	(global_thread_step_over_chain_enqueue_chain):
	(global_thread_step_over_chain_remove): Adjust.
	(set_running_thread): Adjust.
	* infrun.h (global_thread_step_over_chain_head): Rename to...
	(global_thread_step_over_list): ... this, change type.
	* infrun.c (global_thread_step_over_chain_head): Rename to...
	(global_thread_step_over_list): ... this, change type.
	(start_step_over): Adjust.
	(prepare_for_detach): Adjust.
	* unittests/intrusive_list-selftests.c (struct intrusive_list_test)
	<test_splice>: New.
	(test_intrusive_list): Call test_splice.

gdbsupport/ChangeLog:

	* intrusive_list.h (class intrusive_list) <splice>: New.
	* reference-to-pointer-iterator.h (struct
	reference_to_pointer_iterator): Add default assignment
	operators.

Change-Id: I31b2ff67c7b78251314646b31887ef1dfebe510c
---
 gdb/gdbthread.h                            |  52 +++++-----
 gdb/infrun.c                               |  39 ++++----
 gdb/infrun.h                               |   4 +-
 gdb/thread.c                               | 106 +++------------------
 gdb/unittests/intrusive_list-selftests.c   |  84 ++++++++++++++++
 gdbsupport/intrusive_list.h                |  27 ++++++
 gdbsupport/reference-to-pointer-iterator.h |   3 +
 7 files changed, 177 insertions(+), 138 deletions(-)

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 0e28b1de9ff0..54c097206d16 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -387,11 +387,9 @@ class thread_info : public refcounted_object,
      expressions.  */
   std::vector<struct value *> stack_temporaries;
 
-  /* Step-over chain.  A thread is in the step-over queue if these are
-     non-NULL.  If only a single thread is in the chain, then these
-     fields point to self.  */
-  struct thread_info *step_over_prev = NULL;
-  struct thread_info *step_over_next = NULL;
+  /* Step-over chain.  A thread is in the step-over queue if this node is
+     linked.  */
+  intrusive_list_node<thread_info> step_over_list_node;
 
   /* Displaced-step state for this thread.  */
   displaced_step_thread_state displaced_step_state;
@@ -746,36 +744,42 @@ extern value *get_last_thread_stack_temporary (struct thread_info *tp);
 extern bool value_in_thread_stack_temporaries (struct value *,
 					       struct thread_info *thr);
 
+/* Thread step-over list type.  */
+using thread_step_over_list_node
+  = intrusive_member_node<thread_info, &thread_info::step_over_list_node>;
+using thread_step_over_list
+  = intrusive_list<thread_info, thread_step_over_list_node>;
+using thread_step_over_list_iterator
+  = reference_to_pointer_iterator<thread_step_over_list::iterator>;
+using thread_step_over_list_safe_iterator
+  = basic_safe_iterator<thread_step_over_list_iterator>;
+using thread_step_over_list_safe_range
+  = iterator_range<thread_step_over_list_safe_iterator>;
+
+static inline thread_step_over_list_safe_range
+make_thread_step_over_list_safe_range (thread_step_over_list &list)
+{
+  return thread_step_over_list_safe_range
+    (thread_step_over_list_safe_iterator (list.begin (),
+					  list.end ()),
+     thread_step_over_list_safe_iterator (list.end (),
+					  list.end ()));
+}
+
 /* Add TP to the end of the global pending step-over chain.  */
 
 extern void global_thread_step_over_chain_enqueue (thread_info *tp);
 
-/* Append the thread step over chain CHAIN_HEAD to the global thread step over
+/* Append the thread step over list LIST to the global thread step over
    chain. */
 
 extern void global_thread_step_over_chain_enqueue_chain
-  (thread_info *chain_head);
-
-/* Remove TP from step-over chain LIST_P.  */
-
-extern void thread_step_over_chain_remove (thread_info **list_p,
-					   thread_info *tp);
+  (thread_step_over_list &&list);
 
 /* Remove TP from the global pending step-over chain.  */
 
 extern void global_thread_step_over_chain_remove (thread_info *tp);
 
-/* Return the thread following TP in the step-over chain whose head is
-   CHAIN_HEAD.  Return NULL if TP is the last entry in the chain.  */
-
-extern thread_info *thread_step_over_chain_next (thread_info *chain_head,
-						 thread_info *tp);
-
-/* Return the thread following TP in the global step-over chain, or NULL if TP
-   is the last entry in the chain.  */
-
-extern thread_info *global_thread_step_over_chain_next (thread_info *tp);
-
 /* Return true if TP is in any step-over chain.  */
 
 extern int thread_is_in_step_over_chain (struct thread_info *tp);
@@ -786,7 +790,7 @@ extern int thread_is_in_step_over_chain (struct thread_info *tp);
    TP may be nullptr, in which case it denotes an empty list, so a length of
    0.  */
 
-extern int thread_step_over_chain_length (thread_info *tp);
+extern int thread_step_over_chain_length (const thread_step_over_list &l);
 
 /* Cancel any ongoing execution command.  */
 
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 3f7e80216b82..fcd0f4e10ced 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1245,7 +1245,7 @@ follow_exec (ptid_t ptid, const char *exec_file_target)
    to avoid starvation, otherwise, we could e.g., find ourselves
    constantly stepping the same couple threads past their breakpoints
    over and over, if the single-step finish fast enough.  */
-struct thread_info *global_thread_step_over_chain_head;
+thread_step_over_list global_thread_step_over_list;
 
 /* Bit flags indicating what the thread needs to step over.  */
 
@@ -1843,8 +1843,6 @@ start_step_over (void)
 {
   INFRUN_SCOPED_DEBUG_ENTER_EXIT;
 
-  thread_info *next;
-
   /* Don't start a new step-over if we already have an in-line
      step-over operation ongoing.  */
   if (step_over_info_valid_p ())
@@ -1854,8 +1852,8 @@ start_step_over (void)
      steps, threads will be enqueued in the global chain if no buffers are
      available.  If we iterated on the global chain directly, we might iterate
      indefinitely.  */
-  thread_info *threads_to_step = global_thread_step_over_chain_head;
-  global_thread_step_over_chain_head = NULL;
+  thread_step_over_list threads_to_step
+    = std::move (global_thread_step_over_list);
 
   infrun_debug_printf ("stealing global queue of threads to step, length = %d",
 		       thread_step_over_chain_length (threads_to_step));
@@ -1867,18 +1865,22 @@ start_step_over (void)
      global list.  */
   SCOPE_EXIT
     {
-      if (threads_to_step == nullptr)
+      if (threads_to_step.empty ())
 	infrun_debug_printf ("step-over queue now empty");
       else
 	{
 	  infrun_debug_printf ("putting back %d threads to step in global queue",
 			       thread_step_over_chain_length (threads_to_step));
 
-	  global_thread_step_over_chain_enqueue_chain (threads_to_step);
+	  global_thread_step_over_chain_enqueue_chain
+	    (std::move (threads_to_step));
 	}
     };
 
-  for (thread_info *tp = threads_to_step; tp != NULL; tp = next)
+  thread_step_over_list_safe_range range
+    = make_thread_step_over_list_safe_range (threads_to_step);
+
+  for (thread_info *tp : range)
     {
       struct execution_control_state ecss;
       struct execution_control_state *ecs = &ecss;
@@ -1887,8 +1889,6 @@ start_step_over (void)
 
       gdb_assert (!tp->stop_requested);
 
-      next = thread_step_over_chain_next (threads_to_step, tp);
-
       if (tp->inf->displaced_step_state.unavailable)
 	{
 	  /* The arch told us to not even try preparing another displaced step
@@ -1903,7 +1903,7 @@ start_step_over (void)
 	 step over chain indefinitely if something goes wrong when resuming it
 	 If the error is intermittent and it still needs a step over, it will
 	 get enqueued again when we try to resume it normally.  */
-      thread_step_over_chain_remove (&threads_to_step, tp);
+      threads_to_step.erase (threads_to_step.iterator_to (*tp));
 
       step_what = thread_still_needs_step_over (tp);
       must_be_in_line = ((step_what & STEP_OVER_WATCHPOINT)
@@ -3793,15 +3793,16 @@ prepare_for_detach (void)
 
   /* Remove all threads of INF from the global step-over chain.  We
      want to stop any ongoing step-over, not start any new one.  */
-  thread_info *next;
-  for (thread_info *tp = global_thread_step_over_chain_head;
-       tp != nullptr;
-       tp = next)
-    {
-      next = global_thread_step_over_chain_next (tp);
-      if (tp->inf == inf)
+  thread_step_over_list_safe_range range
+    = make_thread_step_over_list_safe_range (global_thread_step_over_list);
+
+  for (thread_info *tp : range)
+    if (tp->inf == inf)
+      {
+	infrun_debug_printf ("removing thread %s from global step over chain",
+			     target_pid_to_str (tp->ptid).c_str ());
 	global_thread_step_over_chain_remove (tp);
-    }
+      }
 
   /* If we were already in the middle of an inline step-over, and the
      thread stepping belongs to the inferior we're detaching, we need
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 7ebb9fc9f4e6..5a577365f946 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -18,8 +18,10 @@
 #ifndef INFRUN_H
 #define INFRUN_H 1
 
+#include "gdbthread.h"
 #include "symtab.h"
 #include "gdbsupport/byte-vector.h"
+#include "gdbsupport/intrusive_list.h"
 
 struct target_waitstatus;
 struct frame_info;
@@ -253,7 +255,7 @@ extern void mark_infrun_async_event_handler (void);
 
 /* The global chain of threads that need to do a step-over operation
    to get past e.g., a breakpoint.  */
-extern struct thread_info *global_thread_step_over_chain_head;
+extern thread_step_over_list global_thread_step_over_list;
 
 /* Remove breakpoints if possible (usually that means, if everything
    is stopped).  On failure, print a message.  */
diff --git a/gdb/thread.c b/gdb/thread.c
index 506e93cf4016..925ed96c3d83 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -183,7 +183,7 @@ void
 set_thread_exited (thread_info *tp, bool silent)
 {
   /* Dead threads don't need to step-over.  Remove from chain.  */
-  if (tp->step_over_next != NULL)
+  if (thread_is_in_step_over_chain (tp))
     global_thread_step_over_chain_remove (tp);
 
   if (tp->state != THREAD_EXITED)
@@ -293,93 +293,22 @@ thread_info::deletable () const
   return refcount () == 0 && !is_current_thread (this);
 }
 
-/* Add TP to the end of the step-over chain LIST_P.  */
-
-static void
-step_over_chain_enqueue (struct thread_info **list_p, struct thread_info *tp)
-{
-  gdb_assert (tp->step_over_next == NULL);
-  gdb_assert (tp->step_over_prev == NULL);
-
-  if (*list_p == NULL)
-    {
-      *list_p = tp;
-      tp->step_over_prev = tp->step_over_next = tp;
-    }
-  else
-    {
-      struct thread_info *head = *list_p;
-      struct thread_info *tail = head->step_over_prev;
-
-      tp->step_over_prev = tail;
-      tp->step_over_next = head;
-      head->step_over_prev = tp;
-      tail->step_over_next = tp;
-    }
-}
-
-/* See gdbthread.h.  */
-
-void
-thread_step_over_chain_remove (thread_info **list_p, thread_info *tp)
-{
-  gdb_assert (tp->step_over_next != NULL);
-  gdb_assert (tp->step_over_prev != NULL);
-
-  if (*list_p == tp)
-    {
-      if (tp == tp->step_over_next)
-	*list_p = NULL;
-      else
-	*list_p = tp->step_over_next;
-    }
-
-  tp->step_over_prev->step_over_next = tp->step_over_next;
-  tp->step_over_next->step_over_prev = tp->step_over_prev;
-  tp->step_over_prev = tp->step_over_next = NULL;
-}
-
-/* See gdbthread.h.  */
-
-thread_info *
-thread_step_over_chain_next (thread_info *chain_head, thread_info *tp)
-{
-  thread_info *next = tp->step_over_next;
-
-  return next == chain_head ? NULL : next;
-}
-
-/* See gdbthread.h.  */
-
-struct thread_info *
-global_thread_step_over_chain_next (struct thread_info *tp)
-{
-  return thread_step_over_chain_next (global_thread_step_over_chain_head, tp);
-}
-
 /* See gdbthread.h.  */
 
 int
 thread_is_in_step_over_chain (struct thread_info *tp)
 {
-  return (tp->step_over_next != NULL);
+  return tp->step_over_list_node.is_linked ();
 }
 
 /* See gdbthread.h.  */
 
 int
-thread_step_over_chain_length (thread_info *tp)
+thread_step_over_chain_length (const thread_step_over_list &l)
 {
-  if (tp == nullptr)
-    return 0;
-
-  gdb_assert (thread_is_in_step_over_chain (tp));
-
   int num = 1;
 
-  for (thread_info *iter = tp->step_over_next;
-       iter != tp;
-       iter = iter->step_over_next)
+  for (const thread_info &thread ATTRIBUTE_UNUSED : l)
     ++num;
 
   return num;
@@ -393,29 +322,16 @@ global_thread_step_over_chain_enqueue (struct thread_info *tp)
   infrun_debug_printf ("enqueueing thread %s in global step over chain",
 		       target_pid_to_str (tp->ptid).c_str ());
 
-  step_over_chain_enqueue (&global_thread_step_over_chain_head, tp);
+  gdb_assert (!thread_is_in_step_over_chain (tp));
+  global_thread_step_over_list.push_back (*tp);
 }
 
 /* See gdbthread.h.  */
 
 void
-global_thread_step_over_chain_enqueue_chain (thread_info *chain_head)
+global_thread_step_over_chain_enqueue_chain (thread_step_over_list &&list)
 {
-  gdb_assert (chain_head->step_over_next != nullptr);
-  gdb_assert (chain_head->step_over_prev != nullptr);
-
-  if (global_thread_step_over_chain_head == nullptr)
-    global_thread_step_over_chain_head = chain_head;
-  else
-    {
-      thread_info *global_last = global_thread_step_over_chain_head->step_over_prev;
-      thread_info *chain_last = chain_head->step_over_prev;
-
-      chain_last->step_over_next = global_thread_step_over_chain_head;
-      global_last->step_over_next = chain_head;
-      global_thread_step_over_chain_head->step_over_prev = chain_last;
-      chain_head->step_over_prev = global_last;
-    }
+  global_thread_step_over_list.splice (std::move (list));
 }
 
 /* See gdbthread.h.  */
@@ -426,7 +342,9 @@ global_thread_step_over_chain_remove (struct thread_info *tp)
   infrun_debug_printf ("removing thread %s from global step over chain",
 		       target_pid_to_str (tp->ptid).c_str ());
 
-  thread_step_over_chain_remove (&global_thread_step_over_chain_head, tp);
+  gdb_assert (thread_is_in_step_over_chain (tp));
+  auto it = global_thread_step_over_list.iterator_to (*tp);
+  global_thread_step_over_list.erase (it);
 }
 
 /* Delete the thread referenced by THR.  If SILENT, don't notify
@@ -810,7 +728,7 @@ set_running_thread (struct thread_info *tp, bool running)
       /* If the thread is now marked stopped, remove it from
 	 the step-over queue, so that we don't try to resume
 	 it until the user wants it to.  */
-      if (tp->step_over_next != NULL)
+      if (thread_is_in_step_over_chain (tp))
 	global_thread_step_over_chain_remove (tp);
     }
 
diff --git a/gdb/unittests/intrusive_list-selftests.c b/gdb/unittests/intrusive_list-selftests.c
index 3ccff54b5ff9..fd2e1fb51af7 100644
--- a/gdb/unittests/intrusive_list-selftests.c
+++ b/gdb/unittests/intrusive_list-selftests.c
@@ -503,6 +503,89 @@ struct intrusive_list_test
     }
   }
 
+  static void
+  test_splice ()
+  {
+    {
+      /* Two non-empty lists.  */
+      item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      list2.push_back (d);
+      list2.push_back (e);
+
+      list1.splice (std::move (list2));
+
+      expected = {&a, &b, &c, &d, &e};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Receiving list empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list2.push_back (a);
+      list2.push_back (b);
+      list2.push_back (c);
+
+      list1.splice (std::move (list2));
+
+      expected = {&a, &b, &c};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Giving list empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.push_back (a);
+      list1.push_back (b);
+      list1.push_back (c);
+
+      list1.splice (std::move (list2));
+
+      expected = {&a, &b, &c};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+
+    {
+      /* Both lists empty.  */
+      item_type a ("a"), b ("b"), c ("c");
+      ListType list1;
+      ListType list2;
+      std::vector<const item_type *> expected;
+
+      list1.splice (std::move (list2));
+
+      expected = {};
+      verify_items (list1, expected);
+
+      expected = {};
+      verify_items (list2, expected);
+    }
+  }
+
   static void
   test_pop_front ()
   {
@@ -682,6 +765,7 @@ test_intrusive_list ()
   tests.test_push_front ();
   tests.test_push_back ();
   tests.test_insert ();
+  tests.test_splice ();
   tests.test_pop_front ();
   tests.test_pop_back ();
   tests.test_erase ();
diff --git a/gdbsupport/intrusive_list.h b/gdbsupport/intrusive_list.h
index 8e98e5b2c1a5..e369a8fcf1dc 100644
--- a/gdbsupport/intrusive_list.h
+++ b/gdbsupport/intrusive_list.h
@@ -350,6 +350,33 @@ class intrusive_list
     pos_node->prev = &elem;
   }
 
+  /* Move elements from LIST at the end of the current list.  */
+  void splice (intrusive_list &&other)
+  {
+    if (other.empty ())
+      return;
+
+    if (this->empty ())
+      {
+	*this = std::move (other);
+	return;
+      }
+
+    /* [A ... B] + [C ... D] */
+    T *b_elem = m_back;
+    node_type *b_node = as_node (b_elem);
+    T *c_elem = other.m_front;
+    node_type *c_node = as_node (c_elem);
+    T *d_elem = other.m_back;
+
+    b_node->next = c_elem;
+    c_node->prev = b_elem;
+    m_back = d_elem;
+
+    other.m_front = nullptr;
+    other.m_back = nullptr;
+  }
+
   void pop_front ()
   {
     gdb_assert (!this->empty ());
diff --git a/gdbsupport/reference-to-pointer-iterator.h b/gdbsupport/reference-to-pointer-iterator.h
index 7303fa4a04ae..9210426adccc 100644
--- a/gdbsupport/reference-to-pointer-iterator.h
+++ b/gdbsupport/reference-to-pointer-iterator.h
@@ -56,6 +56,9 @@ struct reference_to_pointer_iterator
   reference_to_pointer_iterator (const reference_to_pointer_iterator &) = default;
   reference_to_pointer_iterator (reference_to_pointer_iterator &&) = default;
 
+  reference_to_pointer_iterator &operator= (const reference_to_pointer_iterator &) = default;
+  reference_to_pointer_iterator &operator= (reference_to_pointer_iterator &&) = default;
+
   value_type operator* () const
   { return &*m_it; }
 
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 05/11] gdb: add setter / getter for thread_info resumed state
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (3 preceding siblings ...)
  2021-06-22 16:56 ` [PATCH 04/11] gdb: use intrusive list for step-over chain Simon Marchi
@ 2021-06-22 16:56 ` Simon Marchi
  2021-07-05 15:45   ` Pedro Alves
  2021-06-22 16:56 ` [PATCH 06/11] gdb: make thread_info::suspend private, add getters / setters Simon Marchi
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches

A following patch will want to do things when a thread's resumed state
changes.  Make the `resumed` field private (renamed to `m_resumed`) and
add a getter and a setter for it.  The following patch in question will
therefore be able to add some code to the setter.

gdb/ChangeLog:

	* gdbthread.h (struct thread_info) <resumed>: Rename to...
	<m_resumed>: This.  Change all uses to use the resumed and
	set_resumed methods.
	<resumed, set_resumed>: New methods.

Change-Id: I360c48cc55a036503174313261ce4e757d795319
---
 gdb/breakpoint.c |  3 +--
 gdb/gdbthread.h  | 23 +++++++++++++++--------
 gdb/infrun.c     | 44 ++++++++++++++++++++++----------------------
 gdb/remote.c     |  2 +-
 gdb/thread.c     |  2 +-
 5 files changed, 40 insertions(+), 34 deletions(-)

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 7d7e299ad5ff..73d2b536b35d 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -406,8 +406,7 @@ breakpoints_should_be_inserted_now (void)
       /* Don't remove breakpoints yet if, even though all threads are
 	 stopped, we still have events to process.  */
       for (thread_info *tp : all_non_exited_threads ())
-	if (tp->resumed
-	    && tp->suspend.waitstatus_pending_p)
+	if (tp->resumed () && tp->suspend.waitstatus_pending_p)
 	  return 1;
     }
   return 0;
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 54c097206d16..2f3d85d34eaa 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -293,14 +293,11 @@ class thread_info : public refcounted_object,
      thread is off and running.  */
   bool executing = false;
 
-  /* True if this thread is resumed from infrun's perspective.
-     Note that a thread can be marked both as not-executing and
-     resumed at the same time.  This happens if we try to resume a
-     thread that has a wait status pending.  We shouldn't let the
-     thread really run until that wait status has been processed, but
-     we should not process that wait status if we didn't try to let
-     the thread run.  */
-  bool resumed = false;
+  bool resumed () const
+  { return m_resumed; }
+
+  void set_resumed (bool resumed)
+  { m_resumed = resumed; }
 
   /* Frontend view of the thread state.  Note that the THREAD_RUNNING/
      THREAD_STOPPED states are different from EXECUTING.  When the
@@ -393,6 +390,16 @@ class thread_info : public refcounted_object,
 
   /* Displaced-step state for this thread.  */
   displaced_step_thread_state displaced_step_state;
+
+private:
+  /* True if this thread is resumed from infrun's perspective.
+     Note that a thread can be marked both as not-executing and
+     resumed at the same time.  This happens if we try to resume a
+     thread that has a wait status pending.  We shouldn't let the
+     thread really run until that wait status has been processed, but
+     we should not process that wait status if we didn't try to let
+     the thread run.  */
+  bool m_resumed = false;
 };
 
 /* A gdb::ref_ptr pointer to a thread_info.  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index fcd0f4e10ced..450e6177cb89 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1920,7 +1920,7 @@ start_step_over (void)
 	}
 
       if (tp->control.trap_expected
-	  || tp->resumed
+	  || tp->resumed ()
 	  || tp->executing)
 	{
 	  internal_error (__FILE__, __LINE__,
@@ -1928,7 +1928,7 @@ start_step_over (void)
 			  "trap_expected=%d, resumed=%d, executing=%d\n",
 			  target_pid_to_str (tp->ptid).c_str (),
 			  tp->control.trap_expected,
-			  tp->resumed,
+			  tp->resumed (),
 			  tp->executing);
 	}
 
@@ -1953,7 +1953,7 @@ start_step_over (void)
 
       /* If the thread's step over could not be initiated because no buffers
 	 were available, it was re-added to the global step over chain.  */
-      if (tp->resumed)
+      if (tp->resumed  ())
 	{
 	  infrun_debug_printf ("[%s] was resumed.",
 			       target_pid_to_str (tp->ptid).c_str ());
@@ -2220,7 +2220,7 @@ resume_1 (enum gdb_signal sig)
 	 currently_stepping (tp));
 
       tp->inf->process_target ()->threads_executing = true;
-      tp->resumed = true;
+      tp->set_resumed (true);
 
       /* FIXME: What should we do if we are supposed to resume this
 	 thread with a signal?  Maybe we should maintain a queue of
@@ -2342,7 +2342,7 @@ resume_1 (enum gdb_signal sig)
 
 	      resume_ptid = internal_resume_ptid (user_step);
 	      do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
-	      tp->resumed = true;
+	      tp->set_resumed (true);
 	      return;
 	    }
 	}
@@ -2551,7 +2551,7 @@ resume_1 (enum gdb_signal sig)
     }
 
   do_target_resume (resume_ptid, step, sig);
-  tp->resumed = true;
+  tp->set_resumed (true);
 }
 
 /* Resume the inferior.  SIG is the signal to give the inferior
@@ -2803,7 +2803,7 @@ maybe_set_commit_resumed_all_targets ()
 	 resuming more threads.  */
       bool has_thread_with_pending_status = false;
       for (thread_info *thread : all_non_exited_threads (proc_target))
-	if (thread->resumed && thread->suspend.waitstatus_pending_p)
+	if (thread->resumed () && thread->suspend.waitstatus_pending_p)
 	  {
 	    has_thread_with_pending_status = true;
 	    break;
@@ -3238,7 +3238,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
 		continue;
 	      }
 
-	    if (tp->resumed)
+	    if (tp->resumed ())
 	      {
 		infrun_debug_printf ("[%s] resumed",
 				     target_pid_to_str (tp->ptid).c_str ());
@@ -3263,7 +3263,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
 	      error (_("Command aborted."));
 	  }
       }
-    else if (!cur_thr->resumed && !thread_is_in_step_over_chain (cur_thr))
+    else if (!cur_thr->resumed () && !thread_is_in_step_over_chain (cur_thr))
       {
 	/* The thread wasn't started, and isn't queued, run it now.  */
 	reset_ecs (ecs, cur_thr);
@@ -3408,7 +3408,7 @@ infrun_thread_stop_requested (ptid_t ptid)
       /* Otherwise we can process the (new) pending event now.  Set
 	 it so this pending event is considered by
 	 do_target_wait.  */
-      tp->resumed = true;
+      tp->set_resumed (true);
     }
 }
 
@@ -3505,7 +3505,7 @@ random_pending_event_thread (inferior *inf, ptid_t waiton_ptid)
   auto has_event = [&] (thread_info *tp)
     {
       return (tp->ptid.matches (waiton_ptid)
-	      && tp->resumed
+	      && tp->resumed ()
 	      && tp->suspend.waitstatus_pending_p);
     };
 
@@ -3852,7 +3852,7 @@ prepare_for_detach (void)
 		    }
 		}
 	      else
-		thr->resumed = false;
+		thr->set_resumed (false);
 	    }
 	}
 
@@ -4893,7 +4893,7 @@ handle_one (const wait_one_event &event)
 
       t->stop_requested = 0;
       t->executing = 0;
-      t->resumed = false;
+      t->set_resumed (false);
       t->control.may_range_step = 0;
 
       /* This may be the first time we see the inferior report
@@ -5063,7 +5063,7 @@ stop_all_threads (void)
 
 		  /* The thread may be not executing, but still be
 		     resumed with a pending status to process.  */
-		  t->resumed = false;
+		  t->set_resumed (false);
 		}
 	    }
 
@@ -5808,7 +5808,7 @@ restart_threads (struct thread_info *event_thread)
 	  continue;
 	}
 
-      if (tp->resumed)
+      if (tp->resumed ())
 	{
 	  infrun_debug_printf ("restart threads: [%s] resumed",
 			      target_pid_to_str (tp->ptid).c_str ());
@@ -5820,7 +5820,7 @@ restart_threads (struct thread_info *event_thread)
 	{
 	  infrun_debug_printf ("restart threads: [%s] needs step-over",
 			       target_pid_to_str (tp->ptid).c_str ());
-	  gdb_assert (!tp->resumed);
+	  gdb_assert (!tp->resumed ());
 	  continue;
 	}
 
@@ -5829,7 +5829,7 @@ restart_threads (struct thread_info *event_thread)
 	{
 	  infrun_debug_printf ("restart threads: [%s] has pending status",
 			       target_pid_to_str (tp->ptid).c_str ());
-	  tp->resumed = true;
+	  tp->set_resumed (true);
 	  continue;
 	}
 
@@ -5873,7 +5873,7 @@ static int
 resumed_thread_with_pending_status (struct thread_info *tp,
 				    void *arg)
 {
-  return (tp->resumed
+  return (tp->resumed ()
 	  && tp->suspend.waitstatus_pending_p);
 }
 
@@ -5959,7 +5959,7 @@ finish_step_over (struct execution_control_state *ecs)
 	  /* This was cleared early, by handle_inferior_event.  Set it
 	     so this pending event is considered by
 	     do_target_wait.  */
-	  tp->resumed = true;
+	  tp->set_resumed (true);
 
 	  gdb_assert (!tp->executing);
 
@@ -7519,7 +7519,7 @@ restart_after_all_stop_detach (process_stratum_target *proc_target)
 
       /* If we have a pending event to process, skip resuming the
 	 target and go straight to processing it.  */
-      if (thr->resumed && thr->suspend.waitstatus_pending_p)
+      if (thr->resumed () && thr->suspend.waitstatus_pending_p)
 	return;
     }
 
@@ -7624,7 +7624,7 @@ keep_going_stepped_thread (struct thread_info *tp)
 				     get_frame_address_space (frame),
 				     tp->suspend.stop_pc);
 
-      tp->resumed = true;
+      tp->set_resumed (true);
       resume_ptid = internal_resume_ptid (tp->control.stepping_command);
       do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
     }
@@ -8039,7 +8039,7 @@ static void
 keep_going_pass_signal (struct execution_control_state *ecs)
 {
   gdb_assert (ecs->event_thread->ptid == inferior_ptid);
-  gdb_assert (!ecs->event_thread->resumed);
+  gdb_assert (!ecs->event_thread->resumed ());
 
   /* Save the pc before execution, to compare with pc after stop.  */
   ecs->event_thread->prev_pc
diff --git a/gdb/remote.c b/gdb/remote.c
index 22933eeaeec6..f08616be9b67 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -8132,7 +8132,7 @@ static ptid_t
 first_remote_resumed_thread (remote_target *target)
 {
   for (thread_info *tp : all_non_exited_threads (target, minus_one_ptid))
-    if (tp->resumed)
+    if (tp->resumed ())
       return tp->ptid;
   return null_ptid;
 }
diff --git a/gdb/thread.c b/gdb/thread.c
index 925ed96c3d83..c6c63b742db4 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -708,7 +708,7 @@ void
 set_resumed (process_stratum_target *targ, ptid_t ptid, bool resumed)
 {
   for (thread_info *tp : all_non_exited_threads (targ, ptid))
-    tp->resumed = resumed;
+    tp->set_resumed (resumed);
 }
 
 /* Helper for set_running, that marks one thread either running or
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 06/11] gdb: make thread_info::suspend private, add getters / setters
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (4 preceding siblings ...)
  2021-06-22 16:56 ` [PATCH 05/11] gdb: add setter / getter for thread_info resumed state Simon Marchi
@ 2021-06-22 16:56 ` Simon Marchi
  2021-07-05 15:45   ` Pedro Alves
  2021-06-22 16:57 ` [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status Simon Marchi
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:56 UTC (permalink / raw)
  To: gdb-patches

A following patch will want to take some action when a pending wait
status is set on or removed from a thread.  Add a getter and a setter on
thread_info for the pending waitstatus, so that we can add some code in
the setter later.

The thing is, the pending wait status field is in the
thread_suspend_state, along with other fields that we need to backup
before and restore after the thread does an inferior function call.
Therefore, make the thread_suspend_state member private
(thread_info::suspend becomes thread_info::m_suspend), and add getters /
setters for all of its fields:

 - pending wait status
 - stop signal
 - stop reason
 - stop pc

For the pending wait status, add the additional has_pending_waitstatus
and clear_pending_waitstatus methods.

I think this makes the thread_info interface a bit nicer, because we
now access the fields as:

  thread->stop_pc ()

rather than

  thread->suspend.stop_pc

The stop_pc field being in the `suspend` structure is an implementation
detail of thread_info that callers don't need to be aware of.

For the backup / restore of the thread_suspend_state structure, add
save_suspend_to and restore_suspend_from methods.  You might wonder why
`save_suspend_to`, as opposed to a simple getter like

  thread_suspend_state &suspend ();

I want to make it clear that this is to be used only for backing up and
restoring the suspend state, _not_ to access fields like:

  thread->suspend ()->stop_pc

Adding some getters / setters allows adding some assertions.  I find
that this helps understand how things are supposed to work.  Add:

 - When getting the pending status (pending_waitstatus method), ensure
   that there is a pending status.
 - When setting a pending status (set_pending_waitstatus method), ensure
   there is no pending status.

There is one case I found where this wasn't true - in
remote_target::process_initial_stop_replies - which needed adjustments
to respect that contract.  I think it's because
process_initial_stop_replies is kind of (ab)using the
thread_info::suspend::waitstatus to store some statuses temporarily, for
its internal use (statuses it doesn't intent on leaving pending).

process_initial_stop_replies pulls out stop replies received during the
initial connection using target_wait.  It always stores the received
event in `evthread->suspend.waitstatus`.  But it only sets
waitstatus_pending_p, if it deems the event interesting enough to leave
pending, to be reported to the core:

      if (ws.kind != TARGET_WAITKIND_STOPPED
	  || ws.value.sig != GDB_SIGNAL_0)
	evthread->suspend.waitstatus_pending_p = 1;

It later uses this flag a bit below, to choose which thread to make the
"selected" one:

      if (selected == NULL
	  && thread->suspend.waitstatus_pending_p)
	selected = thread;

And ultimately that's used if the user-visible mode is all-stop, so that
we print the stop for that interesting thread:

  /* In all-stop, we only print the status of one thread, and leave
     others with their status pending.  */
  if (!non_stop)
    {
      thread_info *thread = selected;
      if (thread == NULL)
	thread = lowest_stopped;
      if (thread == NULL)
	thread = first;

      print_one_stopped_thread (thread);
    }

But in any case (all-stop or non-stop), print_one_stopped_thread needs
to access the waitstatus value of these threads that don't have a
pending waitstatus (those that had TARGET_WAITKIND_STOPPED +
GDB_SIGNAL_0).  This doesn't work with the assertions I've
put.

So, change the code to only set the thread's wait status if it is an
interesting one that we are going to leave pending.  If the thread
stopped due to a non-interesting event (TARGET_WAITKIND_STOPPED +
GDB_SIGNAL_0), don't store it.  Adjust print_one_stopped_thread to
understand that if a thread has no pending waitstatus, it's because it
stopped with TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.

The call to set_last_target_status also uses the pending waitstatus.
However, given that the pending waitstatus for the thread may have been
cleared in print_one_stopped_thread (and that there might not even be a
pending waitstatus in the first place, as explained above), it is no
longer possible to do it at this point.  To fix that, move the call to
set_last_target_status in print_one_stopped_thread.  I think this will
preserve the existing behavior, because set_last_target_status is
currently using the current thread's wait status.  And the current
thread is the last one for which print_one_stopped_thread is called.  So
by calling set_last_target_status in print_one_stopped_thread, we'll get
the same result.  set_last_target_status will possibly be called
multiple times, but only the last call will matter.  It just means
possibly more calls to set_last_target_status, but those are cheap.

gdb/ChangeLog:

	* gdbthread.h (class thread_info) <save_suspend_to,
	restore_suspend_from, stop_pc, set_stop_pc,
	has_pending_waitstatus, pending_waitstatus,
	set_pending_waitstatus, clear_pending_waitstatus, stop_signal,
	set_stop_signal, stop_reason, set_stop_reason): New.
	<suspend>: Rename to...
	<m_suspend>: ... this, make private.  Adjust all uses to use one
	of the new methods above.
	* thread.c (thread_info::set_pending_waitstatus,
	thread_info::clear_pending_waitstatus): New.
	* remote.c (class remote_target) <print_one_stopped_thread>: New
	method.
	(print_one_stopped_thread): Rename to...
	(remote_target::print_one_stopped_thread): ... this.  Assume
	that if thread has no pending waitstatus, it's
	TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.  Call
	set_last_target_status.
	(remote_target::process_initial_stop_replies): Don't set pending
	waitstatus if TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.  Don't
	call set_last_target_status.
	(is_pending_fork_parent): Constify param.
	(thread_pending_fork_status): Constify return.
	(is_pending_fork_parent_thread): Adjust.
	(remote_target::remove_new_fork_children): Adjust.

Change-Id: Iedab9653238eaf8231abcf0baa20145acc8b77a7
---
 gdb/breakpoint.c         |   2 +-
 gdb/elf-none-tdep.c      |   2 +-
 gdb/fbsd-tdep.c          |   6 +-
 gdb/gcore.c              |   4 +-
 gdb/gdbthread.h          |  92 +++++++++++-
 gdb/infcmd.c             |  33 ++--
 gdb/inflow.c             |   2 +-
 gdb/infrun.c             | 315 ++++++++++++++++++---------------------
 gdb/linux-fork.c         |   3 +-
 gdb/linux-nat.c          |  12 +-
 gdb/linux-tdep.c         |   2 +-
 gdb/python/py-inferior.c |   2 +-
 gdb/record-btrace.c      |   3 +-
 gdb/record-full.c        |   3 +-
 gdb/remote.c             |  66 ++++----
 gdb/thread.c             |  25 +++-
 16 files changed, 332 insertions(+), 240 deletions(-)

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 73d2b536b35d..2b643b6790e0 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -406,7 +406,7 @@ breakpoints_should_be_inserted_now (void)
       /* Don't remove breakpoints yet if, even though all threads are
 	 stopped, we still have events to process.  */
       for (thread_info *tp : all_non_exited_threads ())
-	if (tp->resumed () && tp->suspend.waitstatus_pending_p)
+	if (tp->resumed () && tp->has_pending_waitstatus ())
 	  return 1;
     }
   return 0;
diff --git a/gdb/elf-none-tdep.c b/gdb/elf-none-tdep.c
index 4cbb664607e7..52a6281290fa 100644
--- a/gdb/elf-none-tdep.c
+++ b/gdb/elf-none-tdep.c
@@ -92,7 +92,7 @@ elf_none_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
      that stopped SIGNALLED_THR.  */
   gdb_signal stop_signal;
   if (signalled_thr != nullptr)
-    stop_signal = signalled_thr->suspend.stop_signal;
+    stop_signal = signalled_thr->stop_signal ();
   else
     stop_signal = GDB_SIGNAL_0;
 
diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
index 5cb6fa31500c..eb792c36a54d 100644
--- a/gdb/fbsd-tdep.c
+++ b/gdb/fbsd-tdep.c
@@ -634,7 +634,7 @@ fbsd_core_xfer_siginfo (struct gdbarch *gdbarch, gdb_byte *readbuf,
 static int
 find_signalled_thread (struct thread_info *info, void *data)
 {
-  if (info->suspend.stop_signal != GDB_SIGNAL_0
+  if (info->stop_signal () != GDB_SIGNAL_0
       && info->ptid.pid () == inferior_ptid.pid ())
     return 1;
 
@@ -708,7 +708,7 @@ fbsd_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
      In case there's more than one signalled thread, prefer the
      current thread, if it is signalled.  */
   curr_thr = inferior_thread ();
-  if (curr_thr->suspend.stop_signal != GDB_SIGNAL_0)
+  if (curr_thr->stop_signal () != GDB_SIGNAL_0)
     signalled_thr = curr_thr;
   else
     {
@@ -717,7 +717,7 @@ fbsd_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 	signalled_thr = curr_thr;
     }
 
-  enum gdb_signal stop_signal = signalled_thr->suspend.stop_signal;
+  enum gdb_signal stop_signal = signalled_thr->stop_signal ();
   gcore_elf_build_thread_register_notes (gdbarch, signalled_thr, stop_signal,
 					 obfd, &note_data, note_size);
   for (thread_info *thr : current_inferior ()->non_exited_threads ())
diff --git a/gdb/gcore.c b/gdb/gcore.c
index 76e856d71a81..c768b2415983 100644
--- a/gdb/gcore.c
+++ b/gdb/gcore.c
@@ -586,11 +586,11 @@ gcore_find_signalled_thread ()
 {
   thread_info *curr_thr = inferior_thread ();
   if (curr_thr->state != THREAD_EXITED
-      && curr_thr->suspend.stop_signal != GDB_SIGNAL_0)
+      && curr_thr->stop_signal () != GDB_SIGNAL_0)
     return curr_thr;
 
   for (thread_info *thr : current_inferior ()->non_exited_threads ())
-    if (thr->suspend.stop_signal != GDB_SIGNAL_0)
+    if (thr->stop_signal () != GDB_SIGNAL_0)
       return thr;
 
   /* Default to the current thread, unless it has exited.  */
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 2f3d85d34eaa..5ea08a13ee5f 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -310,9 +310,91 @@ class thread_info : public refcounted_object,
      See `struct thread_control_state'.  */
   thread_control_state control;
 
-  /* State of inferior thread to restore after GDB is done with an inferior
-     call.  See `struct thread_suspend_state'.  */
-  thread_suspend_state suspend;
+  /* Save M_SUSPEND to SUSPEND.  */
+
+  void save_suspend_to (thread_suspend_state &suspend) const
+  {
+    suspend = m_suspend;
+  }
+
+  /* Restore M_SUSPEND from SUSPEND.  */
+
+  void restore_suspend_from (const thread_suspend_state &suspend)
+  {
+    m_suspend = suspend;
+  }
+
+  /* Return this thread's stop PC.  */
+
+  CORE_ADDR stop_pc () const
+  {
+    return m_suspend.stop_pc;
+  }
+
+  /* Set this thread's stop PC.  */
+
+  void set_stop_pc (CORE_ADDR stop_pc)
+  {
+    m_suspend.stop_pc = stop_pc;
+  }
+
+  /* Return true if this thread has a pending wait status.  */
+
+  bool has_pending_waitstatus () const
+  {
+    return m_suspend.waitstatus_pending_p;
+  }
+
+  /* Get this thread's pending wait status.
+
+     May only be called if has_pending_waitstatus returns true.  */
+
+  const target_waitstatus &pending_waitstatus () const
+  {
+    gdb_assert (this->has_pending_waitstatus ());
+
+    return m_suspend.waitstatus;
+  }
+
+  /* Set this thread's pending wait status.
+
+     May only be called if has_pending_waitstatus returns false.  */
+
+  void set_pending_waitstatus (const target_waitstatus &ws);
+
+  /* Clear this thread's pending wait status.
+
+     May only be called if has_pending_waitstatus returns true.  */
+
+  void clear_pending_waitstatus ();
+
+  /* Return this thread's stop signal.  */
+
+  gdb_signal stop_signal () const
+  {
+    return m_suspend.stop_signal;
+  }
+
+  /* Set this thread's stop signal.  */
+
+  void set_stop_signal (gdb_signal sig)
+  {
+    m_suspend.stop_signal = sig;
+  }
+
+  /* Return this thread's stop reason.  */
+
+  target_stop_reason stop_reason () const
+  {
+    return m_suspend.stop_reason;
+  }
+
+  /* Set this thread's stop reason.  */
+
+  void set_stop_reason (target_stop_reason reason)
+  {
+    m_suspend.stop_reason = reason;
+  }
 
   int current_line = 0;
   struct symtab *current_symtab = NULL;
@@ -400,6 +482,10 @@ class thread_info : public refcounted_object,
      we should not process that wait status if we didn't try to let
      the thread run.  */
   bool m_resumed = false;
+
+  /* State of inferior thread to restore after GDB is done with an inferior
+     call.  See `struct thread_suspend_state'.  */
+  thread_suspend_state m_suspend;
 };
 
 /* A gdb::ref_ptr pointer to a thread_info.  */
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 8190ba36565e..0a5edef69824 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -298,11 +298,11 @@ post_create_inferior (int from_tty)
      missing registers info), ignore it.  */
   thread_info *thr = inferior_thread ();
 
-  thr->suspend.stop_pc = 0;
+  thr->set_stop_pc (0);
   try
     {
       regcache *rc = get_thread_regcache (thr);
-      thr->suspend.stop_pc = regcache_read_pc (rc);
+      thr->set_stop_pc (regcache_read_pc (rc));
     }
   catch (const gdb_exception_error &ex)
     {
@@ -534,9 +534,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_FIRST_INSN)
     {
       thread_info *thr = inferior_thread ();
-      thr->suspend.waitstatus_pending_p = 1;
-      thr->suspend.waitstatus.kind = TARGET_WAITKIND_STOPPED;
-      thr->suspend.waitstatus.value.sig = GDB_SIGNAL_0;
+      target_waitstatus ws;
+      ws.kind = TARGET_WAITKIND_STOPPED;
+      ws.value.sig = GDB_SIGNAL_0;
+      thr->set_pending_waitstatus (ws);
     }
 
   /* Start the target running.  Do not use -1 continuation as it would skip
@@ -1223,15 +1224,15 @@ signal_command (const char *signum_exp, int from_tty)
 	  if (tp == current)
 	    continue;
 
-	  if (tp->suspend.stop_signal != GDB_SIGNAL_0
-	      && signal_pass_state (tp->suspend.stop_signal))
+	  if (tp->stop_signal () != GDB_SIGNAL_0
+	      && signal_pass_state (tp->stop_signal ()))
 	    {
 	      if (!must_confirm)
 		printf_unfiltered (_("Note:\n"));
 	      printf_unfiltered (_("  Thread %s previously stopped with signal %s, %s.\n"),
 				 print_thread_id (tp),
-				 gdb_signal_to_name (tp->suspend.stop_signal),
-				 gdb_signal_to_string (tp->suspend.stop_signal));
+				 gdb_signal_to_name (tp->stop_signal ()),
+				 gdb_signal_to_string (tp->stop_signal ()));
 	      must_confirm = 1;
 	    }
 	}
@@ -1294,7 +1295,7 @@ queue_signal_command (const char *signum_exp, int from_tty)
     error (_("Signal handling set to not pass this signal to the program."));
 
   tp = inferior_thread ();
-  tp->suspend.stop_signal = oursig;
+  tp->set_stop_signal (oursig);
 }
 
 /* Data for the FSM that manages the until (with no argument)
@@ -1914,7 +1915,7 @@ info_program_command (const char *args, int from_tty)
 
   target_files_info ();
   printf_filtered (_("Program stopped at %s.\n"),
-		   paddress (target_gdbarch (), tp->suspend.stop_pc));
+		   paddress (target_gdbarch (), tp->stop_pc ()));
   if (tp->control.stop_step)
     printf_filtered (_("It stopped after being stepped.\n"));
   else if (stat != 0)
@@ -1933,11 +1934,11 @@ info_program_command (const char *args, int from_tty)
 	  stat = bpstat_num (&bs, &num);
 	}
     }
-  else if (tp->suspend.stop_signal != GDB_SIGNAL_0)
+  else if (tp->stop_signal () != GDB_SIGNAL_0)
     {
       printf_filtered (_("It stopped with signal %s, %s.\n"),
-		       gdb_signal_to_name (tp->suspend.stop_signal),
-		       gdb_signal_to_string (tp->suspend.stop_signal));
+		       gdb_signal_to_name (tp->stop_signal ()),
+		       gdb_signal_to_string (tp->stop_signal ()));
     }
 
   if (from_tty)
@@ -2425,7 +2426,7 @@ proceed_after_attach (inferior *inf)
   for (thread_info *thread : inf->non_exited_threads ())
     if (!thread->executing
 	&& !thread->stop_requested
-	&& thread->suspend.stop_signal == GDB_SIGNAL_0)
+	&& thread->stop_signal () == GDB_SIGNAL_0)
       {
 	switch_to_thread (thread);
 	clear_proceed_status (0);
@@ -2500,7 +2501,7 @@ attach_post_wait (int from_tty, enum attach_post_wait_mode mode)
 	proceed_after_attach (inferior);
       else
 	{
-	  if (inferior_thread ()->suspend.stop_signal == GDB_SIGNAL_0)
+	  if (inferior_thread ()->stop_signal () == GDB_SIGNAL_0)
 	    {
 	      clear_proceed_status (0);
 	      proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT);
diff --git a/gdb/inflow.c b/gdb/inflow.c
index f9917d6a81cd..970450632159 100644
--- a/gdb/inflow.c
+++ b/gdb/inflow.c
@@ -530,7 +530,7 @@ child_interrupt (struct target_ops *self)
 	  resumed = thr;
 	  break;
 	}
-      if (thr->suspend.waitstatus_pending_p)
+      if (thr->has_pending_waitstatus ())
 	resumed = thr;
     }
 
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 450e6177cb89..c973b34fa1d6 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -899,7 +899,7 @@ proceed_after_vfork_done (struct thread_info *thread,
       && thread->state == THREAD_RUNNING
       && !thread->executing
       && !thread->stop_requested
-      && thread->suspend.stop_signal == GDB_SIGNAL_0)
+      && thread->stop_signal () == GDB_SIGNAL_0)
     {
       infrun_debug_printf ("resuming vfork parent thread %s",
 			   target_pid_to_str (thread->ptid).c_str ());
@@ -2152,7 +2152,7 @@ do_target_resume (ptid_t resume_ptid, bool step, enum gdb_signal sig)
 
   /* Avoid confusing the next resume, if the next stop/resume
      happens to apply to another thread.  */
-  tp->suspend.stop_signal = GDB_SIGNAL_0;
+  tp->set_stop_signal (GDB_SIGNAL_0);
 
   /* Advise target which signals may be handled silently.
 
@@ -2210,13 +2210,13 @@ resume_1 (enum gdb_signal sig)
   gdb_assert (!tp->stop_requested);
   gdb_assert (!thread_is_in_step_over_chain (tp));
 
-  if (tp->suspend.waitstatus_pending_p)
+  if (tp->has_pending_waitstatus ())
     {
       infrun_debug_printf
 	("thread %s has pending wait "
 	 "status %s (currently_stepping=%d).",
 	 target_pid_to_str (tp->ptid).c_str (),
-	 target_waitstatus_to_string (&tp->suspend.waitstatus).c_str (),
+	 target_waitstatus_to_string (&tp->pending_waitstatus ()).c_str (),
 	 currently_stepping (tp));
 
       tp->inf->process_target ()->threads_executing = true;
@@ -2232,7 +2232,7 @@ resume_1 (enum gdb_signal sig)
 		   target_pid_to_str (tp->ptid).c_str ());
 	}
 
-      tp->suspend.stop_signal = GDB_SIGNAL_0;
+      tp->set_stop_signal (GDB_SIGNAL_0);
 
       if (target_can_async_p ())
 	{
@@ -2618,31 +2618,31 @@ clear_proceed_status_thread (struct thread_info *tp)
 
   /* If we're starting a new sequence, then the previous finished
      single-step is no longer relevant.  */
-  if (tp->suspend.waitstatus_pending_p)
+  if (tp->has_pending_waitstatus ())
     {
-      if (tp->suspend.stop_reason == TARGET_STOPPED_BY_SINGLE_STEP)
+      if (tp->stop_reason () == TARGET_STOPPED_BY_SINGLE_STEP)
 	{
 	  infrun_debug_printf ("pending event of %s was a finished step. "
 			       "Discarding.",
 			       target_pid_to_str (tp->ptid).c_str ());
 
-	  tp->suspend.waitstatus_pending_p = 0;
-	  tp->suspend.stop_reason = TARGET_STOPPED_BY_NO_REASON;
+	  tp->clear_pending_waitstatus ();
+	  tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
 	}
       else
 	{
 	  infrun_debug_printf
 	    ("thread %s has pending wait status %s (currently_stepping=%d).",
 	     target_pid_to_str (tp->ptid).c_str (),
-	     target_waitstatus_to_string (&tp->suspend.waitstatus).c_str (),
+	     target_waitstatus_to_string (&tp->pending_waitstatus ()).c_str (),
 	     currently_stepping (tp));
 	}
     }
 
   /* If this signal should not be seen by program, give it zero.
      Used for debugging signals.  */
-  if (!signal_pass_state (tp->suspend.stop_signal))
-    tp->suspend.stop_signal = GDB_SIGNAL_0;
+  if (!signal_pass_state (tp->stop_signal ()))
+    tp->set_stop_signal (GDB_SIGNAL_0);
 
   delete tp->thread_fsm;
   tp->thread_fsm = NULL;
@@ -2803,7 +2803,7 @@ maybe_set_commit_resumed_all_targets ()
 	 resuming more threads.  */
       bool has_thread_with_pending_status = false;
       for (thread_info *thread : all_non_exited_threads (proc_target))
-	if (thread->resumed () && thread->suspend.waitstatus_pending_p)
+	if (thread->resumed () && thread->has_pending_waitstatus ())
 	  {
 	    has_thread_with_pending_status = true;
 	    break;
@@ -3096,7 +3096,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
 
   if (addr == (CORE_ADDR) -1)
     {
-      if (pc == cur_thr->suspend.stop_pc
+      if (pc == cur_thr->stop_pc ()
 	  && breakpoint_here_p (aspace, pc) == ordinary_breakpoint_here
 	  && execution_direction != EXEC_REVERSE)
 	/* There is a breakpoint at the address we will resume at,
@@ -3121,7 +3121,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
     }
 
   if (siggnal != GDB_SIGNAL_DEFAULT)
-    cur_thr->suspend.stop_signal = siggnal;
+    cur_thr->set_stop_signal (siggnal);
 
   /* If an exception is thrown from this point on, make sure to
      propagate GDB's knowledge of the executing state to the
@@ -3242,7 +3242,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
 	      {
 		infrun_debug_printf ("[%s] resumed",
 				     target_pid_to_str (tp->ptid).c_str ());
-		gdb_assert (tp->executing || tp->suspend.waitstatus_pending_p);
+		gdb_assert (tp->executing || tp->has_pending_waitstatus ());
 		continue;
 	      }
 
@@ -3387,11 +3387,12 @@ infrun_thread_stop_requested (ptid_t ptid)
 	 know about that yet, queue a pending event, as if the
 	 thread had just stopped now.  Unless the thread already had
 	 a pending event.  */
-      if (!tp->suspend.waitstatus_pending_p)
+      if (!tp->has_pending_waitstatus ())
 	{
-	  tp->suspend.waitstatus_pending_p = 1;
-	  tp->suspend.waitstatus.kind = TARGET_WAITKIND_STOPPED;
-	  tp->suspend.waitstatus.value.sig = GDB_SIGNAL_0;
+	  target_waitstatus ws;
+	  ws.kind = TARGET_WAITKIND_STOPPED;
+	  ws.value.sig = GDB_SIGNAL_0;
+	  tp->set_pending_waitstatus (ws);
 	}
 
       /* Clear the inline-frame state, since we're re-processing the
@@ -3506,7 +3507,7 @@ random_pending_event_thread (inferior *inf, ptid_t waiton_ptid)
     {
       return (tp->ptid.matches (waiton_ptid)
 	      && tp->resumed ()
-	      && tp->suspend.waitstatus_pending_p);
+	      && tp->has_pending_waitstatus ());
     };
 
   /* First see how many events we have.  Count only resumed threads
@@ -3567,13 +3568,13 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
       /* We have a specific thread to check.  */
       tp = find_thread_ptid (inf, ptid);
       gdb_assert (tp != NULL);
-      if (!tp->suspend.waitstatus_pending_p)
+      if (!tp->has_pending_waitstatus ())
 	tp = NULL;
     }
 
   if (tp != NULL
-      && (tp->suspend.stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT
-	  || tp->suspend.stop_reason == TARGET_STOPPED_BY_HW_BREAKPOINT))
+      && (tp->stop_reason () == TARGET_STOPPED_BY_SW_BREAKPOINT
+	  || tp->stop_reason () == TARGET_STOPPED_BY_HW_BREAKPOINT))
     {
       struct regcache *regcache = get_thread_regcache (tp);
       struct gdbarch *gdbarch = regcache->arch ();
@@ -3582,11 +3583,11 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
 
       pc = regcache_read_pc (regcache);
 
-      if (pc != tp->suspend.stop_pc)
+      if (pc != tp->stop_pc ())
 	{
 	  infrun_debug_printf ("PC of %s changed.  was=%s, now=%s",
 			       target_pid_to_str (tp->ptid).c_str (),
-			       paddress (gdbarch, tp->suspend.stop_pc),
+			       paddress (gdbarch, tp->stop_pc ()),
 			       paddress (gdbarch, pc));
 	  discard = 1;
 	}
@@ -3604,8 +3605,11 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
 	  infrun_debug_printf ("pending event of %s cancelled.",
 			       target_pid_to_str (tp->ptid).c_str ());
 
-	  tp->suspend.waitstatus.kind = TARGET_WAITKIND_SPURIOUS;
-	  tp->suspend.stop_reason = TARGET_STOPPED_BY_NO_REASON;
+	  tp->clear_pending_waitstatus ();
+	  target_waitstatus ws;
+	  ws.kind = TARGET_WAITKIND_SPURIOUS;
+	  tp->set_pending_waitstatus (ws);
+	  tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
 	}
     }
 
@@ -3613,13 +3617,13 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
     {
       infrun_debug_printf ("Using pending wait status %s for %s.",
 			   target_waitstatus_to_string
-			     (&tp->suspend.waitstatus).c_str (),
+			     (&tp->pending_waitstatus ()).c_str (),
 			   target_pid_to_str (tp->ptid).c_str ());
 
       /* Now that we've selected our final event LWP, un-adjust its PC
 	 if it was a software breakpoint (and the target doesn't
 	 always adjust the PC itself).  */
-      if (tp->suspend.stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT
+      if (tp->stop_reason () == TARGET_STOPPED_BY_SW_BREAKPOINT
 	  && !target_supports_stopped_by_sw_breakpoint ())
 	{
 	  struct regcache *regcache;
@@ -3639,9 +3643,9 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
 	    }
 	}
 
-      tp->suspend.stop_reason = TARGET_STOPPED_BY_NO_REASON;
-      *status = tp->suspend.waitstatus;
-      tp->suspend.waitstatus_pending_p = 0;
+      tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
+      *status = tp->pending_waitstatus ();
+      tp->clear_pending_waitstatus ();
 
       /* Wake up the event loop again, until all pending events are
 	 processed.  */
@@ -4292,7 +4296,7 @@ context_switch (execution_control_state *ecs)
 
 static void
 adjust_pc_after_break (struct thread_info *thread,
-		       struct target_waitstatus *ws)
+		       const target_waitstatus *ws)
 {
   struct regcache *regcache;
   struct gdbarch *gdbarch;
@@ -4520,7 +4524,7 @@ handle_syscall_event (struct execution_control_state *ecs)
 
   regcache = get_thread_regcache (ecs->event_thread);
   syscall_number = ecs->ws.value.syscall_number;
-  ecs->event_thread->suspend.stop_pc = regcache_read_pc (regcache);
+  ecs->event_thread->set_stop_pc (regcache_read_pc (regcache));
 
   if (catch_syscall_enabled () > 0
       && catching_syscall_number (syscall_number) > 0)
@@ -4529,7 +4533,7 @@ handle_syscall_event (struct execution_control_state *ecs)
 
       ecs->event_thread->control.stop_bpstat
 	= bpstat_stop_status (regcache->aspace (),
-			      ecs->event_thread->suspend.stop_pc,
+			      ecs->event_thread->stop_pc (),
 			      ecs->event_thread, &ecs->ws);
 
       if (handle_stop_requested (ecs))
@@ -4564,7 +4568,7 @@ fill_in_stop_func (struct gdbarch *gdbarch,
 
       /* Don't care about return value; stop_func_start and stop_func_name
 	 will both be 0 if it doesn't work.  */
-      find_pc_partial_function_sym (ecs->event_thread->suspend.stop_pc,
+      find_pc_partial_function_sym (ecs->event_thread->stop_pc (),
 				    &gsi,
 				    &ecs->stop_func_start,
 				    &ecs->stop_func_end,
@@ -4717,8 +4721,7 @@ save_waitstatus (struct thread_info *tp, const target_waitstatus *ws)
 		       tp->ptid.tid ());
 
   /* Record for later.  */
-  tp->suspend.waitstatus = *ws;
-  tp->suspend.waitstatus_pending_p = 1;
+  tp->set_pending_waitstatus (*ws);
 
   if (ws->kind == TARGET_WAITKIND_STOPPED
       && ws->value.sig == GDB_SIGNAL_TRAP)
@@ -4727,48 +4730,28 @@ save_waitstatus (struct thread_info *tp, const target_waitstatus *ws)
       const address_space *aspace = regcache->aspace ();
       CORE_ADDR pc = regcache_read_pc (regcache);
 
-      adjust_pc_after_break (tp, &tp->suspend.waitstatus);
+      adjust_pc_after_break (tp, &tp->pending_waitstatus ());
 
       scoped_restore_current_thread restore_thread;
       switch_to_thread (tp);
 
       if (target_stopped_by_watchpoint ())
-	{
-	  tp->suspend.stop_reason
-	    = TARGET_STOPPED_BY_WATCHPOINT;
-	}
+	tp->set_stop_reason (TARGET_STOPPED_BY_WATCHPOINT);
       else if (target_supports_stopped_by_sw_breakpoint ()
 	       && target_stopped_by_sw_breakpoint ())
-	{
-	  tp->suspend.stop_reason
-	    = TARGET_STOPPED_BY_SW_BREAKPOINT;
-	}
+	tp->set_stop_reason (TARGET_STOPPED_BY_SW_BREAKPOINT);
       else if (target_supports_stopped_by_hw_breakpoint ()
 	       && target_stopped_by_hw_breakpoint ())
-	{
-	  tp->suspend.stop_reason
-	    = TARGET_STOPPED_BY_HW_BREAKPOINT;
-	}
+	tp->set_stop_reason (TARGET_STOPPED_BY_HW_BREAKPOINT);
       else if (!target_supports_stopped_by_hw_breakpoint ()
-	       && hardware_breakpoint_inserted_here_p (aspace,
-						       pc))
-	{
-	  tp->suspend.stop_reason
-	    = TARGET_STOPPED_BY_HW_BREAKPOINT;
-	}
+	       && hardware_breakpoint_inserted_here_p (aspace, pc))
+	tp->set_stop_reason (TARGET_STOPPED_BY_HW_BREAKPOINT);
       else if (!target_supports_stopped_by_sw_breakpoint ()
-	       && software_breakpoint_inserted_here_p (aspace,
-						       pc))
-	{
-	  tp->suspend.stop_reason
-	    = TARGET_STOPPED_BY_SW_BREAKPOINT;
-	}
+	       && software_breakpoint_inserted_here_p (aspace, pc))
+	tp->set_stop_reason (TARGET_STOPPED_BY_SW_BREAKPOINT);
       else if (!thread_has_single_step_breakpoints_set (tp)
 	       && currently_stepping (tp))
-	{
-	  tp->suspend.stop_reason
-	    = TARGET_STOPPED_BY_SINGLE_STEP;
-	}
+	tp->set_stop_reason (TARGET_STOPPED_BY_SINGLE_STEP);
     }
 }
 
@@ -4909,9 +4892,7 @@ handle_one (const wait_one_event &event)
 	  && event.ws.value.sig == GDB_SIGNAL_0)
 	{
 	  /* We caught the event that we intended to catch, so
-	     there's no event pending.  */
-	  t->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
-	  t->suspend.waitstatus_pending_p = 0;
+	     there's no event to save as pending.  */
 
 	  if (displaced_step_finish (t, GDB_SIGNAL_0)
 	      == DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED)
@@ -4952,12 +4933,11 @@ handle_one (const wait_one_event &event)
 	    }
 
 	  regcache = get_thread_regcache (t);
-	  t->suspend.stop_pc = regcache_read_pc (regcache);
+	  t->set_stop_pc (regcache_read_pc (regcache));
 
 	  infrun_debug_printf ("saved stop_pc=%s for %s "
 			       "(currently_stepping=%d)",
-			       paddress (target_gdbarch (),
-					 t->suspend.stop_pc),
+			       paddress (target_gdbarch (), t->stop_pc ()),
 			       target_pid_to_str (t->ptid).c_str (),
 			       currently_stepping (t));
 	}
@@ -5193,8 +5173,7 @@ handle_no_resumed (struct execution_control_state *ecs)
 	}
 
       if (!ignore_event
-	  && (thread->executing
-	      || thread->suspend.waitstatus_pending_p))
+	  && (thread->executing || thread->has_pending_waitstatus ()))
 	{
 	  /* Either there were no unwaited-for children left in the
 	     target at some point, but there are now, or some target
@@ -5355,7 +5334,7 @@ handle_inferior_event (struct execution_control_state *ecs)
 
 	    ecs->event_thread->control.stop_bpstat
 	      = bpstat_stop_status (regcache->aspace (),
-				    ecs->event_thread->suspend.stop_pc,
+				    ecs->event_thread->stop_pc (),
 				    ecs->event_thread, &ecs->ws);
 
 	    if (handle_stop_requested (ecs))
@@ -5372,7 +5351,7 @@ handle_inferior_event (struct execution_control_state *ecs)
 	       gdb of events.  This allows the user to get control
 	       and place breakpoints in initializer routines for
 	       dynamically loaded objects (among other things).  */
-	    ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+	    ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 	    if (stop_on_solib_events)
 	      {
 		/* Make sure we print "Stopped due to solib-event" in
@@ -5593,12 +5572,12 @@ handle_inferior_event (struct execution_control_state *ecs)
 	 and not immediately.  */
       ecs->event_thread->pending_follow = ecs->ws;
 
-      ecs->event_thread->suspend.stop_pc
-	= regcache_read_pc (get_thread_regcache (ecs->event_thread));
+      ecs->event_thread->set_stop_pc
+	(regcache_read_pc (get_thread_regcache (ecs->event_thread)));
 
       ecs->event_thread->control.stop_bpstat
 	= bpstat_stop_status (get_current_regcache ()->aspace (),
-			      ecs->event_thread->suspend.stop_pc,
+			      ecs->event_thread->stop_pc (),
 			      ecs->event_thread, &ecs->ws);
 
       if (handle_stop_requested (ecs))
@@ -5613,7 +5592,7 @@ handle_inferior_event (struct execution_control_state *ecs)
 	  bool follow_child
 	    = (follow_fork_mode_string == follow_fork_mode_child);
 
-	  ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+	  ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 
 	  process_stratum_target *targ
 	    = ecs->event_thread->inf->process_target ();
@@ -5705,12 +5684,12 @@ handle_inferior_event (struct execution_control_state *ecs)
 	 execd thread for that case (this is a nop otherwise).  */
       ecs->event_thread = inferior_thread ();
 
-      ecs->event_thread->suspend.stop_pc
-	= regcache_read_pc (get_thread_regcache (ecs->event_thread));
+      ecs->event_thread->set_stop_pc
+	(regcache_read_pc (get_thread_regcache (ecs->event_thread)));
 
       ecs->event_thread->control.stop_bpstat
 	= bpstat_stop_status (get_current_regcache ()->aspace (),
-			      ecs->event_thread->suspend.stop_pc,
+			      ecs->event_thread->stop_pc (),
 			      ecs->event_thread, &ecs->ws);
 
       /* Note that this may be referenced from inside
@@ -5724,7 +5703,7 @@ handle_inferior_event (struct execution_control_state *ecs)
       /* If no catchpoint triggered for this, then keep going.  */
       if (!bpstat_causes_stop (ecs->event_thread->control.stop_bpstat))
 	{
-	  ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+	  ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 	  keep_going (ecs);
 	  return;
 	}
@@ -5761,8 +5740,8 @@ handle_inferior_event (struct execution_control_state *ecs)
       infrun_debug_printf ("stopped");
 
       delete_just_stopped_threads_single_step_breakpoints ();
-      ecs->event_thread->suspend.stop_pc
-	= regcache_read_pc (get_thread_regcache (inferior_thread ()));
+      ecs->event_thread->set_stop_pc
+	(regcache_read_pc (get_thread_regcache (inferior_thread ())));
 
       if (handle_stop_requested (ecs))
 	return;
@@ -5812,7 +5791,7 @@ restart_threads (struct thread_info *event_thread)
 	{
 	  infrun_debug_printf ("restart threads: [%s] resumed",
 			      target_pid_to_str (tp->ptid).c_str ());
-	  gdb_assert (tp->executing || tp->suspend.waitstatus_pending_p);
+	  gdb_assert (tp->executing || tp->has_pending_waitstatus ());
 	  continue;
 	}
 
@@ -5825,7 +5804,7 @@ restart_threads (struct thread_info *event_thread)
 	}
 
 
-      if (tp->suspend.waitstatus_pending_p)
+      if (tp->has_pending_waitstatus ())
 	{
 	  infrun_debug_printf ("restart threads: [%s] has pending status",
 			       target_pid_to_str (tp->ptid).c_str ());
@@ -5873,8 +5852,7 @@ static int
 resumed_thread_with_pending_status (struct thread_info *tp,
 				    void *arg)
 {
-  return (tp->resumed ()
-	  && tp->suspend.waitstatus_pending_p);
+  return tp->resumed () && tp->has_pending_waitstatus ();
 }
 
 /* Called when we get an event that may finish an in-line or
@@ -5886,8 +5864,7 @@ resumed_thread_with_pending_status (struct thread_info *tp,
 static int
 finish_step_over (struct execution_control_state *ecs)
 {
-  displaced_step_finish (ecs->event_thread,
-			 ecs->event_thread->suspend.stop_signal);
+  displaced_step_finish (ecs->event_thread, ecs->event_thread->stop_signal ());
 
   bool had_step_over_info = step_over_info_valid_p ();
 
@@ -5964,12 +5941,11 @@ finish_step_over (struct execution_control_state *ecs)
 	  gdb_assert (!tp->executing);
 
 	  regcache = get_thread_regcache (tp);
-	  tp->suspend.stop_pc = regcache_read_pc (regcache);
+	  tp->set_stop_pc (regcache_read_pc (regcache));
 
 	  infrun_debug_printf ("saved stop_pc=%s for %s "
 			       "(currently_stepping=%d)",
-			       paddress (target_gdbarch (),
-					 tp->suspend.stop_pc),
+			       paddress (target_gdbarch (), tp->stop_pc ()),
 			       target_pid_to_str (tp->ptid).c_str (),
 			       currently_stepping (tp));
 
@@ -6002,7 +5978,7 @@ handle_signal_stop (struct execution_control_state *ecs)
 
   gdb_assert (ecs->ws.kind == TARGET_WAITKIND_STOPPED);
 
-  ecs->event_thread->suspend.stop_signal = ecs->ws.value.sig;
+  ecs->event_thread->set_stop_signal (ecs->ws.value.sig);
 
   /* Do we need to clean up the state of a thread that has
      completed a displaced single-step?  (Doing so usually affects
@@ -6014,11 +5990,11 @@ handle_signal_stop (struct execution_control_state *ecs)
      the user wanted this thread to be stopped, pretend we got a
      SIG0 (generic unsignaled stop).  */
   if (ecs->event_thread->stop_requested
-      && ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP)
-    ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+      && ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP)
+    ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 
-  ecs->event_thread->suspend.stop_pc
-    = regcache_read_pc (get_thread_regcache (ecs->event_thread));
+  ecs->event_thread->set_stop_pc
+    (regcache_read_pc (get_thread_regcache (ecs->event_thread)));
 
   context_switch (ecs);
 
@@ -6030,9 +6006,8 @@ handle_signal_stop (struct execution_control_state *ecs)
       struct regcache *regcache = get_thread_regcache (ecs->event_thread);
       struct gdbarch *reg_gdbarch = regcache->arch ();
 
-      infrun_debug_printf ("stop_pc=%s",
-			   paddress (reg_gdbarch,
-				     ecs->event_thread->suspend.stop_pc));
+      infrun_debug_printf
+	("stop_pc=%s", paddress (reg_gdbarch, ecs->event_thread->stop_pc ()));
       if (target_stopped_by_watchpoint ())
 	{
 	  CORE_ADDR addr;
@@ -6080,13 +6055,13 @@ handle_signal_stop (struct execution_control_state *ecs)
      GDB_SIGNAL_0, meaning: stopped for no particular reason
      other than GDB's request.  */
   if (stop_soon == STOP_QUIETLY_NO_SIGSTOP
-      && (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_STOP
-	  || ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
-	  || ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_0))
+      && (ecs->event_thread->stop_signal () == GDB_SIGNAL_STOP
+	  || ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
+	  || ecs->event_thread->stop_signal () == GDB_SIGNAL_0))
     {
       stop_print_frame = true;
       stop_waiting (ecs);
-      ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+      ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
       return;
     }
 
@@ -6095,7 +6070,7 @@ handle_signal_stop (struct execution_control_state *ecs)
   gdbarch = get_frame_arch (frame);
 
   /* Pull the single step breakpoints out of the target.  */
-  if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP)
+  if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP)
     {
       struct regcache *regcache;
       CORE_ADDR pc;
@@ -6127,7 +6102,7 @@ handle_signal_stop (struct execution_control_state *ecs)
     }
   delete_just_stopped_threads_single_step_breakpoints ();
 
-  if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
+  if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
       && ecs->event_thread->control.trap_expected
       && ecs->event_thread->stepping_over_watchpoint)
     stopped_by_watchpoint = 0;
@@ -6204,16 +6179,16 @@ handle_signal_stop (struct execution_control_state *ecs)
 	 skip_inline_frames call would break things.  Fortunately
 	 that's an extremely unlikely scenario.  */
       if (!pc_at_non_inline_function (aspace,
-				      ecs->event_thread->suspend.stop_pc,
+				      ecs->event_thread->stop_pc (),
 				      &ecs->ws)
-	  && !(ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
+	  && !(ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
 	       && ecs->event_thread->control.trap_expected
 	       && pc_at_non_inline_function (aspace,
 					     ecs->event_thread->prev_pc,
 					     &ecs->ws)))
 	{
 	  stop_chain = build_bpstat_chain (aspace,
-					   ecs->event_thread->suspend.stop_pc,
+					   ecs->event_thread->stop_pc (),
 					   &ecs->ws);
 	  skip_inline_frames (ecs->event_thread, stop_chain);
 
@@ -6224,7 +6199,7 @@ handle_signal_stop (struct execution_control_state *ecs)
 	}
     }
 
-  if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
+  if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
       && ecs->event_thread->control.trap_expected
       && gdbarch_single_step_through_delay_p (gdbarch)
       && currently_stepping (ecs->event_thread))
@@ -6265,7 +6240,7 @@ handle_signal_stop (struct execution_control_state *ecs)
      handles this event.  */
   ecs->event_thread->control.stop_bpstat
     = bpstat_stop_status (get_current_regcache ()->aspace (),
-			  ecs->event_thread->suspend.stop_pc,
+			  ecs->event_thread->stop_pc (),
 			  ecs->event_thread, &ecs->ws, stop_chain);
 
   /* Following in case break condition called a
@@ -6280,7 +6255,7 @@ handle_signal_stop (struct execution_control_state *ecs)
      simply make sure to ignore it if `stopped_by_watchpoint' is
      set.  */
 
-  if (ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
+  if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
       && !bpstat_explains_signal (ecs->event_thread->control.stop_bpstat,
 				  GDB_SIGNAL_TRAP)
       && stopped_by_watchpoint)
@@ -6312,14 +6287,14 @@ handle_signal_stop (struct execution_control_state *ecs)
   /* See if the breakpoints module can explain the signal.  */
   random_signal
     = !bpstat_explains_signal (ecs->event_thread->control.stop_bpstat,
-			       ecs->event_thread->suspend.stop_signal);
+			       ecs->event_thread->stop_signal ());
 
   /* Maybe this was a trap for a software breakpoint that has since
      been removed.  */
   if (random_signal && target_stopped_by_sw_breakpoint ())
     {
       if (gdbarch_program_breakpoint_here_p (gdbarch,
-					     ecs->event_thread->suspend.stop_pc))
+					     ecs->event_thread->stop_pc ()))
 	{
 	  struct regcache *regcache;
 	  int decr_pc;
@@ -6338,7 +6313,7 @@ handle_signal_stop (struct execution_control_state *ecs)
 		  (record_full_gdb_operation_disable_set ());
 
 	      regcache_write_pc (regcache,
-				 ecs->event_thread->suspend.stop_pc + decr_pc);
+				 ecs->event_thread->stop_pc () + decr_pc);
 	    }
 	}
       else
@@ -6361,7 +6336,7 @@ handle_signal_stop (struct execution_control_state *ecs)
 
   /* If not, perhaps stepping/nexting can.  */
   if (random_signal)
-    random_signal = !(ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP
+    random_signal = !(ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
 		      && currently_stepping (ecs->event_thread));
 
   /* Perhaps the thread hit a single-step breakpoint of _another_
@@ -6388,7 +6363,7 @@ handle_signal_stop (struct execution_control_state *ecs)
   if (random_signal)
     {
       /* Signal not for debugging purposes.  */
-      enum gdb_signal stop_signal = ecs->event_thread->suspend.stop_signal;
+      enum gdb_signal stop_signal = ecs->event_thread->stop_signal ();
 
       infrun_debug_printf ("random signal (%s)",
 			   gdb_signal_to_symbol_string (stop_signal));
@@ -6400,7 +6375,7 @@ handle_signal_stop (struct execution_control_state *ecs)
 	 to remain stopped.  */
       if (stop_soon != NO_STOP_QUIETLY
 	  || ecs->event_thread->stop_requested
-	  || signal_stop_state (ecs->event_thread->suspend.stop_signal))
+	  || signal_stop_state (ecs->event_thread->stop_signal ()))
 	{
 	  stop_waiting (ecs);
 	  return;
@@ -6409,19 +6384,19 @@ handle_signal_stop (struct execution_control_state *ecs)
       /* Notify observers the signal has "handle print" set.  Note we
 	 returned early above if stopping; normal_stop handles the
 	 printing in that case.  */
-      if (signal_print[ecs->event_thread->suspend.stop_signal])
+      if (signal_print[ecs->event_thread->stop_signal ()])
 	{
 	  /* The signal table tells us to print about this signal.  */
 	  target_terminal::ours_for_output ();
-	  gdb::observers::signal_received.notify (ecs->event_thread->suspend.stop_signal);
+	  gdb::observers::signal_received.notify (ecs->event_thread->stop_signal ());
 	  target_terminal::inferior ();
 	}
 
       /* Clear the signal if it should not be passed.  */
-      if (signal_program[ecs->event_thread->suspend.stop_signal] == 0)
-	ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+      if (signal_program[ecs->event_thread->stop_signal ()] == 0)
+	ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 
-      if (ecs->event_thread->prev_pc == ecs->event_thread->suspend.stop_pc
+      if (ecs->event_thread->prev_pc == ecs->event_thread->stop_pc ()
 	  && ecs->event_thread->control.trap_expected
 	  && ecs->event_thread->control.step_resume_breakpoint == NULL)
 	{
@@ -6449,8 +6424,8 @@ handle_signal_stop (struct execution_control_state *ecs)
 	  return;
 	}
 
-      if (ecs->event_thread->suspend.stop_signal != GDB_SIGNAL_0
-	  && (pc_in_thread_step_range (ecs->event_thread->suspend.stop_pc,
+      if (ecs->event_thread->stop_signal () != GDB_SIGNAL_0
+	  && (pc_in_thread_step_range (ecs->event_thread->stop_pc (),
 				       ecs->event_thread)
 	      || ecs->event_thread->control.step_range_end == 1)
 	  && frame_id_eq (get_stack_frame_id (frame),
@@ -6668,7 +6643,7 @@ process_event_stop_test (struct execution_control_state *ecs)
 	  return;
 	}
       fill_in_stop_func (gdbarch, ecs);
-      if (ecs->event_thread->suspend.stop_pc == ecs->stop_func_start
+      if (ecs->event_thread->stop_pc () == ecs->stop_func_start
 	  && execution_direction == EXEC_REVERSE)
 	{
 	  /* We are stepping over a function call in reverse, and just
@@ -6792,7 +6767,7 @@ process_event_stop_test (struct execution_control_state *ecs)
      through a function epilogue and therefore must detect when
      the current-frame changes in the middle of a line.  */
 
-  if (pc_in_thread_step_range (ecs->event_thread->suspend.stop_pc,
+  if (pc_in_thread_step_range (ecs->event_thread->stop_pc (),
 			       ecs->event_thread)
       && (execution_direction != EXEC_REVERSE
 	  || frame_id_eq (get_frame_id (frame),
@@ -6811,7 +6786,7 @@ process_event_stop_test (struct execution_control_state *ecs)
       /* When stepping backward, stop at beginning of line range
 	 (unless it's the function entry point, in which case
 	 keep going back to the call point).  */
-      CORE_ADDR stop_pc = ecs->event_thread->suspend.stop_pc;
+      CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
       if (stop_pc == ecs->event_thread->control.step_range_start
 	  && stop_pc != ecs->stop_func_start
 	  && execution_direction == EXEC_REVERSE)
@@ -6838,11 +6813,10 @@ process_event_stop_test (struct execution_control_state *ecs)
 
   if (execution_direction != EXEC_REVERSE
       && ecs->event_thread->control.step_over_calls == STEP_OVER_UNDEBUGGABLE
-      && in_solib_dynsym_resolve_code (ecs->event_thread->suspend.stop_pc))
+      && in_solib_dynsym_resolve_code (ecs->event_thread->stop_pc ()))
     {
       CORE_ADDR pc_after_resolver =
-	gdbarch_skip_solib_resolver (gdbarch,
-				     ecs->event_thread->suspend.stop_pc);
+	gdbarch_skip_solib_resolver (gdbarch, ecs->event_thread->stop_pc ());
 
       infrun_debug_printf ("stepped into dynsym resolve code");
 
@@ -6865,7 +6839,7 @@ process_event_stop_test (struct execution_control_state *ecs)
   /* Step through an indirect branch thunk.  */
   if (ecs->event_thread->control.step_over_calls != STEP_OVER_NONE
       && gdbarch_in_indirect_branch_thunk (gdbarch,
-					   ecs->event_thread->suspend.stop_pc))
+					   ecs->event_thread->stop_pc ()))
     {
       infrun_debug_printf ("stepped into indirect branch thunk");
       keep_going (ecs);
@@ -6893,12 +6867,12 @@ process_event_stop_test (struct execution_control_state *ecs)
      call check below as on some targets return trampolines look
      like subroutine calls (MIPS16 return thunks).  */
   if (gdbarch_in_solib_return_trampoline (gdbarch,
-					  ecs->event_thread->suspend.stop_pc,
+					  ecs->event_thread->stop_pc (),
 					  ecs->stop_func_name)
       && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
     {
       /* Determine where this trampoline returns.  */
-      CORE_ADDR stop_pc = ecs->event_thread->suspend.stop_pc;
+      CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
       CORE_ADDR real_stop_pc
 	= gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
 
@@ -6950,9 +6924,9 @@ process_event_stop_test (struct execution_control_state *ecs)
 	  && (!frame_id_eq (ecs->event_thread->control.step_stack_frame_id,
 			    outer_frame_id)
 	      || (ecs->event_thread->control.step_start_function
-		  != find_pc_function (ecs->event_thread->suspend.stop_pc)))))
+		  != find_pc_function (ecs->event_thread->stop_pc ())))))
     {
-      CORE_ADDR stop_pc = ecs->event_thread->suspend.stop_pc;
+      CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
       CORE_ADDR real_stop_pc;
 
       infrun_debug_printf ("stepped into subroutine");
@@ -7110,7 +7084,7 @@ process_event_stop_test (struct execution_control_state *ecs)
   if (execution_direction == EXEC_REVERSE
       && ecs->event_thread->control.step_over_calls != STEP_OVER_NONE)
     {
-      CORE_ADDR stop_pc = ecs->event_thread->suspend.stop_pc;
+      CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
 
       if (gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc)
 	  || (ecs->stop_func_start == 0
@@ -7143,7 +7117,7 @@ process_event_stop_test (struct execution_control_state *ecs)
      stack of inlined frames, even if GDB actually believes that it is in a
      more outer frame.  This is checked for below by calls to
      inline_skipped_frames.  */
-  stop_pc_sal = find_pc_line (ecs->event_thread->suspend.stop_pc, 0);
+  stop_pc_sal = find_pc_line (ecs->event_thread->stop_pc (), 0);
 
   /* NOTE: tausq/2004-05-24: This if block used to be done before all
      the trampoline processing logic, however, there are some trampolines 
@@ -7270,7 +7244,7 @@ process_event_stop_test (struct execution_control_state *ecs)
     }
 
   bool refresh_step_info = true;
-  if ((ecs->event_thread->suspend.stop_pc == stop_pc_sal.pc)
+  if ((ecs->event_thread->stop_pc () == stop_pc_sal.pc)
       && (ecs->event_thread->current_line != stop_pc_sal.line
 	  || ecs->event_thread->current_symtab != stop_pc_sal.symtab))
     {
@@ -7365,7 +7339,7 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs)
       /* Check if the current thread is blocked on an incomplete
 	 step-over, interrupted by a random signal.  */
       if (ecs->event_thread->control.trap_expected
-	  && ecs->event_thread->suspend.stop_signal != GDB_SIGNAL_TRAP)
+	  && ecs->event_thread->stop_signal () != GDB_SIGNAL_TRAP)
 	{
 	  infrun_debug_printf
 	    ("need to finish step-over of [%s]",
@@ -7410,8 +7384,8 @@ switch_back_to_stepped_thread (struct execution_control_state *ecs)
       ecs->event_thread->control.trap_expected = 0;
 
       /* Likewise, clear the signal if it should not be passed.  */
-      if (!signal_program[ecs->event_thread->suspend.stop_signal])
-	ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+      if (!signal_program[ecs->event_thread->stop_signal ()])
+	ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
 
       if (restart_stepped_thread (ecs->target, ecs->ptid))
 	{
@@ -7444,7 +7418,7 @@ restart_stepped_thread (process_stratum_target *resume_target,
       if (tp->state == THREAD_EXITED)
 	continue;
 
-      if (tp->suspend.waitstatus_pending_p)
+      if (tp->has_pending_waitstatus ())
 	continue;
 
       /* Ignore threads of processes the caller is not
@@ -7468,7 +7442,7 @@ restart_stepped_thread (process_stratum_target *resume_target,
       if (tp->state == THREAD_EXITED)
 	continue;
 
-      if (tp->suspend.waitstatus_pending_p)
+      if (tp->has_pending_waitstatus ())
 	continue;
 
       /* Ignore threads of processes the caller is not
@@ -7519,7 +7493,7 @@ restart_after_all_stop_detach (process_stratum_target *proc_target)
 
       /* If we have a pending event to process, skip resuming the
 	 target and go straight to processing it.  */
-      if (thr->resumed () && thr->suspend.waitstatus_pending_p)
+      if (thr->resumed () && thr->has_pending_waitstatus ())
 	return;
     }
 
@@ -7586,7 +7560,7 @@ keep_going_stepped_thread (struct thread_info *tp)
   reset_ecs (ecs, tp);
   switch_to_thread (tp);
 
-  tp->suspend.stop_pc = regcache_read_pc (get_thread_regcache (tp));
+  tp->set_stop_pc (regcache_read_pc (get_thread_regcache (tp)));
   frame = get_current_frame ();
 
   /* If the PC of the thread we were trying to single-step has
@@ -7602,13 +7576,13 @@ keep_going_stepped_thread (struct thread_info *tp)
      This prevents us continuously moving the single-step breakpoint
      forward, one instruction at a time, overstepping.  */
 
-  if (tp->suspend.stop_pc != tp->prev_pc)
+  if (tp->stop_pc () != tp->prev_pc)
     {
       ptid_t resume_ptid;
 
       infrun_debug_printf ("expected thread advanced also (%s -> %s)",
 			   paddress (target_gdbarch (), tp->prev_pc),
-			   paddress (target_gdbarch (), tp->suspend.stop_pc));
+			   paddress (target_gdbarch (), tp->stop_pc ()));
 
       /* Clear the info of the previous step-over, as it's no longer
 	 valid (if the thread was trying to step over a breakpoint, it
@@ -7622,7 +7596,7 @@ keep_going_stepped_thread (struct thread_info *tp)
 
       insert_single_step_breakpoint (get_frame_arch (frame),
 				     get_frame_address_space (frame),
-				     tp->suspend.stop_pc);
+				     tp->stop_pc ());
 
       tp->set_resumed (true);
       resume_ptid = internal_resume_ptid (tp->control.stepping_command);
@@ -7663,7 +7637,7 @@ handle_step_into_function (struct gdbarch *gdbarch,
   fill_in_stop_func (gdbarch, ecs);
 
   compunit_symtab *cust
-    = find_pc_compunit_symtab (ecs->event_thread->suspend.stop_pc);
+    = find_pc_compunit_symtab (ecs->event_thread->stop_pc ());
   if (cust != NULL && compunit_language (cust) != language_asm)
     ecs->stop_func_start
       = gdbarch_skip_prologue_noexcept (gdbarch, ecs->stop_func_start);
@@ -7703,7 +7677,7 @@ handle_step_into_function (struct gdbarch *gdbarch,
 					     ecs->stop_func_start);
     }
 
-  if (ecs->stop_func_start == ecs->event_thread->suspend.stop_pc)
+  if (ecs->stop_func_start == ecs->event_thread->stop_pc ())
     {
       /* We are already there: stop now.  */
       end_stepping_range (ecs);
@@ -7742,15 +7716,15 @@ handle_step_into_function_backward (struct gdbarch *gdbarch,
 
   fill_in_stop_func (gdbarch, ecs);
 
-  cust = find_pc_compunit_symtab (ecs->event_thread->suspend.stop_pc);
+  cust = find_pc_compunit_symtab (ecs->event_thread->stop_pc ());
   if (cust != NULL && compunit_language (cust) != language_asm)
     ecs->stop_func_start
       = gdbarch_skip_prologue_noexcept (gdbarch, ecs->stop_func_start);
 
-  stop_func_sal = find_pc_line (ecs->event_thread->suspend.stop_pc, 0);
+  stop_func_sal = find_pc_line (ecs->event_thread->stop_pc (), 0);
 
   /* OK, we're just going to keep stepping here.  */
-  if (stop_func_sal.pc == ecs->event_thread->suspend.stop_pc)
+  if (stop_func_sal.pc == ecs->event_thread->stop_pc ())
     {
       /* We're there already.  Just stop stepping now.  */
       end_stepping_range (ecs);
@@ -8057,7 +8031,7 @@ keep_going_pass_signal (struct execution_control_state *ecs)
 	 non-signal event (e.g., a fork); or took a signal which we
 	 are supposed to pass through to the inferior.  Simply
 	 continue.  */
-      resume (ecs->event_thread->suspend.stop_signal);
+      resume (ecs->event_thread->stop_signal ());
     }
   else if (step_over_info_valid_p ())
     {
@@ -8148,7 +8122,7 @@ keep_going_pass_signal (struct execution_control_state *ecs)
 
       ecs->event_thread->control.trap_expected = (remove_bp || remove_wps);
 
-      resume (ecs->event_thread->suspend.stop_signal);
+      resume (ecs->event_thread->stop_signal ());
     }
 
   prepare_to_wait (ecs);
@@ -8162,11 +8136,11 @@ static void
 keep_going (struct execution_control_state *ecs)
 {
   if (ecs->event_thread->control.trap_expected
-      && ecs->event_thread->suspend.stop_signal == GDB_SIGNAL_TRAP)
+      && ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP)
     ecs->event_thread->control.trap_expected = 0;
 
-  if (!signal_program[ecs->event_thread->suspend.stop_signal])
-    ecs->event_thread->suspend.stop_signal = GDB_SIGNAL_0;
+  if (!signal_program[ecs->event_thread->stop_signal ()])
+    ecs->event_thread->set_stop_signal (GDB_SIGNAL_0);
   keep_going_pass_signal (ecs);
 }
 
@@ -8352,7 +8326,7 @@ print_stop_location (struct target_waitstatus *ws)
 	  && frame_id_eq (tp->control.step_frame_id,
 			  get_frame_id (get_current_frame ()))
 	  && (tp->control.step_start_function
-	      == find_pc_function (tp->suspend.stop_pc)))
+	      == find_pc_function (tp->stop_pc ())))
 	{
 	  /* Finished step, just print source line.  */
 	  source_flag = SRC_LINE;
@@ -8548,7 +8522,7 @@ normal_stop (void)
   update_thread_list ();
 
   if (last.kind == TARGET_WAITKIND_STOPPED && stopped_by_random_signal)
-    gdb::observers::signal_received.notify (inferior_thread ()->suspend.stop_signal);
+    gdb::observers::signal_received.notify (inferior_thread ()->stop_signal ());
 
   /* As with the notification of thread events, we want to delay
      notifying the user that we've switched thread context until
@@ -9137,9 +9111,10 @@ class infcall_suspend_state
   infcall_suspend_state (struct gdbarch *gdbarch,
 			 const struct thread_info *tp,
 			 struct regcache *regcache)
-    : m_thread_suspend (tp->suspend),
-      m_registers (new readonly_detached_regcache (*regcache))
+    : m_registers (new readonly_detached_regcache (*regcache))
   {
+    tp->save_suspend_to (m_thread_suspend);
+
     gdb::unique_xmalloc_ptr<gdb_byte> siginfo_data;
 
     if (gdbarch_get_siginfo_type_p (gdbarch))
@@ -9178,7 +9153,7 @@ class infcall_suspend_state
 		struct thread_info *tp,
 		struct regcache *regcache) const
   {
-    tp->suspend = m_thread_suspend;
+    tp->restore_suspend_from (m_thread_suspend);
 
     if (m_siginfo_gdbarch == gdbarch)
       {
@@ -9228,7 +9203,7 @@ save_infcall_suspend_state ()
      any stop signal information.  The stop signal is not useful when
      starting an inferior function call, and run_inferior_call will not use
      the signal due to its `proceed' call with GDB_SIGNAL_0.  */
-  tp->suspend.stop_signal = GDB_SIGNAL_0;
+  tp->set_stop_signal (GDB_SIGNAL_0);
 
   return inf_state;
 }
diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c
index b162466f810a..1559ad9fd717 100644
--- a/gdb/linux-fork.c
+++ b/gdb/linux-fork.c
@@ -224,8 +224,7 @@ fork_load_infrun_state (struct fork_info *fp)
   registers_changed ();
   reinit_frame_cache ();
 
-  inferior_thread ()->suspend.stop_pc
-    = regcache_read_pc (get_current_regcache ());
+  inferior_thread ()->set_stop_pc (regcache_read_pc (get_current_regcache ()));
   nullify_last_target_wait_ptid ();
 
   /* Now restore the file positions of open file descriptors.  */
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 34a2aee41d78..a7aa5c09e288 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1299,10 +1299,10 @@ get_detach_signal (struct lwp_info *lp)
 
       if (target_is_non_stop_p () && !tp->executing)
 	{
-	  if (tp->suspend.waitstatus_pending_p)
-	    signo = tp->suspend.waitstatus.value.sig;
+	  if (tp->has_pending_waitstatus ())
+	    signo = tp->pending_waitstatus ().value.sig;
 	  else
-	    signo = tp->suspend.stop_signal;
+	    signo = tp->stop_signal ();
 	}
       else if (!target_is_non_stop_p ())
 	{
@@ -1313,7 +1313,7 @@ get_detach_signal (struct lwp_info *lp)
 
 	  if (last_target == linux_target
 	      && lp->ptid.lwp () == last_ptid.lwp ())
-	    signo = tp->suspend.stop_signal;
+	    signo = tp->stop_signal ();
 	}
     }
 
@@ -1627,8 +1627,8 @@ linux_nat_resume_callback (struct lwp_info *lp, struct lwp_info *except)
       thread = find_thread_ptid (linux_target, lp->ptid);
       if (thread != NULL)
 	{
-	  signo = thread->suspend.stop_signal;
-	  thread->suspend.stop_signal = GDB_SIGNAL_0;
+	  signo = thread->stop_signal ();
+	  thread->set_stop_signal (GDB_SIGNAL_0);
 	}
     }
 
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1e..8c0885ed5b49 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -2017,7 +2017,7 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
   thread_info *signalled_thr = gcore_find_signalled_thread ();
   gdb_signal stop_signal;
   if (signalled_thr != nullptr)
-    stop_signal = signalled_thr->suspend.stop_signal;
+    stop_signal = signalled_thr->stop_signal ();
   else
     stop_signal = GDB_SIGNAL_0;
 
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 39efa804d801..cfbc2f6574f1 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -96,7 +96,7 @@ python_on_normal_stop (struct bpstats *bs, int print_frame)
   if (inferior_ptid == null_ptid)
     return;
 
-  stop_signal = inferior_thread ()->suspend.stop_signal;
+  stop_signal = inferior_thread ()->stop_signal ();
 
   gdbpy_enter enter_py (get_current_arch (), current_language);
 
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 00affb85d22e..58c46d848b25 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -2793,8 +2793,7 @@ record_btrace_set_replay (struct thread_info *tp,
   /* Start anew from the new replay position.  */
   record_btrace_clear_histories (btinfo);
 
-  inferior_thread ()->suspend.stop_pc
-    = regcache_read_pc (get_current_regcache ());
+  inferior_thread ()->set_stop_pc (regcache_read_pc (get_current_regcache ()));
   print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
 }
 
diff --git a/gdb/record-full.c b/gdb/record-full.c
index 9e157c6e0489..f5c4244f0edd 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -1993,8 +1993,7 @@ record_full_goto_entry (struct record_full_entry *p)
 
   registers_changed ();
   reinit_frame_cache ();
-  inferior_thread ()->suspend.stop_pc
-    = regcache_read_pc (get_current_regcache ());
+  inferior_thread ()->set_stop_pc (regcache_read_pc (get_current_regcache ()));
   print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
 }
 
diff --git a/gdb/remote.c b/gdb/remote.c
index f08616be9b67..d3503a96e097 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -766,6 +766,7 @@ class remote_target : public process_stratum_target
 
   void remote_notice_new_inferior (ptid_t currthread, bool executing);
 
+  void print_one_stopped_thread (thread_info *thread);
   void process_initial_stop_replies (int from_tty);
 
   thread_info *remote_add_thread (ptid_t ptid, bool running, bool executing);
@@ -4479,20 +4480,36 @@ remote_target::add_current_inferior_and_thread (const char *wait_status)
 /* Print info about a thread that was found already stopped on
    connection.  */
 
-static void
-print_one_stopped_thread (struct thread_info *thread)
+void
+remote_target::print_one_stopped_thread (thread_info *thread)
 {
-  struct target_waitstatus *ws = &thread->suspend.waitstatus;
+  target_waitstatus ws;
+
+  /* If there is a pending waitstatus, use it.  If there isn't it's because
+     the thread's stop was reported with TARGET_WAITKIND_STOPPED / GDB_SIGNAL_0
+     and process_initial_stop_replies decided it wasn't interesting to save
+     and report to the core.  */
+  if (thread->has_pending_waitstatus ())
+    {
+      ws = thread->pending_waitstatus ();
+      thread->clear_pending_waitstatus ();
+    }
+  else
+    {
+      ws.kind = TARGET_WAITKIND_STOPPED;
+      ws.value.sig = GDB_SIGNAL_0;
+    }
 
   switch_to_thread (thread);
-  thread->suspend.stop_pc = get_frame_pc (get_current_frame ());
+  thread->set_stop_pc (get_frame_pc (get_current_frame ()));
   set_current_sal_from_frame (get_current_frame ());
 
-  thread->suspend.waitstatus_pending_p = 0;
+  /* For "info program".  */
+  set_last_target_status (this, thread->ptid, ws);
 
-  if (ws->kind == TARGET_WAITKIND_STOPPED)
+  if (ws.kind == TARGET_WAITKIND_STOPPED)
     {
-      enum gdb_signal sig = ws->value.sig;
+      enum gdb_signal sig = ws.value.sig;
 
       if (signal_print_state (sig))
 	gdb::observers::signal_received.notify (sig);
@@ -4513,6 +4530,9 @@ remote_target::process_initial_stop_replies (int from_tty)
   struct thread_info *lowest_stopped = NULL;
   struct thread_info *first = NULL;
 
+  /* This is only used when the target is non-stop.  */
+  gdb_assert (target_is_non_stop_p ());
+
   /* Consume the initial pending events.  */
   while (pending_stop_replies-- > 0)
     {
@@ -4557,15 +4577,13 @@ remote_target::process_initial_stop_replies (int from_tty)
 	     instead of signal 0.  Suppress it.  */
 	  if (sig == GDB_SIGNAL_TRAP)
 	    sig = GDB_SIGNAL_0;
-	  evthread->suspend.stop_signal = sig;
+	  evthread->set_stop_signal (sig);
 	  ws.value.sig = sig;
 	}
 
-      evthread->suspend.waitstatus = ws;
-
       if (ws.kind != TARGET_WAITKIND_STOPPED
 	  || ws.value.sig != GDB_SIGNAL_0)
-	evthread->suspend.waitstatus_pending_p = 1;
+	evthread->set_pending_waitstatus (ws);
 
       set_executing (this, event_ptid, false);
       set_running (this, event_ptid, false);
@@ -4619,8 +4637,7 @@ remote_target::process_initial_stop_replies (int from_tty)
       else if (thread->state != THREAD_STOPPED)
 	continue;
 
-      if (selected == NULL
-	  && thread->suspend.waitstatus_pending_p)
+      if (selected == nullptr && thread->has_pending_waitstatus ())
 	selected = thread;
 
       if (lowest_stopped == NULL
@@ -4644,11 +4661,6 @@ remote_target::process_initial_stop_replies (int from_tty)
 
       print_one_stopped_thread (thread);
     }
-
-  /* For "info program".  */
-  thread_info *thread = inferior_thread ();
-  if (thread->state == THREAD_STOPPED)
-    set_last_target_status (this, inferior_ptid, thread->suspend.waitstatus);
 }
 
 /* Start the remote connection and sync state.  */
@@ -6260,11 +6272,11 @@ remote_target::append_pending_thread_resumptions (char *p, char *endp,
 {
   for (thread_info *thread : all_non_exited_threads (this, ptid))
     if (inferior_ptid != thread->ptid
-	&& thread->suspend.stop_signal != GDB_SIGNAL_0)
+	&& thread->stop_signal () != GDB_SIGNAL_0)
       {
 	p = append_resumption (p, endp, thread->ptid,
-			       0, thread->suspend.stop_signal);
-	thread->suspend.stop_signal = GDB_SIGNAL_0;
+			       0, thread->stop_signal ());
+	thread->set_stop_signal (GDB_SIGNAL_0);
 	resume_clear_thread_private_info (thread);
       }
 
@@ -7202,7 +7214,7 @@ struct notif_client notif_client_stop =
    -1 if we want to check all threads.  */
 
 static int
-is_pending_fork_parent (struct target_waitstatus *ws, int event_pid,
+is_pending_fork_parent (const target_waitstatus *ws, int event_pid,
 			ptid_t thread_ptid)
 {
   if (ws->kind == TARGET_WAITKIND_FORKED
@@ -7218,11 +7230,11 @@ is_pending_fork_parent (struct target_waitstatus *ws, int event_pid,
 /* Return the thread's pending status used to determine whether the
    thread is a fork parent stopped at a fork event.  */
 
-static struct target_waitstatus *
+static const target_waitstatus *
 thread_pending_fork_status (struct thread_info *thread)
 {
-  if (thread->suspend.waitstatus_pending_p)
-    return &thread->suspend.waitstatus;
+  if (thread->has_pending_waitstatus ())
+    return &thread->pending_waitstatus ();
   else
     return &thread->pending_follow;
 }
@@ -7232,7 +7244,7 @@ thread_pending_fork_status (struct thread_info *thread)
 static int
 is_pending_fork_parent_thread (struct thread_info *thread)
 {
-  struct target_waitstatus *ws = thread_pending_fork_status (thread);
+  const target_waitstatus *ws = thread_pending_fork_status (thread);
   int pid = -1;
 
   return is_pending_fork_parent (ws, pid, thread->ptid);
@@ -7254,7 +7266,7 @@ remote_target::remove_new_fork_children (threads_listing_context *context)
      fork child threads from the CONTEXT list.  */
   for (thread_info *thread : all_non_exited_threads (this))
     {
-      struct target_waitstatus *ws = thread_pending_fork_status (thread);
+      const target_waitstatus *ws = thread_pending_fork_status (thread);
 
       if (is_pending_fork_parent (ws, pid, thread->ptid))
 	context->remove_thread (ws->value.related_pid);
diff --git a/gdb/thread.c b/gdb/thread.c
index c6c63b742db4..289d33c74c3b 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -275,7 +275,7 @@ thread_info::thread_info (struct inferior *inf_, ptid_t ptid_)
   /* Nothing to follow yet.  */
   memset (&this->pending_follow, 0, sizeof (this->pending_follow));
   this->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
-  this->suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
+  this->m_suspend.waitstatus.kind = TARGET_WAITKIND_IGNORE;
 }
 
 thread_info::~thread_info ()
@@ -295,6 +295,27 @@ thread_info::deletable () const
 
 /* See gdbthread.h.  */
 
+void
+thread_info::set_pending_waitstatus (const target_waitstatus &ws)
+{
+  gdb_assert (!this->has_pending_waitstatus ());
+
+  m_suspend.waitstatus = ws;
+  m_suspend.waitstatus_pending_p = 1;
+}
+
+/* See gdbthread.h.  */
+
+void
+thread_info::clear_pending_waitstatus ()
+{
+  gdb_assert (this->has_pending_waitstatus ());
+
+  m_suspend.waitstatus_pending_p = 0;
+}
+
+/* See gdbthread.h.  */
+
 int
 thread_is_in_step_over_chain (struct thread_info *tp)
 {
@@ -771,7 +792,7 @@ set_executing_thread (thread_info *thr, bool executing)
 {
   thr->executing = executing;
   if (executing)
-    thr->suspend.stop_pc = ~(CORE_ADDR) 0;
+    thr->set_stop_pc (~(CORE_ADDR) 0);
 }
 
 void
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (5 preceding siblings ...)
  2021-06-22 16:56 ` [PATCH 06/11] gdb: make thread_info::suspend private, add getters / setters Simon Marchi
@ 2021-06-22 16:57 ` Simon Marchi
  2021-07-05 15:51   ` Pedro Alves
  2021-06-22 16:57 ` [PATCH 08/11] gdb: optimize check for resumed threads with pending wait status in maybe_set_commit_resumed_all_targets Simon Marchi
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:57 UTC (permalink / raw)
  To: gdb-patches

Looking up threads that are both resumed and have a pending wait
status to report is something that we do quite often in the fast path
and is expensive if there are many threads, since it currently requires
walking whole thread lists.

The first instance is in maybe_set_commit_resumed_all_targets.  This is
called after handling each event in fetch_inferior_event, to see if we
should ask targets to commit their resumed threads or not.  If at least
one thread is resumed but has a pending wait status, we don't ask the
targets to commit their resumed threads, because we want to consume and
handle the pending wait status first.

The second instance is in random_pending_event_thread, where we want to
select a random thread among all those that are resumed and have a
pending wait status.  This is called every time we try to consume
events, to see if there are any pending events that we we want to
consume, before asking the targets for more events.

To allow optimizing these cases, maintain a per-process-target list of
threads that are resumed and have a pending wait status.

In maybe_set_commit_resumed_all_targets, we'll be able to check in O(1)
if there are any such threads simply by checking whether the list is
empty.

In random_pending_event_thread, we'll be able to use that list, which
will be quicker than iterating the list of threads, especially when
there are no resumed with pending wait status threads.

About implementation details: using the new setters on class
thread_info, it's relatively easy to maintain that list.  Any time the
"resumed" or "pending wait status" property is changed, we check whether
that should cause the thread to be added or removed from the list.

In set_thread_exited, we try to remove the thread from the list, because
keeping an exited thread in that list would make no sense (especially if
the thread is freed).  My first implementation assumed that a process
stratum target was always present when set_thread_exited is called.
That's however, not the case: in some cases, targets unpush themselves
from an inferior and then call "exit_inferior", which exits all the
threads.  If the target is unpushed before set_thread_exited is called
on the threads, it means we could mistakenly leave some threads in the
list.  I tried to see how hard it would be to make it such that targets
have to exit all threads before unpushing themselves from the inferior
(that would seem logical to me, we don't want threads belonging to an
inferior that has no process target).  That seem quite difficult and not
worth the time.  Instead, I changed inferior::unpush_target to remove an
threads of that inferior from the list.

As of this patch, the list is not used, this is done in the subsequent
patches.

gdb/ChangeLog:

	* process-stratum-target.h (class process_stratum_target)
	<maybe_add_resumed_with_pending_wait_status,
	maybe_remove_resumed_with_pending_wait_status>: New.
	<m_resumed_with_pending_wait_status>: New.
	* process-stratum-target.c
	(process_stratum_target::maybe_add_resumed_with_pending_wait_status,
	process_stratum_target::maybe_remove_resumed_with_pending_wait_status):
	New.
	* gdbthread.h (class thread_info) <set_resumed>: Remove
	definition.
	<resumed_with_pending_wait_status_node>: New.
	(thread_info_resumed_with_pending_wait_status_node,
	thread_info_resumed_with_pending_wait_status_list): New.
	(set_thread_exited): Call
	maybe_remove_resumed_with_pending_wait_status.
	(thread_info::set_resumed): New definition.
	(thread_info::set_pending_waitstatus): Call
	maybe_add_resumed_with_pending_wait_status.
	(thread_info::clear_pending_waitstatus): Call
	maybe_remove_resumed_with_pending_wait_status.
	* inferior.h (class inferior) <unpush_target>: Remove
	definition.
	* inferior.c (inferior::unpush_target): New definition.

Change-Id: Iad8f93db2d13984dd5aa5867db940ed1169dbb67
---
 gdb/gdbthread.h              | 14 ++++++++++++--
 gdb/inferior.c               | 19 +++++++++++++++++++
 gdb/inferior.h               |  3 +--
 gdb/process-stratum-target.c | 34 ++++++++++++++++++++++++++++++++++
 gdb/process-stratum-target.h | 16 ++++++++++++++++
 gdb/thread.c                 | 33 +++++++++++++++++++++++++++++++++
 6 files changed, 115 insertions(+), 4 deletions(-)

diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 5ea08a13ee5f..47d7f40eaa08 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -296,8 +296,7 @@ class thread_info : public refcounted_object,
   bool resumed () const
   { return m_resumed; }
 
-  void set_resumed (bool resumed)
-  { m_resumed = resumed; }
+  void set_resumed (bool resumed);
 
   /* Frontend view of the thread state.  Note that the THREAD_RUNNING/
      THREAD_STOPPED states are different from EXECUTING.  When the
@@ -470,6 +469,10 @@ class thread_info : public refcounted_object,
      linked.  */
   intrusive_list_node<thread_info> step_over_list_node;
 
+  /* Node for list of threads that are resumed and have a pending wait
+     status.  */
+  intrusive_list_node<thread_info> resumed_with_pending_wait_status_node;
+
   /* Displaced-step state for this thread.  */
   displaced_step_thread_state displaced_step_state;
 
@@ -488,6 +491,13 @@ class thread_info : public refcounted_object,
   thread_suspend_state m_suspend;
 };
 
+using thread_info_resumed_with_pending_wait_status_node
+  = intrusive_member_node<thread_info,
+			  &thread_info::resumed_with_pending_wait_status_node>;
+using thread_info_resumed_with_pending_wait_status_list
+  = intrusive_list<thread_info,
+		   thread_info_resumed_with_pending_wait_status_node>;
+
 /* A gdb::ref_ptr pointer to a thread_info.  */
 
 using thread_info_ref
diff --git a/gdb/inferior.c b/gdb/inferior.c
index f1b0bdde554b..e07a8f88422a 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -89,6 +89,25 @@ inferior::inferior (int pid_)
   m_target_stack.push (get_dummy_target ());
 }
 
+/* See inferior.h.  */
+
+int
+inferior::unpush_target (struct target_ops *t)
+{
+  /* If unpushing the process stratum target while threads exists, ensure that
+     we don't leave any threads of this inferior in the target's "resumed with
+     pending wait status" list.  */
+  if (t->stratum () == process_stratum)
+    {
+      process_stratum_target *proc_target = as_process_stratum_target (t);
+
+      for (thread_info *thread : this->non_exited_threads ())
+	proc_target->maybe_remove_resumed_with_pending_wait_status (thread);
+    }
+
+  return m_target_stack.unpush (t);
+}
+
 void
 inferior::set_tty (const char *terminal_name)
 {
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 830dec3ebbaa..2bfe29afed3f 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -362,8 +362,7 @@ class inferior : public refcounted_object,
   }
 
   /* Unpush T from this inferior's target stack.  */
-  int unpush_target (struct target_ops *t)
-  { return m_target_stack.unpush (t); }
+  int unpush_target (struct target_ops *t);
 
   /* Returns true if T is pushed in this inferior's target stack.  */
   bool target_is_pushed (target_ops *t)
diff --git a/gdb/process-stratum-target.c b/gdb/process-stratum-target.c
index c851090a7f2e..44e138273b2f 100644
--- a/gdb/process-stratum-target.c
+++ b/gdb/process-stratum-target.c
@@ -106,6 +106,40 @@ process_stratum_target::follow_exec (inferior *follow_inf, ptid_t ptid,
 
 /* See process-stratum-target.h.  */
 
+void
+process_stratum_target::maybe_add_resumed_with_pending_wait_status
+  (thread_info *thread)
+{
+  gdb_assert (!thread->resumed_with_pending_wait_status_node.is_linked ());
+
+  if (thread->resumed () && thread->has_pending_waitstatus ())
+    {
+      infrun_debug_printf ("adding to resumed threads with event list: %s",
+			   target_pid_to_str (thread->ptid).c_str ());
+      m_resumed_with_pending_wait_status.push_back (*thread);
+    }
+}
+
+/* See process-stratum-target.h.  */
+
+void
+process_stratum_target::maybe_remove_resumed_with_pending_wait_status
+  (thread_info *thread)
+{
+  if (thread->resumed () && thread->has_pending_waitstatus ())
+    {
+      infrun_debug_printf ("removing from resumed threads with event list: %s",
+			   target_pid_to_str (thread->ptid).c_str ());
+      gdb_assert (thread->resumed_with_pending_wait_status_node.is_linked ());
+      auto it = m_resumed_with_pending_wait_status.iterator_to (*thread);
+      m_resumed_with_pending_wait_status.erase (it);
+    }
+  else
+    gdb_assert (!thread->resumed_with_pending_wait_status_node.is_linked ());
+}
+
+/* See process-stratum-target.h.  */
+
 std::set<process_stratum_target *>
 all_non_exited_process_targets ()
 {
diff --git a/gdb/process-stratum-target.h b/gdb/process-stratum-target.h
index 31a97753db9c..d79efbee8f2c 100644
--- a/gdb/process-stratum-target.h
+++ b/gdb/process-stratum-target.h
@@ -22,6 +22,8 @@
 
 #include "target.h"
 #include <set>
+#include "gdbsupport/intrusive_list.h"
+#include "gdbthread.h"
 
 /* Abstract base class inherited by all process_stratum targets.  */
 
@@ -78,6 +80,14 @@ class process_stratum_target : public target_ops
      may have spawned new threads we haven't heard of yet.  */
   bool threads_executing = false;
 
+  /* If THREAD is resumed and has a pending wait status, add it to the
+     target's "resumed with pending wait status" list.  */
+  void maybe_add_resumed_with_pending_wait_status (thread_info *thread);
+
+  /* If THREAD is resumed and has a pending wait status, remove it from the
+     target's "resumed with pending wait status" list.  */
+  void maybe_remove_resumed_with_pending_wait_status (thread_info *thread);
+
   /* The connection number.  Visible in "info connections".  */
   int connection_number = 0;
 
@@ -112,6 +122,12 @@ class process_stratum_target : public target_ops
      coalesce multiple resumption requests in a single vCont
      packet.  */
   bool commit_resumed_state = false;
+
+private:
+  /* List of threads managed by this target which simultaneously are resumed
+     and have a pending wait status.  */
+  thread_info_resumed_with_pending_wait_status_list
+    m_resumed_with_pending_wait_status;
 };
 
 /* Downcast TARGET to process_stratum_target.  */
diff --git a/gdb/thread.c b/gdb/thread.c
index 289d33c74c3b..26974e1b8cbc 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -188,6 +188,10 @@ set_thread_exited (thread_info *tp, bool silent)
 
   if (tp->state != THREAD_EXITED)
     {
+      process_stratum_target *proc_target = tp->inf->process_target ();
+      if (proc_target != nullptr)
+	proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
+
       gdb::observers::thread_exit.notify (tp, silent);
 
       /* Tag it as exited.  */
@@ -295,6 +299,29 @@ thread_info::deletable () const
 
 /* See gdbthread.h.  */
 
+void
+thread_info::set_resumed (bool resumed)
+{
+  if (resumed == m_resumed)
+    return;
+
+  process_stratum_target *proc_target = this->inf->process_target ();
+
+  /* If we transition from resumed to not resumed, we might need to remove
+     the thread from the resumed threads with pending statuses list.  */
+  if (!resumed)
+    proc_target->maybe_remove_resumed_with_pending_wait_status (this);
+
+  m_resumed = resumed;
+
+  /* If we transition from not resumed to resumed, we might need to add
+     the thread to the resumed threads with pending statuses list.  */
+  if (resumed)
+    proc_target->maybe_add_resumed_with_pending_wait_status (this);
+}
+
+/* See gdbthread.h.  */
+
 void
 thread_info::set_pending_waitstatus (const target_waitstatus &ws)
 {
@@ -302,6 +329,9 @@ thread_info::set_pending_waitstatus (const target_waitstatus &ws)
 
   m_suspend.waitstatus = ws;
   m_suspend.waitstatus_pending_p = 1;
+
+  process_stratum_target *proc_target = this->inf->process_target ();
+  proc_target->maybe_add_resumed_with_pending_wait_status (this);
 }
 
 /* See gdbthread.h.  */
@@ -311,6 +341,9 @@ thread_info::clear_pending_waitstatus ()
 {
   gdb_assert (this->has_pending_waitstatus ());
 
+  process_stratum_target *proc_target = this->inf->process_target ();
+  proc_target->maybe_remove_resumed_with_pending_wait_status (this);
+
   m_suspend.waitstatus_pending_p = 0;
 }
 
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 08/11] gdb: optimize check for resumed threads with pending wait status in maybe_set_commit_resumed_all_targets
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (6 preceding siblings ...)
  2021-06-22 16:57 ` [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status Simon Marchi
@ 2021-06-22 16:57 ` Simon Marchi
  2021-07-05 15:51   ` Pedro Alves
  2021-06-22 16:57 ` [PATCH 09/11] gdb: optimize selection of resumed thread with pending event Simon Marchi
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:57 UTC (permalink / raw)
  To: gdb-patches

Consider a test case where many threads (thousands) keep hitting a
breakpoint whose condition evaluates to false.
maybe_set_commit_resumed_all_targets is called at each handled event,
when the scoped_disable_commit_resumed object in fetch_inferior_event is
reset_and_commit-ed.  One particularly expensive check in there is
whether the target has at least one resumed thread with a pending wait
status (in which case, we don't want to commit the resumed threads, as
we want to consume this status first).  It is currently implemented as
walking all threads of the target.

Since we now maintain a per-target list of resumed threads with pending
status, we can do this check efficiently, by checking whether that list
is empty or not.

Add the process_stratum_target::has_resumed_with_pending_wait_status
method for this, and use it in maybe_set_commit_resumed_all_targets.

gdb/ChangeLog:

	  * process-stratum-target.h (class process_stratum_target)
	  <has_resumed_with_pending_wait_status>: New.
	  * infrun.c (maybe_set_commit_resumed_all_targets): Use
	  has_resumed_with_pending_wait_status.

Change-Id: Ia1595baa1b358338f94fc3cb3af7f27092dad5b6
---
 gdb/infrun.c                 | 10 +---------
 gdb/process-stratum-target.h |  5 +++++
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index c973b34fa1d6..657f97e23fbb 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2801,15 +2801,7 @@ maybe_set_commit_resumed_all_targets ()
 	 status to report, handle it before requiring the target to
 	 commit its resumed threads: handling the status might lead to
 	 resuming more threads.  */
-      bool has_thread_with_pending_status = false;
-      for (thread_info *thread : all_non_exited_threads (proc_target))
-	if (thread->resumed () && thread->has_pending_waitstatus ())
-	  {
-	    has_thread_with_pending_status = true;
-	    break;
-	  }
-
-      if (has_thread_with_pending_status)
+      if (proc_target->has_resumed_with_pending_wait_status ())
 	{
 	  infrun_debug_printf ("not requesting commit-resumed for target %s, a"
 			       " thread has a pending waitstatus",
diff --git a/gdb/process-stratum-target.h b/gdb/process-stratum-target.h
index d79efbee8f2c..c73207e0531e 100644
--- a/gdb/process-stratum-target.h
+++ b/gdb/process-stratum-target.h
@@ -88,6 +88,11 @@ class process_stratum_target : public target_ops
      target's "resumed with pending wait status" list.  */
   void maybe_remove_resumed_with_pending_wait_status (thread_info *thread);
 
+  /* Return true if this target has at least one resumed thread with a pending
+     wait status.  */
+  bool has_resumed_with_pending_wait_status () const
+  { return !m_resumed_with_pending_wait_status.empty (); }
+
   /* The connection number.  Visible in "info connections".  */
   int connection_number = 0;
 
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 09/11] gdb: optimize selection of resumed thread with pending event
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (7 preceding siblings ...)
  2021-06-22 16:57 ` [PATCH 08/11] gdb: optimize check for resumed threads with pending wait status in maybe_set_commit_resumed_all_targets Simon Marchi
@ 2021-06-22 16:57 ` Simon Marchi
  2021-07-05 15:51   ` Pedro Alves
  2021-06-22 16:57 ` [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid Simon Marchi
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:57 UTC (permalink / raw)
  To: gdb-patches

Consider a case where many threads (thousands) keep hitting a breakpoint
whose condition evaluates to false.  random_pending_event_thread is
responsible for selecting a thread from an inferior among all that are
resumed with a pending wait status.  It is currently implemented by
walking the inferior's thread list twice: once to count the number of
candidates and once to select a random one.

Since we now maintain a per target list of resumed threads with pending
event, we can implement this more efficiently by walking that list and
selecting the first thread that matches the criteria
(random_pending_event_thread looks for an thread from a specific
inferior, and possibly a filter ptid).  It will be faster especially in
the common case where there isn't any resumed thread with pending
event.  Currently, we have to iterate the thread list to figure this
out.  With this patch, the list of resumed threads with pending event
will be empty, so it's quick to figure out.

The random selection is kept, but is moved to
process_stratum_target::random_resumed_with_pending_wait_status.  The
same technique is used: do a first pass to count the number of
candidates, and do a second pass to select a random one.  But given that
the list of resumed threads with pending wait statuses will generally be
short, or at least shorter than the full thread list, it should be
quicker.

Note that this isn't completely true, in case there are multiple
inferiors on the same target.  Imagine that inferior A has 10k resumed
threads with pending wait statuses, and random_pending_event_thread is
called with inferior B.  We'll need to go through the list that contains
inferior A's threads to realize that inferior B has no resumed threads
with pending wait status.  But I think that this is a corner /
pathological case.  And a possible fix for this situation would be to
make random_pending_event_thread work per-process-target, rather than
per-inferior.

gdb/ChangeLog:

	* process-stratum-target.h (class process_stratum_target)
	<random_resumed_with_pending_wait_status>: New.
	* process-stratum-target.c
	(process_stratum_target::random_resumed_with_pending_wait_status):
	New.
	* infrun.c (random_pending_event_thread): Use
	random_resumed_with_pending_wait_status.

Change-Id: I1b71d01beaa500a148b5b9797745103e13917325
---
 gdb/infrun.c                 | 40 +++++++++------------------------
 gdb/process-stratum-target.c | 43 ++++++++++++++++++++++++++++++++++++
 gdb/process-stratum-target.h |  5 +++++
 3 files changed, 59 insertions(+), 29 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 657f97e23fbb..80834fed1e3b 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3493,39 +3493,21 @@ print_target_wait_results (ptid_t waiton_ptid, ptid_t result_ptid,
 static struct thread_info *
 random_pending_event_thread (inferior *inf, ptid_t waiton_ptid)
 {
-  int num_events = 0;
+  process_stratum_target *proc_target = inf->process_target ();
+  thread_info *thread
+    = proc_target->random_resumed_with_pending_wait_status (inf, waiton_ptid);
 
-  auto has_event = [&] (thread_info *tp)
+  if (thread == nullptr)
     {
-      return (tp->ptid.matches (waiton_ptid)
-	      && tp->resumed ()
-	      && tp->has_pending_waitstatus ());
-    };
-
-  /* First see how many events we have.  Count only resumed threads
-     that have an event pending.  */
-  for (thread_info *tp : inf->non_exited_threads ())
-    if (has_event (tp))
-      num_events++;
-
-  if (num_events == 0)
-    return NULL;
-
-  /* Now randomly pick a thread out of those that have had events.  */
-  int random_selector = (int) ((num_events * (double) rand ())
-			       / (RAND_MAX + 1.0));
-
-  if (num_events > 1)
-    infrun_debug_printf ("Found %d events, selecting #%d",
-			 num_events, random_selector);
+      infrun_debug_printf ("None found.");
+      return nullptr;
+    }
 
-  /* Select the Nth thread that has had an event.  */
-  for (thread_info *tp : inf->non_exited_threads ())
-    if (has_event (tp))
-      if (random_selector-- == 0)
-	return tp;
+  infrun_debug_printf ("Found %s.", target_pid_to_str (thread->ptid).c_str ());
+  gdb_assert (thread->resumed ());
+  gdb_assert (thread->has_pending_waitstatus ());
 
-  gdb_assert_not_reached ("event thread not found");
+  return thread;
 }
 
 /* Wrapper for target_wait that first checks whether threads have
diff --git a/gdb/process-stratum-target.c b/gdb/process-stratum-target.c
index 44e138273b2f..c828da0f3bca 100644
--- a/gdb/process-stratum-target.c
+++ b/gdb/process-stratum-target.c
@@ -20,6 +20,7 @@
 #include "defs.h"
 #include "process-stratum-target.h"
 #include "inferior.h"
+#include <algorithm>
 
 process_stratum_target::~process_stratum_target ()
 {
@@ -140,6 +141,48 @@ process_stratum_target::maybe_remove_resumed_with_pending_wait_status
 
 /* See process-stratum-target.h.  */
 
+thread_info *
+process_stratum_target::random_resumed_with_pending_wait_status
+  (inferior *inf, ptid_t filter_ptid)
+{
+  auto matches = [inf, filter_ptid] (const thread_info &thread)
+    {
+      return thread.inf == inf && thread.ptid.matches (filter_ptid);
+    };
+
+  /* First see how many matching events we have.  */
+  const auto &l = m_resumed_with_pending_wait_status;
+  unsigned int count = std::count_if (l.begin (), l.end (), matches);
+
+  if (count == 0)
+    return nullptr;
+
+  /* Now randomly pick a thread out of those that match the criteria.  */
+  int random_selector
+    = (int) ((count * (double) rand ()) / (RAND_MAX + 1.0));
+
+  if (count > 1)
+    infrun_debug_printf ("Found %u events, selecting #%d",
+			 count, random_selector);
+
+  /* Select the Nth thread that matches.  */
+  auto it = std::find_if (l.begin (), l.end (),
+			  [&random_selector, &matches]
+			  (const thread_info &thread)
+    {
+      if (!matches (thread))
+	return false;
+
+      return random_selector-- == 0;
+    });
+
+  gdb_assert (it != l.end ());
+
+  return &*it;
+}
+
+/* See process-stratum-target.h.  */
+
 std::set<process_stratum_target *>
 all_non_exited_process_targets ()
 {
diff --git a/gdb/process-stratum-target.h b/gdb/process-stratum-target.h
index c73207e0531e..4d1d14f7a94f 100644
--- a/gdb/process-stratum-target.h
+++ b/gdb/process-stratum-target.h
@@ -93,6 +93,11 @@ class process_stratum_target : public target_ops
   bool has_resumed_with_pending_wait_status () const
   { return !m_resumed_with_pending_wait_status.empty (); }
 
+  /* Return a random resumed thread with pending wait status belonging to INF
+     and matching FILTER_PTID.  */
+  thread_info *random_resumed_with_pending_wait_status
+    (inferior *inf, ptid_t filter_ptid);
+
   /* The connection number.  Visible in "info connections".  */
   int connection_number = 0;
 
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (8 preceding siblings ...)
  2021-06-22 16:57 ` [PATCH 09/11] gdb: optimize selection of resumed thread with pending event Simon Marchi
@ 2021-06-22 16:57 ` Simon Marchi
  2021-07-05 15:52   ` Pedro Alves
  2021-06-22 16:57 ` [PATCH 11/11] gdb: optimize all_matching_threads_iterator Simon Marchi
  2021-07-13  0:47 ` [PATCH 00/11] Various thread lists optimizations Simon Marchi
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:57 UTC (permalink / raw)
  To: gdb-patches

When debugging a large number of threads (thousands), looking up a
thread by ptid_t using the inferior::thread_list linked list can add up.

Add inferior::thread_map, an std::unordered_map indexed by ptid_t, and
change the find_thread_ptid function to look up a thread using
std::unordered_map::find, instead of iterating on all of the
inferior's threads.  This should make it faster to look up a thread
from its ptid.

gdb/ChangeLog:
yyyy-mm-dd  Simon Marchi  <simon.marchi@efficios.com>
      	    Pedro Alves  <palves@palves.net>

	* gdbarch-selftests.c (register_to_value_test): Update the mock
	inferior's thread map as well.
	* inferior.c (inferior::clear_thread_list): Clear the thread map.
	* inferior.h: Include <unordered_map>.
	(class inferior::thread_map): New field.
	* regcache.c (cooked_read_test): Update the mock inferior's thread
	map as well.
	* thread.c (set_thread_exited): Remove the thread from the thread
	map.
	(new_thread): Insert the thread in the ptid map.
	(find_thread_ptid): Lookup up the thread in the ptid map.
	(thread_change_ptid): Update ptid map entry.

Change-Id: I3a8da0a839e18dee5bb98b8b7dbeb7f3dfa8ae1c
---
 gdb/inferior.c            |  1 +
 gdb/inferior.h            |  6 ++++++
 gdb/infrun.c              | 10 ++++++++++
 gdb/regcache.c            |  5 +++++
 gdb/scoped-mock-context.h |  1 +
 gdb/thread.c              | 29 ++++++++++++++++++++++++-----
 6 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/gdb/inferior.c b/gdb/inferior.c
index e07a8f88422a..8705c0f7f4b7 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -184,6 +184,7 @@ inferior::clear_thread_list (bool silent)
       if (thr->deletable ())
 	delete thr;
     });
+  ptid_thread_map.clear ();
 }
 
 void
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 2bfe29afed3f..6662a3bde463 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -63,6 +63,8 @@ struct thread_info;
 #include "process-stratum-target.h"
 #include "displaced-stepping.h"
 
+#include <unordered_map>
+
 struct infcall_suspend_state;
 struct infcall_control_state;
 
@@ -391,6 +393,10 @@ class inferior : public refcounted_object,
   /* This inferior's thread list, sorted by creation order.  */
   intrusive_list<thread_info> thread_list;
 
+  /* A map of ptid_t to thread_info*, for average O(1) ptid_t lookup.
+     Exited threads do not appear in the map.  */
+  std::unordered_map<ptid_t, thread_info *, hash_ptid> ptid_thread_map;
+
   /* Returns a range adapter covering the inferior's threads,
      including exited threads.  Used like this:
 
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 80834fed1e3b..1f290b7fa7a6 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -9421,8 +9421,13 @@ infrun_thread_ptid_changed ()
 
     target1.mock_inferior.pid = old_ptid.pid ();
     target1.mock_thread.ptid = old_ptid;
+    target1.mock_inferior.ptid_thread_map.clear ();
+    target1.mock_inferior.ptid_thread_map[old_ptid] = &target1.mock_thread;
+
     target2.mock_inferior.pid = old_ptid.pid ();
     target2.mock_thread.ptid = old_ptid;
+    target2.mock_inferior.ptid_thread_map.clear ();
+    target2.mock_inferior.ptid_thread_map[old_ptid] = &target2.mock_thread;
 
     auto restore_inferior_ptid = make_scoped_restore (&inferior_ptid, old_ptid);
     set_current_inferior (&target1.mock_inferior);
@@ -9445,8 +9450,13 @@ infrun_thread_ptid_changed ()
 
     target1.mock_inferior.pid = old_ptid.pid ();
     target1.mock_thread.ptid = old_ptid;
+    target1.mock_inferior.ptid_thread_map.clear ();
+    target1.mock_inferior.ptid_thread_map[old_ptid] = &target1.mock_thread;
+
     target2.mock_inferior.pid = old_ptid.pid ();
     target2.mock_thread.ptid = old_ptid;
+    target2.mock_inferior.ptid_thread_map.clear ();
+    target2.mock_inferior.ptid_thread_map[old_ptid] = &target2.mock_thread;
 
     auto restore_inferior_ptid = make_scoped_restore (&inferior_ptid, old_ptid);
     set_current_inferior (&target2.mock_inferior);
diff --git a/gdb/regcache.c b/gdb/regcache.c
index 21fa25d31553..ac44d714ddc1 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -2044,8 +2044,13 @@ regcache_thread_ptid_changed ()
 
   target1.mock_inferior.pid = old_ptid.pid ();
   target1.mock_thread.ptid = old_ptid;
+  target1.mock_inferior.ptid_thread_map.clear ();
+  target1.mock_inferior.ptid_thread_map[old_ptid] = &target1.mock_thread;
+
   target2.mock_inferior.pid = old_ptid.pid ();
   target2.mock_thread.ptid = old_ptid;
+  target2.mock_inferior.ptid_thread_map.clear ();
+  target2.mock_inferior.ptid_thread_map[old_ptid] = &target2.mock_thread;
 
   gdb_assert (regcaches.empty ());
 
diff --git a/gdb/scoped-mock-context.h b/gdb/scoped-mock-context.h
index ba3b81ed12a5..48fdbacbb14f 100644
--- a/gdb/scoped-mock-context.h
+++ b/gdb/scoped-mock-context.h
@@ -51,6 +51,7 @@ struct scoped_mock_context
     inferior_list.push_back (mock_inferior);
 
     mock_inferior.thread_list.push_back (mock_thread);
+    mock_inferior.ptid_thread_map[mock_ptid] = &mock_thread;
     mock_inferior.gdbarch = gdbarch;
     mock_inferior.aspace = mock_pspace.aspace;
     mock_inferior.pspace = &mock_pspace;
diff --git a/gdb/thread.c b/gdb/thread.c
index 26974e1b8cbc..0d5ec48691a0 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -199,6 +199,14 @@ set_thread_exited (thread_info *tp, bool silent)
 
       /* Clear breakpoints, etc. associated with this thread.  */
       clear_thread_inferior_resources (tp);
+
+      /* Remove from the ptid_t map.  We don't want for
+	 find_thread_ptid to find exited threads.  Also, the target
+	 may reuse the ptid for a new thread, and there can only be
+	 one value per key; adding a new thread with the same ptid_t
+	 would overwrite the exited thread's ptid entry.  */
+      size_t nr_deleted = tp->inf->ptid_thread_map.erase (tp->ptid);
+      gdb_assert (nr_deleted == 1);
     }
 }
 
@@ -221,6 +229,11 @@ new_thread (struct inferior *inf, ptid_t ptid)
 
   inf->thread_list.push_back (*tp);
 
+  /* A thread with this ptid should not exist in the map yet.  */
+  gdb_assert (inf->ptid_thread_map.find (ptid) == inf->ptid_thread_map.end ());
+
+  inf->ptid_thread_map[ptid] = tp;
+
   return tp;
 }
 
@@ -477,11 +490,11 @@ find_thread_ptid (inferior *inf, ptid_t ptid)
 {
   gdb_assert (inf != nullptr);
 
-  for (thread_info *tp : inf->non_exited_threads ())
-    if (tp->ptid == ptid)
-      return tp;
-
-  return NULL;
+  auto it = inf->ptid_thread_map.find (ptid);
+  if (it != inf->ptid_thread_map.end ())
+    return it->second;
+  else
+    return nullptr;
 }
 
 /* See gdbthread.h.  */
@@ -751,7 +764,13 @@ thread_change_ptid (process_stratum_target *targ,
   inf->pid = new_ptid.pid ();
 
   tp = find_thread_ptid (inf, old_ptid);
+  gdb_assert (tp != nullptr);
+
+  int num_erased = inf->ptid_thread_map.erase (old_ptid);
+  gdb_assert (num_erased == 1);
+
   tp->ptid = new_ptid;
+  inf->ptid_thread_map[new_ptid] = tp;
 
   gdb::observers::thread_ptid_changed.notify (targ, old_ptid, new_ptid);
 }
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 11/11] gdb: optimize all_matching_threads_iterator
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (9 preceding siblings ...)
  2021-06-22 16:57 ` [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid Simon Marchi
@ 2021-06-22 16:57 ` Simon Marchi
  2021-07-05 15:52   ` Pedro Alves
  2021-07-13  0:47 ` [PATCH 00/11] Various thread lists optimizations Simon Marchi
  11 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-06-22 16:57 UTC (permalink / raw)
  To: gdb-patches

all_matching_threads_iterator is used extensively in some pretty fast
paths, often under the all_non_exited_threads function.

If a filter target and thread-specific ptid are given, it iterates on
all threads of all inferiors of that target, to ultimately yield exactly
on thread.  And this happens quite often, which means we unnecessarily
spend time iterating on threads to find the one we are looking for.  The
same thing happens if an inferior-specific ptid is given, although there
the iterator yields all the threads of that inferior.

In those cases, the callers of all_non_exited_threads could have
different behaviors depending on the kind of ptid, to avoid this
inefficiency, but that would be very tedious.  Using
all_non_exited_threads has the advantage that one simple implementation
can work seamlessly on multiple threads or on one specific thread, just
by playing with the ptid.

Instead, optimize all_matching_threads_iterator directly to detect these
different cases and limiting what we iterate on to just what we need.

 - if filter_ptid is minus_one_ptid, do as we do now: filter inferiors
   based on filter_target, iterate on all of the matching inferiors'
   threads
 - if filter_ptid is a pid-only ptid (then a filter_target must
   necessarily be given), look up that inferior and iterate on all its
   threads
 - otherwise, filter_ptid is a thread-specific ptid, so look up that
   specific thread and "iterate" only on it

For the last case, what was an iteration on all threads of the filter
target now becomes a call to find_thread_ptid, which is quite efficient
now thanks to inferior::ptid_thread_map.

gdb/ChangeLog:

	* thread-iter.h (class all_matching_threads_iterator)
	<all_matching_threads_iterator>: Use default.
	<enum class mode>: New.
	<m_inf, m_thr>: Initialize.
	<m_filter_ptid>: Remove.
	* thread-iter.c (all_matching_threads_iterator::m_inf_matches):
	Don't filter on m_filter_ptid.
	(all_matching_threads_iterator::all_matching_threads_iterator):
	Choose path based on filter_ptid (all threads, all threads of
	inferior, single thread).
	(all_matching_threads_iterator::advance): Likewise.

Change-Id: Ic6a19845f5f760fa1b8eac8145793c0ff431bbc9
---
 gdb/thread-iter.c | 138 ++++++++++++++++++++++++++++++----------------
 gdb/thread-iter.h |  29 ++++++----
 2 files changed, 107 insertions(+), 60 deletions(-)

diff --git a/gdb/thread-iter.c b/gdb/thread-iter.c
index 31b7a36eaada..e56ccd857b0a 100644
--- a/gdb/thread-iter.c
+++ b/gdb/thread-iter.c
@@ -75,39 +75,56 @@ all_threads_iterator::advance ()
 bool
 all_matching_threads_iterator::m_inf_matches ()
 {
-  return ((m_filter_target == nullptr
-	   || m_filter_target == m_inf->process_target ())
-	  && (m_filter_ptid == minus_one_ptid
-	      || m_filter_ptid.pid () == m_inf->pid));
+  return (m_filter_target == nullptr
+	  || m_filter_target == m_inf->process_target ());
 }
 
 /* See thread-iter.h.  */
 
 all_matching_threads_iterator::all_matching_threads_iterator
   (process_stratum_target *filter_target, ptid_t filter_ptid)
-    : m_filter_target (filter_target),
-      m_filter_ptid (filter_ptid)
+  : m_filter_target (filter_target)
 {
-  gdb_assert ((filter_target == nullptr && filter_ptid == minus_one_ptid)
-	      || filter_target->stratum () == process_stratum);
-
-  for (inferior &inf : inferior_list)
+  if (filter_ptid == minus_one_ptid)
     {
-      m_inf = &inf;
-      if (m_inf_matches ())
-	for (auto thr_iter = m_inf->thread_list.begin ();
-	     thr_iter != m_inf->thread_list.end ();
-	     ++thr_iter)
-	  {
-	    if (thr_iter->ptid.matches (m_filter_ptid))
-	      {
-		m_thr = &*thr_iter;
-		return;
-	      }
-	  }
+      /* Iterate on all threads of all inferiors, possibly filtering on
+         FILTER_TARGET.  */
+      m_mode = mode::ALL_THREADS;
+
+      /* Seek the first thread of the first matching inferior.  */
+      for (inferior &inf : inferior_list)
+	{
+	  m_inf = &inf;
+
+	  if (!m_inf_matches ()
+	      || inf.thread_list.empty ())
+	    continue;
+
+	  m_thr = &inf.thread_list.front ();
+	  return;
+	}
     }
+  else
+    {
+      gdb_assert (filter_target != nullptr);
 
-  m_thr = nullptr;
+      if (filter_ptid.is_pid ())
+	{
+	  /* Iterate on all threads of the given inferior.  */
+	  m_mode = mode::ALL_THREADS_OF_INFERIOR;
+
+	  m_inf = find_inferior_pid (filter_target, filter_ptid.pid ());
+	  if (m_inf != nullptr)
+	    m_thr = &m_inf->thread_list.front ();
+	}
+      else
+	{
+	  /* Iterate on a single thread.  */
+	  m_mode = mode::SINGLE_THREAD;
+
+	  m_thr = find_thread_ptid (filter_target, filter_ptid);
+	}
+    }
 }
 
 /* See thread-iter.h.  */
@@ -115,32 +132,57 @@ all_matching_threads_iterator::all_matching_threads_iterator
 void
 all_matching_threads_iterator::advance ()
 {
-  intrusive_list<inferior>::iterator inf_iter (m_inf);
-  intrusive_list<thread_info>::iterator thr_iter (m_thr);
+  switch (m_mode)
+    {
+    case mode::ALL_THREADS:
+      {
+	intrusive_list<inferior>::iterator inf_iter (m_inf);
+	intrusive_list<thread_info>::iterator thr_iter
+	  = m_inf->thread_list.iterator_to (*m_thr);
+
+	/* The loop below is written in the natural way as-if we'd always
+	   start at the beginning of the inferior list.  This fast forwards
+	   the algorithm to the actual current position.  */
+	goto start;
+
+	for (; inf_iter != inferior_list.end (); ++inf_iter)
+	  {
+	    m_inf = &*inf_iter;
 
-  /* The loop below is written in the natural way as-if we'd always
-     start at the beginning of the inferior list.  This fast forwards
-     the algorithm to the actual current position.  */
-  goto start;
+	    if (!m_inf_matches ())
+	      continue;
 
-  for (; inf_iter != inferior_list.end (); ++inf_iter)
-    {
-      m_inf = &*inf_iter;
-      if (m_inf_matches ())
-	{
-	  thr_iter = m_inf->thread_list.begin ();
-	  while (thr_iter != m_inf->thread_list.end ())
-	    {
-	      if (thr_iter->ptid.matches (m_filter_ptid))
-		{
-		  m_thr = &*thr_iter;
-		  return;
-		}
-	    start:
-	      ++thr_iter;
-	    }
-	}
-    }
+	    thr_iter = m_inf->thread_list.begin ();
+	    while (thr_iter != m_inf->thread_list.end ())
+	      {
+		m_thr = &*thr_iter;
+		return;
 
-  m_thr = nullptr;
+	      start:
+		++thr_iter;
+	      }
+	  }
+      }
+      m_thr = nullptr;
+      break;
+
+    case mode::ALL_THREADS_OF_INFERIOR:
+      {
+	intrusive_list<thread_info>::iterator thr_iter
+	  = m_inf->thread_list.iterator_to (*m_thr);
+	++thr_iter;
+	if (thr_iter != m_inf->thread_list.end ())
+	  m_thr = &*thr_iter;
+	else
+	  m_thr = nullptr;
+	break;
+      }
+
+    case mode::SINGLE_THREAD:
+      m_thr = nullptr;
+      break;
+
+    default:
+      gdb_assert_not_reached ("invalid mode value");
+    }
 }
diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
index 2e43034550e8..6700f5593b91 100644
--- a/gdb/thread-iter.h
+++ b/gdb/thread-iter.h
@@ -99,12 +99,7 @@ class all_matching_threads_iterator
 				 ptid_t filter_ptid);
 
   /* Create a one-past-end iterator.  */
-  all_matching_threads_iterator ()
-    : m_inf (nullptr),
-      m_thr (nullptr),
-      m_filter_target (nullptr),
-      m_filter_ptid (minus_one_ptid)
-  {}
+  all_matching_threads_iterator () = default;
 
   thread_info *operator* () const { return m_thr; }
 
@@ -124,20 +119,30 @@ class all_matching_threads_iterator
   /* Advance to next thread, skipping filtered threads.  */
   void advance ();
 
-  /* True if M_INF matches the process identified by
-     M_FILTER_PTID.  */
+  /* True if M_INF has the process target M_FILTER_TARGET.  */
   bool m_inf_matches ();
 
 private:
+  enum class mode
+  {
+    /* All threads, possibly filtered down to a single target.  */
+    ALL_THREADS,
+
+    /* All threads of the given inferior.  */
+    ALL_THREADS_OF_INFERIOR,
+
+    /* A specific thread.  */
+    SINGLE_THREAD,
+  } m_mode;
+
   /* The current inferior.  */
-  inferior *m_inf;
+  inferior *m_inf = nullptr;
 
   /* The current thread.  */
-  thread_info *m_thr;
+  thread_info *m_thr = nullptr;
 
-  /* The filter.  */
+  /* The target we filter on (may be nullptr).  */
   process_stratum_target *m_filter_target;
-  ptid_t m_filter_ptid;
 };
 
 /* Filter for filtered_iterator.  Filters out exited threads.  */
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-06-22 16:56 ` [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it Simon Marchi
@ 2021-06-22 23:13   ` Lancelot SIX
  2021-06-23  0:48     ` Simon Marchi
  2021-07-05 15:44   ` Pedro Alves
  1 sibling, 1 reply; 49+ messages in thread
From: Lancelot SIX @ 2021-06-22 23:13 UTC (permalink / raw)
  To: Simon Marchi, Pedro Alves; +Cc: gdb-patches, Simon Marchi

On Tue, Jun 22, 2021 at 12:56:55PM -0400, Simon Marchi via Gdb-patches wrote:
> From: Pedro Alves <pedro@palves.net>
> 
> GDB currently has several objects that are put in a singly linked list,
> by having the object's type have a "next" pointer directly.  For
> example, struct thread_info and struct inferior.  Because these are
> simply-linked lists, and we don't keep track of a "tail" pointer, when
> we want to append a new element on the list, we need to walk the whole
> list to find the current tail.  It would be nice to get rid of that
> walk.  Removing elements from such lists also requires a walk, to find
> the "previous" position relative to the element being removed.  To
> eliminate the need for that walk, we could make those lists
> doubly-linked, by adding a "prev" pointer alongside "next".  It would be
> nice to avoid the boilerplace associated with maintaining such a list
> manually, though.  That is what the new intrusive_list type addresses.
> 
> With an intrusive list, it's also possible to move items out of the
> list without destroying them, which is interesting in our case for
> example for threads, when we exit them, but can't destroy them
> immediately.  We currently keep exited threads on the thread list, but
> we could change that which would simplify some things.
> 
> Note that with std::list, element removal is O(N).  I.e., with
> std::list, we need to walk the list to find the iterator pointing to
> the position to remove.  However, we could store a list iterator
> inside the object as soon as we put the object in the list, to address
> it, because std::list iterators are not invalidated when other
> elements are added/removed.  However, if you need to put the same
> object in more than one list, then std::list<object> doesn't work.
> You need to instead use std::list<object *>, which is less efficient
> for requiring extra memory allocations.  For an example of an object
> in multiple lists, see the step_over_next/step_over_prev fields in
> thread_info:
> 
>   /* Step-over chain.  A thread is in the step-over queue if these are
>      non-NULL.  If only a single thread is in the chain, then these
>      fields point to self.  */
>   struct thread_info *step_over_prev = NULL;
>   struct thread_info *step_over_next = NULL;
> 
> The new intrusive_list type gives us the advantages of an intrusive
> linked list, while avoiding the boilerplate associated with manually
> maintaining it.
> 
> intrusive_list's API follows the standard container interface, and thus
> std::list's interface.  It is based the API of Boost's intrusive list,
> here:
> 
>  https://www.boost.org/doc/libs/1_73_0/doc/html/boost/intrusive/list.html
> 
> Our implementation is relatively simple, while Boost's is complicated
> and intertwined due to a lot of customization options, which our version
> doesn't have.
> 
> The easiest way to use an intrusive_list is to make the list's element
> type inherit from intrusive_node.  This adds a prev/next pointers to
> the element type.  However, to support putting the same object in more
> than one list, intrusive_list supports putting the "node" info as a
> field member, so you can have more than one such nodes, one per list.
> 
> As a first guinea pig, this patch makes the per-inferior thread list use
> intrusive_list using the base class method.
> 
> Unlike Boost's implementation, ours is not a circular list.  An earlier
> version of the patch was circular: the instrusive_list type included an
> intrusive_list_node "head".  In this design, a node contained pointers
> to the previous and next nodes, not the previous and next elements.
> This wasn't great for when debugging GDB with GDB, as it was difficult
> to get from a pointer to the node to a pointer to the element.  With the
> design proposed in this patch, nodes contain pointers to the previous
> and next elements, making it easy to traverse the list by hand and
> inspect each element.
> 
> The intrusive_list object contains pointers to the first and last
> elements of the list.  They are nullptr if the list is empty.
> Each element's node contains a pointer to the previous and next
> elements.  The first element's previous pointer is nullptr and the last
> element's next pointer is nullptr.  Therefore, if there's a single
> element in the list, both its previous and next pointers are nullptr.
> To differentiate such an element from an element that is not linked into
> a list, the previous and next pointers contain a special value (-1) when
> the node is not linked.  This is necessary to be able to reliably tell
> if a given node is currently linked or not.
> 
> A begin() iterator points to the first item in the list.  An end()
> iterator contains nullptr.  This makes iteration until end naturally
> work, as advancing past the last element will make the iterator contain
> nullptr, making it equal to the end iterator.  If the list is empty,
> a begin() iterator will contain nullptr from the start, and therefore be
> immediately equal to the end.
> 
> Iterating on an intrusive_list yields references to objects (e.g.
> `thread_info&`).  The rest of GDB currently expects iterators and ranges
> to yield pointers (e.g. `thread_info*`).  To bridge the gap, add the
> reference_to_pointer_iterator type.  It is used to define
> inf_threads_iterator.
> 
> Add a Python pretty-printer, to help inspecting intrusive lists when
> debugging GDB with GDB.  Here's an example of the output:
> 
>     (top-gdb) p current_inferior_.m_obj.thread_list
>     $1 = intrusive list of thread_info = {0x61700002c000, 0x617000069080, 0x617000069400, 0x61700006d680, 0x61700006eb80}
> 
> It's not possible with current master, but with this patch [1] that I
> hope will be merged eventually, it's possible to index the list and
> access the pretty-printed value's children:
> 
>     (top-gdb) p current_inferior_.m_obj.thread_list[1]
>     $2 = (thread_info *) 0x617000069080
>     (top-gdb) p current_inferior_.m_obj.thread_list[1].ptid
>     $3 = {
>       m_pid = 406499,
>       m_lwp = 406503,
>       m_tid = 0
>     }
> 
> Even though iterating the list in C++ yields references, the Python
> pretty-printer yields pointers.  The reason for this is that the output
> of printing the thread list above would be unreadable, IMO, if each
> thread_info object was printed in-line, since they contain so much
> information.  I think it's more useful to print pointers, and let the
> user drill down as needed.
> 
> [1] https://sourceware.org/pipermail/gdb-patches/2021-April/178050.html
> 
> YYYY-MM-DD  Pedro Alves  <pedro@palves.net>
> YYYY-MM-DD  Simon Marchi  <simon.marchi@efficios.com>
> 
> gdbsupport/ChangeLog:
> 
> 	* intrusive_list.h: New.
> 	* filtered-iterator.h (class filtered_iterator): Add
> 	constructor.
> 	* safe-iterator.h (class basic_safe_iterator): Add defaulted
> 	constructors.
> 
> gdb/ChangeLog:
> 
> 	* Makefile.in (SELFTESTS_SRCS): Add
> 	unittests/intrusive_list-selftests.c.
> 	* gdbthread.h (class thread_info): Inherit from
> 	intrusive_list_node.
> 	<next>: Remove.
> 	(set_thread_exited): New declaration.
> 	* thread.c (set_thread_exited): Make non-static.
> 	(init_thread_list): Use clear_thread_list.
> 	(new_thread): Adjust.
> 	(delete_thread_1): Adjust.
> 	(first_thread_of_inferior): Adjust.
> 	(update_threads_executing): Adjust.
> 	* inferior.h (class inferior) <thread_list>: Change type to
> 	intrusive_list.
> 	<threads, non_exited_threads, threads_safe>: Adjust.
> 	<clear_thread_list>: New.
> 	* inferior.c (inferior::clear_thread_list): New.
> 	(delete_inferior): Use clear_thread_list.
> 	(exit_inferior_1): Use clear_thread_list.
> 	* scoped-mock-context.h (struct scoped_mock_context)
> 	<restore_thread_list>: Remove.
> 	<scoped_mock_context>: Insert mock thread in mock inferior's
> 	thread list.
> 	* thread-iter.h (inf_threads_iterator, inf_threads_range,
> 	inf_non_exited_threads_range, safe_inf_threads_range): Change type.
> 	(inf_threads_iterator): Define using next_iterator_intrusive.
> 	* thread-iter.c (all_threads_iterator::all_threads_iterator):
> 	Adjust.
> 	(all_threads_iterator::advance): Adjust.
> 	(all_matching_threads_iterator::all_matching_threads_iterator):
> 	Adjust.
> 	(all_matching_threads_iterator::advance): Adjust.
> 	* unittests/intrusive_list-selftests.c: New.
> 
> Co-Authored-By: Simon Marchi <simon.marchi@efficios.com>
> Change-Id: I3412a14dc77f25876d742dab8f44e0ba7c7586c0
> ---
>  gdb/Makefile.in                            |   1 +
>  gdb/gdb-gdb.py.in                          |  91 ++-
>  gdb/gdbthread.h                            |  13 +-
>  gdb/inferior.c                             |  24 +-
>  gdb/inferior.h                             |  14 +-
>  gdb/scoped-mock-context.h                  |   4 +-
>  gdb/thread-iter.c                          |  53 +-
>  gdb/thread-iter.h                          |   5 +-
>  gdb/thread.c                               |  61 +-
>  gdb/unittests/intrusive_list-selftests.c   | 734 +++++++++++++++++++++
>  gdbsupport/intrusive_list.h                | 559 ++++++++++++++++
>  gdbsupport/reference-to-pointer-iterator.h |  79 +++
>  12 files changed, 1554 insertions(+), 84 deletions(-)
>  create mode 100644 gdb/unittests/intrusive_list-selftests.c
>  create mode 100644 gdbsupport/intrusive_list.h
>  create mode 100644 gdbsupport/reference-to-pointer-iterator.h
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 1bc97885536e..b405950783c2 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -449,6 +449,7 @@ SELFTESTS_SRCS = \
>  	unittests/function-view-selftests.c \
>  	unittests/gdb_tilde_expand-selftests.c \
>  	unittests/gmp-utils-selftests.c \
> +	unittests/intrusive_list-selftests.c \
>  	unittests/lookup_name_info-selftests.c \
>  	unittests/memory-map-selftests.c \
>  	unittests/memrange-selftests.c \
> diff --git a/gdb/gdb-gdb.py.in b/gdb/gdb-gdb.py.in
> index af9fcfedc2f3..7ff91bd779f9 100644
> --- a/gdb/gdb-gdb.py.in
> +++ b/gdb/gdb-gdb.py.in
> @@ -276,16 +276,101 @@ class CoreAddrPrettyPrinter:
>          return hex(int(self._val))
>  
>  
> +class IntrusiveListPrinter:
> +    """Print a struct intrusive_list."""
> +
> +    def __init__(self, val):
> +        self._val = val
> +
> +        # Type of linked items.
> +        self._item_type = self._val.type.template_argument(0)
> +        self._node_ptr_type = gdb.lookup_type(
> +            f"intrusive_list_node<{self._item_type.tag}>"

Hi,

I do not know what are the compatibility constraints / minimum python
version required, but f-string do require at least python-3.6.  This
covers all still supported python versions, but there might be distros
out-there that are not there yet (there are a few of those in this
file).

> +        ).pointer()
> +
> +        # Type of value -> node converter.
> +        self._conv_type = self._val.type.template_argument(1)
> +
> +        if self._uses_member_node():
> +            # The second template argument of intrusive_member_node is a member
> +            # pointer value.  Its value is the offset of the node member in the
> +            # enclosing type.
> +            member_node_ptr = self._conv_type.template_argument(1)
> +            member_node_ptr = member_node_ptr.cast(gdb.lookup_type("int"))
> +            self._member_node_offset = int(member_node_ptr)
> +
> +            # This is only needed in _as_node_ptr if using a member node.  Look it
> +            # up here so we only do it once.
> +            self._char_ptr_type = gdb.lookup_type("char").pointer()
> +
> +    def display_hint(self):
> +        return "array"
> +
> +    # Return True if the list items use a node as a member.  Return False if
> +    # they use a node as a base class.
> +    def _uses_member_node(self):

The documentation for the function should probably go as a doc string,
not a comment before the function:

    def _uses_member_node(self):
        """ Return True if the list items use a node as a member.  Return False
	if they use a node as a base class.
	"""

The same remark stands for the other method in this file.

Best,
Lancelot.

> +        if self._conv_type.name.startswith("intrusive_member_node<"):
> +            return True
> +        elif self._conv_type.name.startswith("intrusive_base_node<"):
> +            return False
> +        else:
> +            raise RuntimeError(
> +                f"Unexpected intrusive_list value -> node converter type: {self._conv_type.name}"
> +            )
> +
> +    def to_string(self):
> +        s = f"intrusive list of {self._item_type}"
> +
> +        if self._uses_member_node():
> +            node_member = self._conv_type.template_argument(1)
> +            s += f", linked through {node_member}"
> +
> +        return s
> +
> +    # Given ELEM_PTR, a pointer to a list element, return a pointer to the
> +    # corresponding intrusive_list_node.
> +    def _as_node_ptr(self, elem_ptr):
> +        assert elem_ptr.type.code == gdb.TYPE_CODE_PTR
> +
> +        if self._uses_member_node():
> +            # Node as a memer: add the member node offset from to the element's

s/memer/member/ ?

> +            # address to get the member node's address.
> +            elem_char_ptr = elem_ptr.cast(self._char_ptr_type)
> +            node_char_ptr = elem_char_ptr + self._member_node_offset
> +            return node_char_ptr.cast(self._node_ptr_type)
> +        else:
> +            # Node as a base: just casting from node pointer to item pointer
> +            # will adjust the pointer value.
> +            return elem_ptr.cast(self._node_ptr_type)
> +
> +    # Generator that yields one tuple per list item.
> +    def _children_generator(self):
> +        elem_ptr = self._val["m_front"]
> +        idx = 0
> +        while elem_ptr != 0:
> +            yield (str(idx), elem_ptr)
> +            node_ptr = self._as_node_ptr(elem_ptr)
> +            elem_ptr = node_ptr["next"]
> +            idx += 1
> +
> +    def children(self):
> +        return self._children_generator()
> +
> +
>  def type_lookup_function(val):
>      """A routine that returns the correct pretty printer for VAL
>      if appropriate.  Returns None otherwise.
>      """
> -    if val.type.tag == "type":
> +    tag = val.type.tag
> +    name = val.type.name
> +    if tag == "type":
>          return StructTypePrettyPrinter(val)
> -    elif val.type.tag == "main_type":
> +    elif tag == "main_type":
>          return StructMainTypePrettyPrinter(val)
> -    elif val.type.name == "CORE_ADDR":
> +    elif name == "CORE_ADDR":
>          return CoreAddrPrettyPrinter(val)
> +    elif tag is not None and tag.startswith("intrusive_list<"):
> +        return IntrusiveListPrinter(val)
>      return None
>  
>  
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index f19c88f9bb4a..0e28b1de9ff0 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -33,6 +33,7 @@ struct symtab;
>  #include "gdbsupport/common-gdbthread.h"
>  #include "gdbsupport/forward-scope-exit.h"
>  #include "displaced-stepping.h"
> +#include "gdbsupport/intrusive_list.h"
>  
>  struct inferior;
>  struct process_stratum_target;
> @@ -222,9 +223,12 @@ struct private_thread_info
>     delete_thread).  All other thread references are considered weak
>     references.  Placing a thread in the thread list is an implicit
>     strong reference, and is thus not accounted for in the thread's
> -   refcount.  */
> +   refcount.
>  
> -class thread_info : public refcounted_object
> +   The intrusive_list_node base links threads in a per-inferior list.  */
> +
> +class thread_info : public refcounted_object,
> +		    public intrusive_list_node<thread_info>
>  {
>  public:
>    explicit thread_info (inferior *inf, ptid_t ptid);
> @@ -235,7 +239,6 @@ class thread_info : public refcounted_object
>    /* Mark this thread as running and notify observers.  */
>    void set_running (bool running);
>  
> -  struct thread_info *next = NULL;
>    ptid_t ptid;			/* "Actual process id";
>  				    In fact, this may be overloaded with 
>  				    kernel thread id, etc.  */
> @@ -435,6 +438,10 @@ extern void delete_thread (struct thread_info *thread);
>     this thread belonged to has already exited, for example.  */
>  extern void delete_thread_silent (struct thread_info *thread);
>  
> +/* Mark the thread exited, but don't delete it or remove it from the
> +   inferior thread list.  */
> +extern void set_thread_exited (thread_info *tp, bool silent);
> +
>  /* Delete a step_resume_breakpoint from the thread database.  */
>  extern void delete_step_resume_breakpoint (struct thread_info *);
>  
> diff --git a/gdb/inferior.c b/gdb/inferior.c
> index 059839ec9626..693b196556d5 100644
> --- a/gdb/inferior.c
> +++ b/gdb/inferior.c
> @@ -163,6 +163,19 @@ add_inferior (int pid)
>    return inf;
>  }
>  
> +/* See inferior.h.  */
> +
> +void
> +inferior::clear_thread_list (bool silent)
> +{
> +  thread_list.clear_and_dispose ([=] (thread_info *thr)
> +    {
> +      set_thread_exited (thr, silent);
> +      if (thr->deletable ())
> +	delete thr;
> +    });
> +}
> +
>  void
>  delete_inferior (struct inferior *todel)
>  {
> @@ -177,8 +190,7 @@ delete_inferior (struct inferior *todel)
>    if (!inf)
>      return;
>  
> -  for (thread_info *tp : inf->threads_safe ())
> -    delete_thread_silent (tp);
> +  inf->clear_thread_list (true);
>  
>    if (infprev)
>      infprev->next = inf->next;
> @@ -209,13 +221,7 @@ exit_inferior_1 (struct inferior *inftoex, int silent)
>    if (!inf)
>      return;
>  
> -  for (thread_info *tp : inf->threads_safe ())
> -    {
> -      if (silent)
> -	delete_thread_silent (tp);
> -      else
> -	delete_thread (tp);
> -    }
> +  inf->clear_thread_list (silent);
>  
>    gdb::observers::inferior_exit.notify (inf);
>  
> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index c63990aabe0e..2ae9f9a5f9c4 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -390,8 +390,8 @@ class inferior : public refcounted_object
>    /* Pointer to next inferior in singly-linked list of inferiors.  */
>    struct inferior *next = NULL;
>  
> -  /* This inferior's thread list.  */
> -  thread_info *thread_list = nullptr;
> +  /* This inferior's thread list, sorted by creation order.  */
> +  intrusive_list<thread_info> thread_list;
>  
>    /* Returns a range adapter covering the inferior's threads,
>       including exited threads.  Used like this:
> @@ -400,7 +400,7 @@ class inferior : public refcounted_object
>  	 { .... }
>    */
>    inf_threads_range threads ()
> -  { return inf_threads_range (this->thread_list); }
> +  { return inf_threads_range (this->thread_list.begin ()); }
>  
>    /* Returns a range adapter covering the inferior's non-exited
>       threads.  Used like this:
> @@ -409,7 +409,7 @@ class inferior : public refcounted_object
>  	 { .... }
>    */
>    inf_non_exited_threads_range non_exited_threads ()
> -  { return inf_non_exited_threads_range (this->thread_list); }
> +  { return inf_non_exited_threads_range (this->thread_list.begin ()); }
>  
>    /* Like inferior::threads(), but returns a range adapter that can be
>       used with range-for, safely.  I.e., it is safe to delete the
> @@ -420,7 +420,11 @@ class inferior : public refcounted_object
>  	 delete f;
>    */
>    inline safe_inf_threads_range threads_safe ()
> -  { return safe_inf_threads_range (this->thread_list); }
> +  { return safe_inf_threads_range (this->thread_list.begin ()); }
> +
> +  /* Delete all threads in the thread list.  If SILENT, exit threads
> +     silently.  */
> +  void clear_thread_list (bool silent);
>  
>    /* Continuations-related methods.  A continuation is an std::function
>       to be called to finish the execution of a command when running
> diff --git a/gdb/scoped-mock-context.h b/gdb/scoped-mock-context.h
> index 8d295ba1bbe6..37ffe5117423 100644
> --- a/gdb/scoped-mock-context.h
> +++ b/gdb/scoped-mock-context.h
> @@ -44,9 +44,6 @@ struct scoped_mock_context
>  
>    scoped_restore_current_pspace_and_thread restore_pspace_thread;
>  
> -  scoped_restore_tmpl<thread_info *> restore_thread_list
> -    {&mock_inferior.thread_list, &mock_thread};
> -
>    /* Add the mock inferior to the inferior list so that look ups by
>       target+ptid can find it.  */
>    scoped_restore_tmpl<inferior *> restore_inferior_list
> @@ -54,6 +51,7 @@ struct scoped_mock_context
>  
>    explicit scoped_mock_context (gdbarch *gdbarch)
>    {
> +    mock_inferior.thread_list.push_back (mock_thread);
>      mock_inferior.gdbarch = gdbarch;
>      mock_inferior.aspace = mock_pspace.aspace;
>      mock_inferior.pspace = &mock_pspace;
> diff --git a/gdb/thread-iter.c b/gdb/thread-iter.c
> index 012ca5fab090..a1cdd0206bd4 100644
> --- a/gdb/thread-iter.c
> +++ b/gdb/thread-iter.c
> @@ -27,8 +27,15 @@ all_threads_iterator::all_threads_iterator (begin_t)
>  {
>    /* Advance M_INF/M_THR to the first thread's position.  */
>    for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
> -    if ((m_thr = m_inf->thread_list) != NULL)
> -      return;
> +    {
> +      auto thr_iter = m_inf->thread_list.begin ();
> +      if (thr_iter != m_inf->thread_list.end ())
> +	{
> +	  m_thr = &*thr_iter;
> +	  return;
> +	}
> +    }
> +  m_thr = nullptr;
>  }
>  
>  /* See thread-iter.h.  */
> @@ -36,6 +43,8 @@ all_threads_iterator::all_threads_iterator (begin_t)
>  void
>  all_threads_iterator::advance ()
>  {
> +  intrusive_list<thread_info>::iterator thr_iter (m_thr);
> +
>    /* The loop below is written in the natural way as-if we'd always
>       start at the beginning of the inferior list.  This fast forwards
>       the algorithm to the actual current position.  */
> @@ -43,14 +52,17 @@ all_threads_iterator::advance ()
>  
>    for (; m_inf != NULL; m_inf = m_inf->next)
>      {
> -      m_thr = m_inf->thread_list;
> -      while (m_thr != NULL)
> +      thr_iter = m_inf->thread_list.begin ();
> +      while (thr_iter != m_inf->thread_list.end ())
>  	{
> +	  m_thr = &*thr_iter;
>  	  return;
>  	start:
> -	  m_thr = m_thr->next;
> +	  ++thr_iter;
>  	}
>      }
> +
> +  m_thr = nullptr;
>  }
>  
>  /* See thread-iter.h.  */
> @@ -74,12 +86,18 @@ all_matching_threads_iterator::all_matching_threads_iterator
>    gdb_assert ((filter_target == nullptr && filter_ptid == minus_one_ptid)
>  	      || filter_target->stratum () == process_stratum);
>  
> -  m_thr = nullptr;
>    for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
>      if (m_inf_matches ())
> -      for (m_thr = m_inf->thread_list; m_thr != NULL; m_thr = m_thr->next)
> -	if (m_thr->ptid.matches (m_filter_ptid))
> -	  return;
> +      for (auto thr_iter = m_inf->thread_list.begin ();
> +	   thr_iter != m_inf->thread_list.end ();
> +	   ++thr_iter)
> +	if (thr_iter->ptid.matches (m_filter_ptid))
> +	  {
> +	    m_thr = &*thr_iter;
> +	    return;
> +	  }
> +
> +  m_thr = nullptr;
>  }
>  
>  /* See thread-iter.h.  */
> @@ -87,6 +105,8 @@ all_matching_threads_iterator::all_matching_threads_iterator
>  void
>  all_matching_threads_iterator::advance ()
>  {
> +  intrusive_list<thread_info>::iterator thr_iter (m_thr);
> +
>    /* The loop below is written in the natural way as-if we'd always
>       start at the beginning of the inferior list.  This fast forwards
>       the algorithm to the actual current position.  */
> @@ -95,13 +115,18 @@ all_matching_threads_iterator::advance ()
>    for (; m_inf != NULL; m_inf = m_inf->next)
>      if (m_inf_matches ())
>        {
> -	m_thr = m_inf->thread_list;
> -	while (m_thr != NULL)
> +	thr_iter = m_inf->thread_list.begin ();
> +	while (thr_iter != m_inf->thread_list.end ())
>  	  {
> -	    if (m_thr->ptid.matches (m_filter_ptid))
> -	      return;
> +	    if (thr_iter->ptid.matches (m_filter_ptid))
> +	      {
> +		m_thr = &*thr_iter;
> +		return;
> +	      }
>  	  start:
> -	    m_thr = m_thr->next;
> +	    ++thr_iter;
>  	  }
>        }
> +
> +  m_thr = nullptr;
>  }
> diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
> index 098af0f3241b..2e43034550e8 100644
> --- a/gdb/thread-iter.h
> +++ b/gdb/thread-iter.h
> @@ -20,13 +20,16 @@
>  #define THREAD_ITER_H
>  
>  #include "gdbsupport/filtered-iterator.h"
> +#include "gdbsupport/iterator-range.h"
>  #include "gdbsupport/next-iterator.h"
> +#include "gdbsupport/reference-to-pointer-iterator.h"
>  #include "gdbsupport/safe-iterator.h"
>  
>  /* A forward iterator that iterates over a given inferior's
>     threads.  */
>  
> -using inf_threads_iterator = next_iterator<thread_info>;
> +using inf_threads_iterator
> +  = reference_to_pointer_iterator<intrusive_list<thread_info>::iterator>;
>  
>  /* A forward iterator that iterates over all threads of all
>     inferiors.  */
> diff --git a/gdb/thread.c b/gdb/thread.c
> index f850f05ad48e..89f51c01c993 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -177,9 +177,9 @@ clear_thread_inferior_resources (struct thread_info *tp)
>    clear_inline_frame_state (tp);
>  }
>  
> -/* Set the TP's state as exited.  */
> +/* See gdbthread.h.  */
>  
> -static void
> +void
>  set_thread_exited (thread_info *tp, bool silent)
>  {
>    /* Dead threads don't need to step-over.  Remove from chain.  */
> @@ -203,17 +203,8 @@ init_thread_list (void)
>  {
>    highest_thread_num = 0;
>  
> -  for (thread_info *tp : all_threads_safe ())
> -    {
> -      inferior *inf = tp->inf;
> -
> -      if (tp->deletable ())
> -	delete tp;
> -      else
> -	set_thread_exited (tp, 1);
> -
> -      inf->thread_list = NULL;
> -    }
> +  for (inferior *inf : all_inferiors ())
> +    inf->clear_thread_list (true);
>  }
>  
>  /* Allocate a new thread of inferior INF with target id PTID and add
> @@ -224,21 +215,7 @@ new_thread (struct inferior *inf, ptid_t ptid)
>  {
>    thread_info *tp = new thread_info (inf, ptid);
>  
> -  if (inf->thread_list == NULL)
> -    inf->thread_list = tp;
> -  else
> -    {
> -      struct thread_info *last;
> -
> -      for (last = inf->thread_list; last->next != NULL; last = last->next)
> -	gdb_assert (ptid != last->ptid
> -		    || last->state == THREAD_EXITED);
> -
> -      gdb_assert (ptid != last->ptid
> -		  || last->state == THREAD_EXITED);
> -
> -      last->next = tp;
> -    }
> +  inf->thread_list.push_back (*tp);
>  
>    return tp;
>  }
> @@ -462,29 +439,18 @@ delete_thread_1 (thread_info *thr, bool silent)
>  {
>    gdb_assert (thr != nullptr);
>  
> -  struct thread_info *tp, *tpprev = NULL;
> -
> -  for (tp = thr->inf->thread_list; tp; tpprev = tp, tp = tp->next)
> -    if (tp == thr)
> -      break;
> +  set_thread_exited (thr, silent);
>  
> -  if (!tp)
> -    return;
> -
> -  set_thread_exited (tp, silent);
> -
> -  if (!tp->deletable ())
> +  if (!thr->deletable ())
>      {
>         /* Will be really deleted some other time.  */
>         return;
>       }
>  
> -  if (tpprev)
> -    tpprev->next = tp->next;
> -  else
> -    tp->inf->thread_list = tp->next;
> +  auto it = thr->inf->thread_list.iterator_to (*thr);
> +  thr->inf->thread_list.erase (it);
>  
> -  delete tp;
> +  delete thr;
>  }
>  
>  /* See gdbthread.h.  */
> @@ -629,7 +595,10 @@ in_thread_list (process_stratum_target *targ, ptid_t ptid)
>  thread_info *
>  first_thread_of_inferior (inferior *inf)
>  {
> -  return inf->thread_list;
> +  if (inf->thread_list.empty ())
> +    return nullptr;
> +
> +  return &inf->thread_list.front ();
>  }
>  
>  thread_info *
> @@ -2018,7 +1987,7 @@ update_threads_executing (void)
>  
>        /* If the process has no threads, then it must be we have a
>  	 process-exit event pending.  */
> -      if (inf->thread_list == NULL)
> +      if (inf->thread_list.empty ())
>  	{
>  	  targ->threads_executing = true;
>  	  return;
> diff --git a/gdb/unittests/intrusive_list-selftests.c b/gdb/unittests/intrusive_list-selftests.c
> new file mode 100644
> index 000000000000..3ccff54b5ff9
> --- /dev/null
> +++ b/gdb/unittests/intrusive_list-selftests.c
> @@ -0,0 +1,734 @@
> +/* Tests fpr intrusive double linked list for GDB, the GNU debugger.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program 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 of the License, or
> +   (at your option) any later version.
> +
> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +
> +#include "gdbsupport/intrusive_list.h"
> +#include "gdbsupport/selftest.h"
> +#include <unordered_set>
> +
> +/* An item type using intrusive_list_node by inheriting from it and its
> +   corresponding list type.  Put another base before intrusive_list_node
> +   so that a pointer to the node != a pointer to the item.  */
> +
> +struct other_base
> +{
> +  int n = 1;
> +};
> +
> +struct item_with_base : public other_base,
> +			public intrusive_list_node<item_with_base>
> +{
> +  item_with_base (const char *name)
> +    : name (name)
> +  {}
> +
> +  const char *const name;
> +};
> +
> +using item_with_base_list = intrusive_list<item_with_base>;
> +
> +/* An item type using intrusive_list_node as a field and its corresponding
> +   list type.  Put the other field before the node, so that a pointer to the
> +   node != a pointer to the item.  */
> +
> +struct item_with_member
> +{
> +  item_with_member (const char *name)
> +    : name (name)
> +  {}
> +
> +  const char *const name;
> +  intrusive_list_node<item_with_member> node;
> +};
> +
> +using item_with_member_node
> +  = intrusive_member_node<item_with_member, &item_with_member::node>;
> +using item_with_member_list
> +  = intrusive_list<item_with_member, item_with_member_node>;
> +
> +/* To run all tests using both the base and member methods, all tests are
> +   declared in this templated class, which is instantiated once for each
> +   list type.  */
> +
> +template <typename ListType>
> +struct intrusive_list_test
> +{
> +  using item_type = typename ListType::value_type;
> +
> +  /* Verify that LIST contains exactly the items in EXPECTED.
> +
> +     Traverse the list forward and backwards to exercise all links.  */
> +
> +  static void
> +  verify_items (const ListType &list,
> +		gdb::array_view<const typename ListType::value_type *> expected)
> +  {
> +    int i = 0;
> +
> +    for (typename ListType::iterator it = list.begin ();
> +	 it != list.end ();
> +	 ++it)
> +      {
> +	const item_type &item = *it;
> +
> +	gdb_assert (i < expected.size ());
> +	gdb_assert (&item == expected[i]);
> +
> +	++i;
> +      }
> +
> +    gdb_assert (i == expected.size ());
> +
> +    for (typename ListType::reverse_iterator it = list.rbegin ();
> +	 it != list.rend ();
> +	 ++it)
> +      {
> +	const item_type &item = *it;
> +
> +	--i;
> +
> +	gdb_assert (i >= 0);
> +	gdb_assert (&item == expected[i]);
> +      }
> +
> +    gdb_assert (i == 0);
> +  }
> +
> +  static void
> +  test_move_constructor ()
> +  {
> +    {
> +      /* Other list is not empty.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list1;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +      list1.push_back (b);
> +      list1.push_back (c);
> +
> +      ListType list2 (std::move (list1));
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a, &b, &c};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Other list contains 1 element.  */
> +      item_type a ("a");
> +      ListType list1;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +
> +      ListType list2 (std::move (list1));
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Other list is empty.  */
> +      ListType list1;
> +      std::vector<const item_type *> expected;
> +
> +      ListType list2 (std::move (list1));
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {};
> +      verify_items (list2, expected);
> +    }
> +  }
> +
> +  static void
> +  test_move_assignment ()
> +  {
> +    {
> +      /* Both lists are not empty.  */
> +      item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +      list1.push_back (b);
> +      list1.push_back (c);
> +
> +      list2.push_back (d);
> +      list2.push_back (e);
> +
> +      list2 = std::move (list1);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a, &b, &c};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* rhs list is empty.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list2.push_back (a);
> +      list2.push_back (b);
> +      list2.push_back (c);
> +
> +      list2 = std::move (list1);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* lhs list is empty.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +      list1.push_back (b);
> +      list1.push_back (c);
> +
> +      list2 = std::move (list1);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a, &b, &c};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Both lists contain 1 item.  */
> +      item_type a ("a"), b ("b");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +      list2.push_back (b);
> +
> +      list2 = std::move (list1);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Both lists are empty.  */
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list2 = std::move (list1);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {};
> +      verify_items (list2, expected);
> +    }
> +  }
> +
> +  static void
> +  test_swap ()
> +  {
> +    {
> +      /* Two non-empty lists.  */
> +      item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +      list1.push_back (b);
> +      list1.push_back (c);
> +
> +      list2.push_back (d);
> +      list2.push_back (e);
> +
> +      std::swap (list1, list2);
> +
> +      expected = {&d, &e};
> +      verify_items (list1, expected);
> +
> +      expected = {&a, &b, &c};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Other is empty.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +      list1.push_back (b);
> +      list1.push_back (c);
> +
> +      std::swap (list1, list2);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a, &b, &c};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* *this is empty.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list2.push_back (a);
> +      list2.push_back (b);
> +      list2.push_back (c);
> +
> +      std::swap (list1, list2);
> +
> +      expected = {&a, &b, &c};
> +      verify_items (list1, expected);
> +
> +      expected = {};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Both lists empty.  */
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      std::swap (list1, list2);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {};
> +      verify_items (list2, expected);
> +    }
> +
> +    {
> +      /* Swap one element twice.  */
> +      item_type a ("a");
> +      ListType list1;
> +      ListType list2;
> +      std::vector<const item_type *> expected;
> +
> +      list1.push_back (a);
> +
> +      std::swap (list1, list2);
> +
> +      expected = {};
> +      verify_items (list1, expected);
> +
> +      expected = {&a};
> +      verify_items (list2, expected);
> +
> +      std::swap (list1, list2);
> +
> +      expected = {&a};
> +      verify_items (list1, expected);
> +
> +      expected = {};
> +      verify_items (list2, expected);
> +    }
> +  }
> +
> +  static void
> +  test_front_back ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    const ListType &clist = list;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    gdb_assert (&list.front () == &a);
> +    gdb_assert (&clist.front () == &a);
> +    gdb_assert (&list.back () == &c);
> +    gdb_assert (&clist.back () == &c);
> +  }
> +
> +  static void
> +  test_push_front ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +
> +    expected = {};
> +    verify_items (list, expected);
> +
> +    list.push_front (a);
> +    expected = {&a};
> +    verify_items (list, expected);
> +
> +    list.push_front (b);
> +    expected = {&b, &a};
> +    verify_items (list, expected);
> +
> +    list.push_front (c);
> +    expected = {&c, &b, &a};
> +    verify_items (list, expected);
> +  }
> +
> +  static void
> +  test_push_back ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +
> +    expected = {};
> +    verify_items (list, expected);
> +
> +    list.push_back (a);
> +    expected = {&a};
> +    verify_items (list, expected);
> +
> +    list.push_back (b);
> +    expected = {&a, &b};
> +    verify_items (list, expected);
> +
> +    list.push_back (c);
> +    expected = {&a, &b, &c};
> +    verify_items (list, expected);
> +  }
> +
> +  static void
> +  test_insert ()
> +  {
> +    std::vector<const item_type *> expected;
> +
> +    {
> +      /* Insert at beginning.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list;
> +
> +
> +      list.insert (list.begin (), a);
> +      expected = {&a};
> +      verify_items (list, expected);
> +
> +      list.insert (list.begin (), b);
> +      expected = {&b, &a};
> +      verify_items (list, expected);
> +
> +      list.insert (list.begin (), c);
> +      expected = {&c, &b, &a};
> +      verify_items (list, expected);
> +    }
> +
> +    {
> +      /* Insert at end.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list;
> +
> +
> +      list.insert (list.end (), a);
> +      expected = {&a};
> +      verify_items (list, expected);
> +
> +      list.insert (list.end (), b);
> +      expected = {&a, &b};
> +      verify_items (list, expected);
> +
> +      list.insert (list.end (), c);
> +      expected = {&a, &b, &c};
> +      verify_items (list, expected);
> +    }
> +
> +    {
> +      /* Insert in the middle.  */
> +      item_type a ("a"), b ("b"), c ("c");
> +      ListType list;
> +
> +      list.push_back (a);
> +      list.push_back (b);
> +
> +      list.insert (list.iterator_to (b), c);
> +      expected = {&a, &c, &b};
> +      verify_items (list, expected);
> +    }
> +
> +    {
> +      /* Insert in empty list. */
> +      item_type a ("a");
> +      ListType list;
> +
> +      list.insert (list.end (), a);
> +      expected = {&a};
> +      verify_items (list, expected);
> +    }
> +  }
> +
> +  static void
> +  test_pop_front ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    list.pop_front ();
> +    expected = {&b, &c};
> +    verify_items (list, expected);
> +
> +    list.pop_front ();
> +    expected = {&c};
> +    verify_items (list, expected);
> +
> +    list.pop_front ();
> +    expected = {};
> +    verify_items (list, expected);
> +  }
> +
> +  static void
> +  test_pop_back ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    list.pop_back();
> +    expected = {&a, &b};
> +    verify_items (list, expected);
> +
> +    list.pop_back ();
> +    expected = {&a};
> +    verify_items (list, expected);
> +
> +    list.pop_back ();
> +    expected = {};
> +    verify_items (list, expected);
> +  }
> +
> +  static void
> +  test_erase ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    list.erase (list.iterator_to (b));
> +    expected = {&a, &c};
> +    verify_items (list, expected);
> +
> +    list.erase (list.iterator_to (c));
> +    expected = {&a};
> +    verify_items (list, expected);
> +
> +    list.erase (list.iterator_to (a));
> +    expected = {};
> +    verify_items (list, expected);
> +  }
> +
> +  static void
> +  test_clear ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    list.clear ();
> +    expected = {};
> +    verify_items (list, expected);
> +
> +    /* Verify idempotency.  */
> +    list.clear ();
> +    expected = {};
> +    verify_items (list, expected);
> +  }
> +
> +  static void
> +  test_clear_and_dispose ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    std::vector<const item_type *> expected;
> +    std::unordered_set<const item_type *> disposer_seen;
> +    int disposer_calls = 0;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    auto disposer = [&] (const item_type *item)
> +      {
> +	disposer_seen.insert (item);
> +	disposer_calls++;
> +      };
> +    list.clear_and_dispose (disposer);
> +
> +    expected = {};
> +    verify_items (list, expected);
> +    gdb_assert (disposer_calls == 3);
> +    gdb_assert (disposer_seen.find (&a) != disposer_seen.end ());
> +    gdb_assert (disposer_seen.find (&b) != disposer_seen.end ());
> +    gdb_assert (disposer_seen.find (&c) != disposer_seen.end ());
> +
> +    /* Verify idempotency.  */
> +    list.clear_and_dispose (disposer);
> +    gdb_assert (disposer_calls == 3);
> +  }
> +
> +  static void
> +  test_empty ()
> +  {
> +    item_type a ("a");
> +    ListType list;
> +
> +    gdb_assert (list.empty ());
> +    list.push_back (a);
> +    gdb_assert (!list.empty ());
> +    list.erase (list.iterator_to (a));
> +    gdb_assert (list.empty ());
> +  }
> +
> +  static void
> +  test_begin_end ()
> +  {
> +    item_type a ("a"), b ("b"), c ("c");
> +    ListType list;
> +    const ListType &clist = list;
> +
> +    list.push_back (a);
> +    list.push_back (b);
> +    list.push_back (c);
> +
> +    gdb_assert (&*list.begin () == &a);
> +    gdb_assert (&*list.cbegin () == &a);
> +    gdb_assert (&*clist.begin () == &a);
> +    gdb_assert (&*list.rbegin () == &c);
> +    gdb_assert (&*list.crbegin () == &c);
> +    gdb_assert (&*clist.rbegin () == &c);
> +
> +    /* At least check that they compile.  */
> +    list.end ();
> +    list.cend ();
> +    clist.end ();
> +    list.rend ();
> +    list.crend ();
> +    clist.end ();
> +  }
> +};
> +
> +template <typename ListType>
> +static void
> +test_intrusive_list ()
> +{
> +  intrusive_list_test<ListType> tests;
> +
> +  tests.test_move_constructor ();
> +  tests.test_move_assignment ();
> +  tests.test_swap ();
> +  tests.test_front_back ();
> +  tests.test_push_front ();
> +  tests.test_push_back ();
> +  tests.test_insert ();
> +  tests.test_pop_front ();
> +  tests.test_pop_back ();
> +  tests.test_erase ();
> +  tests.test_clear ();
> +  tests.test_clear_and_dispose ();
> +  tests.test_empty ();
> +  tests.test_begin_end ();
> +}
> +
> +static void
> +test_node_is_linked ()
> +{
> +  {
> +    item_with_base a ("a");
> +    item_with_base_list list;
> +
> +    gdb_assert (!a.is_linked ());
> +    list.push_back (a);
> +    gdb_assert (a.is_linked ());
> +    list.pop_back ();
> +    gdb_assert (!a.is_linked ());
> +  }
> +
> +  {
> +    item_with_member a ("a");
> +    item_with_member_list list;
> +
> +    gdb_assert (!a.node.is_linked ());
> +    list.push_back (a);
> +    gdb_assert (a.node.is_linked ());
> +    list.pop_back ();
> +    gdb_assert (!a.node.is_linked ());
> +  }
> +}
> +
> +static void
> +test_intrusive_list ()
> +{
> +  test_intrusive_list<item_with_base_list> ();
> +  test_intrusive_list<item_with_member_list> ();
> +  test_node_is_linked ();
> +}
> +
> +void _initialize_intrusive_list_selftests ();
> +void
> +_initialize_intrusive_list_selftests ()
> +{
> +  selftests::register_test
> +    ("intrusive_list", test_intrusive_list);
> +}
> diff --git a/gdbsupport/intrusive_list.h b/gdbsupport/intrusive_list.h
> new file mode 100644
> index 000000000000..8e98e5b2c1a5
> --- /dev/null
> +++ b/gdbsupport/intrusive_list.h
> @@ -0,0 +1,559 @@
> +/* Intrusive double linked list for GDB, the GNU debugger.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program 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 of the License, or
> +   (at your option) any later version.
> +
> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#ifndef GDBSUPPORT_INTRUSIVE_LIST_H
> +#define GDBSUPPORT_INTRUSIVE_LIST_H
> +
> +#define UNLINKED_VALUE ((T *) -1)
> +
> +/* A list node.  The elements put in an intrusive_list either inherit
> +   from this, or have a field of this type.  */
> +template<typename T>
> +struct intrusive_list_node
> +{
> +  bool is_linked () const
> +  {
> +    return next != UNLINKED_VALUE;
> +  }
> +
> +  T *next = UNLINKED_VALUE;
> +  T *prev = UNLINKED_VALUE;
> +};
> +
> +/* Follows a couple types used by intrusive_list as template parameter to find
> +   the intrusive_list_node for a given element.  One for lists where the
> +   elements inherit intrusive_list_node, and another for elements that keep the
> +   node as member field.  */
> +
> +/* For element types that inherit from intrusive_list_node.  */
> +
> +template<typename T>
> +struct intrusive_base_node
> +{
> +  static intrusive_list_node<T> *as_node (T *elem)
> +  { return elem; }
> +};
> +
> +/* For element types that keep the node as member field.  */
> +
> +template<typename T, intrusive_list_node<T> T::*MemberNode>
> +struct intrusive_member_node
> +{
> +  static intrusive_list_node<T> *as_node (T *elem)
> +  { return &(elem->*MemberNode); }
> +};
> +
> +/* Common code for forward and reverse iterators.  */
> +
> +template<typename T, typename AsNode, typename SelfType>
> +struct intrusive_list_base_iterator
> +{
> +  using self_type = SelfType;
> +  using iterator_category = std::bidirectional_iterator_tag;
> +  using value_type = T;
> +  using pointer = T *;
> +  using const_pointer = const T *;
> +  using reference = T &;
> +  using const_reference = const T &;
> +  using difference_type = ptrdiff_t;
> +  using size_type = size_t;
> +  using node_type = intrusive_list_node<T>;
> +
> +  /* Create an iterator pointing to ELEM.  */
> +  explicit intrusive_list_base_iterator (T *elem)
> +    : m_elem (elem)
> +  {}
> +
> +  /* Create a past-the-end iterator.  */
> +  intrusive_list_base_iterator ()
> +    : m_elem (nullptr)
> +  {}
> +
> +  reference operator* () const
> +  { return *m_elem; }
> +
> +  pointer operator-> () const
> +  { return m_elem; }
> +
> +  bool operator== (const self_type &other) const
> +  { return m_elem == other.m_elem; }
> +
> +  bool operator!= (const self_type &other) const
> +  { return m_elem != other.m_elem; }
> +
> +protected:
> +  static node_type *as_node (T *elem)
> +  { return AsNode::as_node (elem); }
> +
> +  /* A past-end-the iterator points to the list's head.  */
> +  pointer m_elem;
> +};
> +
> +/* Forward iterator for an intrusive_list.  */
> +
> +template<typename T, typename AsNode = intrusive_base_node<T>>
> +struct intrusive_list_iterator
> +  : public intrusive_list_base_iterator
> +	     <T, AsNode, intrusive_list_iterator<T, AsNode>>
> +{
> +  using base = intrusive_list_base_iterator
> +		 <T, AsNode, intrusive_list_iterator<T, AsNode>>;
> +  using self_type = typename base::self_type;
> +  using node_type = typename base::node_type;
> +
> +  /* Inherit constructor and M_NODE visibility from base.  */
> +  using base::base;
> +  using base::m_elem;
> +
> +  self_type &operator++ ()
> +  {
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->next;
> +    return *this;
> +  }
> +
> +  self_type operator++ (int)
> +  {
> +    self_type temp = *this;
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->next;
> +    return temp;
> +  }
> +
> +  self_type &operator-- ()
> +  {
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->prev;
> +    return *this;
> +  }
> +
> +  self_type operator-- (int)
> +  {
> +    self_type temp = *this;
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->prev;
> +    return temp;
> +  }
> +};
> +
> +/* Reverse iterator for an intrusive_list.  */
> +
> +template<typename T, typename AsNode = intrusive_base_node<T>>
> +struct intrusive_list_reverse_iterator
> +  : public intrusive_list_base_iterator
> +	     <T, AsNode, intrusive_list_reverse_iterator<T, AsNode>>
> +{
> +  using base = intrusive_list_base_iterator
> +		 <T, AsNode, intrusive_list_reverse_iterator<T, AsNode>>;
> +  using self_type = typename base::self_type;
> +
> +  /* Inherit constructor and M_NODE visibility from base.  */
> +  using base::base;
> +  using base::m_elem;
> +  using node_type = typename base::node_type;
> +
> +  self_type &operator++ ()
> +  {
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->prev;
> +    return *this;
> +  }
> +
> +  self_type operator++ (int)
> +  {
> +    self_type temp = *this;
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->prev;
> +    return temp;
> +  }
> +
> +  self_type &operator-- ()
> +  {
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->next;
> +    return *this;
> +  }
> +
> +  self_type operator-- (int)
> +  {
> +    self_type temp = *this;
> +    node_type *node = this->as_node (m_elem);
> +    m_elem = node->next;
> +    return temp;
> +  }
> +};
> +
> +/* An intrusive double-linked list.
> +
> +   T is the type of the elements to link.  The type T must either:
> +
> +    - inherit from intrusive_list_node<T>
> +    - have an intrusive_list_node<T> member
> +
> +   AsNode is a type with an as_node static method used to get a node from an
> +   element.  If elements inherit from intrusive_list_node<T>, use the default
> +   intrusive_base_node<T>.  If elements have an intrusive_list_node<T> member,
> +   use:
> +
> +     instrusive_member_node<T, &T::member>
> +
> +   where `member` is the name of the member.  */
> +
> +template <typename T, typename AsNode = intrusive_base_node<T>>
> +class intrusive_list
> +{
> +public:
> +  using value_type = T;
> +  using pointer = T *;
> +  using const_pointer = const T *;
> +  using reference = T &;
> +  using const_reference = const T &;
> +  using difference_type = ptrdiff_t;
> +  using size_type = size_t;
> +  using iterator = intrusive_list_iterator<T, AsNode>;
> +  using reverse_iterator = intrusive_list_reverse_iterator<T, AsNode>;
> +  using const_iterator = const intrusive_list_iterator<T, AsNode>;
> +  using const_reverse_iterator
> +    = const intrusive_list_reverse_iterator<T, AsNode>;
> +  using node_type = intrusive_list_node<T>;
> +
> +  intrusive_list () = default;
> +
> +  ~intrusive_list ()
> +  {
> +    clear ();
> +  }
> +
> +  intrusive_list (intrusive_list &&other)
> +    : m_front (other.m_front),
> +      m_back (other.m_back)
> +  {
> +    other.m_front = nullptr;
> +    other.m_back = nullptr;
> +  }
> +
> +  intrusive_list &operator= (intrusive_list &&other)
> +  {
> +    m_front = other.m_front;
> +    m_back = other.m_back;
> +    other.m_front = nullptr;
> +    other.m_back = nullptr;
> +
> +    return *this;
> +  }
> +
> +  void swap (intrusive_list &other)
> +  {
> +    std::swap (m_front, other.m_front);
> +    std::swap (m_back, other.m_back);
> +  }
> +
> +  iterator iterator_to (reference value)
> +  {
> +    return iterator (&value);
> +  }
> +
> +  const_iterator iterator_to (const_reference value)
> +  {
> +    return const_iterator (&value);
> +  }
> +
> +  reference front ()
> +  {
> +    gdb_assert (!this->empty ());
> +    return *m_front;
> +  }
> +
> +  const_reference front () const
> +  {
> +    gdb_assert (!this->empty ());
> +    return *m_front;
> +  }
> +
> +  reference back ()
> +  {
> +    gdb_assert (!this->empty ());
> +    return *m_back;
> +  }
> +
> +  const_reference back () const
> +  {
> +    gdb_assert (!this->empty ());
> +    return *m_back;
> +  }
> +
> +  void push_front (reference elem)
> +  {
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +
> +    gdb_assert (elem_node->next == UNLINKED_VALUE);
> +    gdb_assert (elem_node->prev == UNLINKED_VALUE);
> +
> +    if (this->empty ())
> +      this->push_empty (elem);
> +    else
> +      this->push_front_non_empty (elem);
> +  }
> +
> +  void push_back (reference elem)
> +  {
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +
> +    gdb_assert (elem_node->next == UNLINKED_VALUE);
> +    gdb_assert (elem_node->prev == UNLINKED_VALUE);
> +
> +    if (this->empty ())
> +      this->push_empty (elem);
> +    else
> +      this->push_back_non_empty (elem);
> +  }
> +
> +  /* Inserts ELEM before POS.  */
> +  void insert (const_iterator pos, reference elem)
> +  {
> +    if (this->empty ())
> +      return this->push_empty (elem);
> +
> +    if (pos == this->begin ())
> +      return this->push_front_non_empty (elem);
> +
> +    if (pos == this->end ())
> +      return this->push_back_non_empty (elem);
> +
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +    T *pos_elem = &*pos;
> +    intrusive_list_node<T> *pos_node = as_node (pos_elem);
> +    T *prev_elem = pos_node->prev;
> +    intrusive_list_node<T> *prev_node = as_node (prev_elem);
> +
> +    gdb_assert (elem_node->next == UNLINKED_VALUE);
> +    gdb_assert (elem_node->prev == UNLINKED_VALUE);
> +
> +    elem_node->prev = prev_elem;
> +    prev_node->next = &elem;
> +    elem_node->next = pos_elem;
> +    pos_node->prev = &elem;
> +  }
> +
> +  void pop_front ()
> +  {
> +    gdb_assert (!this->empty ());
> +    erase_element (*m_front);
> +  }
> +
> +  void pop_back ()
> +  {
> +    gdb_assert (!this->empty ());
> +    erase_element (*m_back);
> +  }
> +
> +private:
> +  /* Push ELEM in the list, knowing the list is empty.  */
> +  void push_empty (T &elem)
> +  {
> +    gdb_assert (this->empty ());
> +
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +
> +    gdb_assert (elem_node->next == UNLINKED_VALUE);
> +    gdb_assert (elem_node->prev == UNLINKED_VALUE);
> +
> +    m_front = &elem;
> +    m_back = &elem;
> +    elem_node->prev = nullptr;
> +    elem_node->next = nullptr;
> +  }
> +
> +  /* Push ELEM at the front of the list, knowing the list is not empty.  */
> +  void push_front_non_empty (T &elem)
> +  {
> +    gdb_assert (!this->empty ());
> +
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +    intrusive_list_node<T> *front_node = as_node (m_front);
> +
> +    gdb_assert (elem_node->next == UNLINKED_VALUE);
> +    gdb_assert (elem_node->prev == UNLINKED_VALUE);
> +
> +    elem_node->next = m_front;
> +    front_node->prev = &elem;
> +    elem_node->prev = nullptr;
> +    m_front = &elem;
> +  }
> +
> +  /* Push ELEM at the back of the list, knowing the list is not empty.  */
> +  void push_back_non_empty (T &elem)
> +  {
> +    gdb_assert (!this->empty ());
> +
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +    intrusive_list_node<T> *back_node = as_node (m_back);
> +
> +    gdb_assert (elem_node->next == UNLINKED_VALUE);
> +    gdb_assert (elem_node->prev == UNLINKED_VALUE);
> +
> +    elem_node->prev = m_back;
> +    back_node->next = &elem;
> +    elem_node->next = nullptr;
> +    m_back = &elem;
> +  }
> +
> +  void erase_element (T &elem)
> +  {
> +    intrusive_list_node<T> *elem_node = as_node (&elem);
> +
> +    gdb_assert (elem_node->prev != UNLINKED_VALUE);
> +    gdb_assert (elem_node->next != UNLINKED_VALUE);
> +
> +    if (m_front == &elem)
> +      {
> +	gdb_assert (elem_node->prev == nullptr);
> +	m_front = elem_node->next;
> +      }
> +    else
> +      {
> +	gdb_assert (elem_node->prev != nullptr);
> +	intrusive_list_node<T> *prev_node = as_node (elem_node->prev);
> +	prev_node->next = elem_node->next;
> +      }
> +
> +    if (m_back == &elem)
> +      {
> +	gdb_assert (elem_node->next == nullptr);
> +	m_back = elem_node->prev;
> +      }
> +    else
> +      {
> +	gdb_assert (elem_node->next != nullptr);
> +	intrusive_list_node<T> *next_node = as_node (elem_node->next);
> +	next_node->prev = elem_node->prev;
> +      }
> +
> +    elem_node->next = UNLINKED_VALUE;
> +    elem_node->prev = UNLINKED_VALUE;
> +  }
> +
> +public:
> +  /* Remove the element pointed by I from the list.  The element
> +     pointed by I is not destroyed.  */
> +  iterator erase (const_iterator i)
> +  {
> +    iterator ret = i;
> +    ++ret;
> +
> +    erase_element (*i);
> +
> +    return ret;
> +  }
> +
> +  /* Erase all the elements.  The elements are not destroyed.  */
> +  void clear ()
> +  {
> +    while (!this->empty ())
> +      pop_front ();
> +  }
> +
> +  /* Erase all the elements.  Disposer::operator()(pointer) is called
> +     for each of the removed elements.  */
> +  template<typename Disposer>
> +  void clear_and_dispose (Disposer disposer)
> +  {
> +    while (!this->empty ())
> +      {
> +	pointer p = &front ();
> +	pop_front ();
> +	disposer (p);
> +      }
> +  }
> +
> +  bool empty () const
> +  {
> +    return m_front == nullptr;
> +  }
> +
> +  iterator begin () noexcept
> +  {
> +    return iterator (m_front);
> +  }
> +
> +  const_iterator begin () const noexcept
> +  {
> +    return const_iterator (m_front);
> +  }
> +
> +  const_iterator cbegin () const noexcept
> +  {
> +    return const_iterator (m_front);
> +  }
> +
> +  iterator end () noexcept
> +  {
> +    return {};
> +  }
> +
> +  const_iterator end () const noexcept
> +  {
> +    return {};
> +  }
> +
> +  const_iterator cend () const noexcept
> +  {
> +    return {};
> +  }
> +
> +  reverse_iterator rbegin () noexcept
> +  {
> +    return reverse_iterator (m_back);
> +  }
> +
> +  const_reverse_iterator rbegin () const noexcept
> +  {
> +    return const_reverse_iterator (m_back);
> +  }
> +
> +  const_reverse_iterator crbegin () const noexcept
> +  {
> +    return const_reverse_iterator (m_back);
> +  }
> +
> +  reverse_iterator rend () noexcept
> +  {
> +    return {};
> +  }
> +
> +  const_reverse_iterator rend () const noexcept
> +  {
> +    return {};
> +  }
> +
> +  const_reverse_iterator crend () const noexcept
> +  {
> +    return {};
> +  }
> +
> +private:
> +  static node_type *as_node (T *elem)
> +  {
> +    return AsNode::as_node (elem);
> +  }
> +
> +  T *m_front = nullptr;
> +  T *m_back = nullptr;
> +};
> +
> +#endif /* GDBSUPPORT_INTRUSIVE_LIST_H */
> diff --git a/gdbsupport/reference-to-pointer-iterator.h b/gdbsupport/reference-to-pointer-iterator.h
> new file mode 100644
> index 000000000000..7303fa4a04ae
> --- /dev/null
> +++ b/gdbsupport/reference-to-pointer-iterator.h
> @@ -0,0 +1,79 @@
> +/* An iterator wrapper that yields pointers instead of references.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program 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 of the License, or
> +   (at your option) any later version.
> +
> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#ifndef GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H
> +#define GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H
> +
> +/* Wrap an iterator that yields references to objects so that it yields
> +   pointers to objects instead.
> +
> +   This is useful for example to bridge the gap between iterators on intrusive
> +   lists, which yield references, and the rest of GDB, which for legacy reasons
> +   expects to iterate on pointers.  */
> +
> +template <typename IteratorType>
> +struct reference_to_pointer_iterator
> +{
> +  using self_type = reference_to_pointer_iterator;
> +  using value_type = typename IteratorType::value_type *;
> +  using reference = typename IteratorType::value_type *&;
> +  using pointer = typename IteratorType::value_type **;
> +  using iterator_category = typename IteratorType::iterator_category;
> +  using difference_type = typename IteratorType::difference_type;
> +
> +  /* Construct a reference_to_pointer_iterator, passing args to the underyling
> +     iterator.  */
> +  template <typename... Args>
> +  reference_to_pointer_iterator (Args &&...args)
> +    : m_it (std::forward<Args> (args)...)
> +  {}
> +
> +  /* Create a past-the-end iterator.
> +
> +     Assumes that default-constructing an underlying iterator creates a
> +     past-the-end iterator.  */
> +  reference_to_pointer_iterator ()
> +  {}
> +
> +  /* Need these as the variadic constructor would be a better match
> +     otherwise.  */
> +  reference_to_pointer_iterator (reference_to_pointer_iterator &) = default;
> +  reference_to_pointer_iterator (const reference_to_pointer_iterator &) = default;
> +  reference_to_pointer_iterator (reference_to_pointer_iterator &&) = default;
> +
> +  value_type operator* () const
> +  { return &*m_it; }
> +
> +  self_type &operator++ ()
> +  {
> +    ++m_it;
> +    return *this;
> +  }
> +
> +  bool operator== (const self_type &other) const
> +  { return m_it == other.m_it; }
> +
> +  bool operator!= (const self_type &other) const
> +  { return m_it != other.m_it; }
> +
> +private:
> +  /* The underlying iterator.  */
> +  IteratorType m_it;
> +};
> +
> +#endif /* GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H */
> -- 
> 2.32.0
> 

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-06-22 23:13   ` Lancelot SIX
@ 2021-06-23  0:48     ` Simon Marchi
  0 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-06-23  0:48 UTC (permalink / raw)
  To: Lancelot SIX, Pedro Alves; +Cc: gdb-patches, Simon Marchi

>> +class IntrusiveListPrinter:
>> +    """Print a struct intrusive_list."""
>> +
>> +    def __init__(self, val):
>> +        self._val = val
>> +
>> +        # Type of linked items.
>> +        self._item_type = self._val.type.template_argument(0)
>> +        self._node_ptr_type = gdb.lookup_type(
>> +            f"intrusive_list_node<{self._item_type.tag}>"
> 
> Hi,
> 
> I do not know what are the compatibility constraints / minimum python
> version required, but f-string do require at least python-3.6.  This
> covers all still supported python versions, but there might be distros
> out-there that are not there yet (there are a few of those in this
> file).

Huh... right.  It's not clear what we aim to support, I'd like if it was
clearer.  But we still support build against Python 2.7, so it would
make sense to try to be compatible with it.  I'll switch to using
`"...".format(...)`.

>> +        ).pointer()
>> +
>> +        # Type of value -> node converter.
>> +        self._conv_type = self._val.type.template_argument(1)
>> +
>> +        if self._uses_member_node():
>> +            # The second template argument of intrusive_member_node is a member
>> +            # pointer value.  Its value is the offset of the node member in the
>> +            # enclosing type.
>> +            member_node_ptr = self._conv_type.template_argument(1)
>> +            member_node_ptr = member_node_ptr.cast(gdb.lookup_type("int"))
>> +            self._member_node_offset = int(member_node_ptr)
>> +
>> +            # This is only needed in _as_node_ptr if using a member node.  Look it
>> +            # up here so we only do it once.
>> +            self._char_ptr_type = gdb.lookup_type("char").pointer()
>> +
>> +    def display_hint(self):
>> +        return "array"
>> +
>> +    # Return True if the list items use a node as a member.  Return False if
>> +    # they use a node as a base class.
>> +    def _uses_member_node(self):
> 
> The documentation for the function should probably go as a doc string,
> not a comment before the function:
> 
>     def _uses_member_node(self):
>         """ Return True if the list items use a node as a member.  Return False
> 	if they use a node as a base class.
> 	"""
> 
> The same remark stands for the other method in this file.

Done.

>> +        if self._conv_type.name.startswith("intrusive_member_node<"):
>> +            return True
>> +        elif self._conv_type.name.startswith("intrusive_base_node<"):
>> +            return False
>> +        else:
>> +            raise RuntimeError(
>> +                f"Unexpected intrusive_list value -> node converter type: {self._conv_type.name}"
>> +            )
>> +
>> +    def to_string(self):
>> +        s = f"intrusive list of {self._item_type}"
>> +
>> +        if self._uses_member_node():
>> +            node_member = self._conv_type.template_argument(1)
>> +            s += f", linked through {node_member}"
>> +
>> +        return s
>> +
>> +    # Given ELEM_PTR, a pointer to a list element, return a pointer to the
>> +    # corresponding intrusive_list_node.
>> +    def _as_node_ptr(self, elem_ptr):
>> +        assert elem_ptr.type.code == gdb.TYPE_CODE_PTR
>> +
>> +        if self._uses_member_node():
>> +            # Node as a memer: add the member node offset from to the element's
> 
> s/memer/member/ ?

Fixed.

I integrated the changes in my local tree.

Simon

PS: when you review, don't hesitate to trim the irrelevant parts of the
diff, it will make your comments easier to find and harder to miss.  I
almost missed the last one, especially because it was after your
signature!  Still, thank you very much for looking at the various
patches on the list, it's very appreciated.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter
  2021-06-22 16:56 ` [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter Simon Marchi
@ 2021-07-05 15:41   ` Pedro Alves
  2021-07-06 19:16     ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:41 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:56 p.m., Simon Marchi via Gdb-patches wrote:
> I was always a bit confused by next_adapter, because it kind of mixes
> the element type and the iterator type.  In reality, it is not much more
> than a class that wraps two iterators (begin and end).  However, it
> assumes that:
> 
>  - you can construct the begin iterator by passing a pointer to the
>    first element of the iterable
>  - you can default-construct iterator to make the end iterator
> 
> I think that by generalizing it a little bit, we can re-use it at more
> places.
> 
> Rename it to "iterator_range".  I think it describes a bit better: it's
> a range made by wrapping a begin and end iterator.  Move it to its own
> file, since it's not related to next_iterator anymore.
> 
> iterator_range has two constructors.  The variadic one, where arguments
> are forwarded to construct the underlying begin iterator.  The end
> iterator is constructed through default construction.  This is a
> generalization of what we have today.
> 
> There is another constructor which receives already constructed begin
> and end iterators, useful if the end iterator can't be obtained by
> default-construction.  Or, if you wanted to make a range that does not
> end at the end of the container, you could pass any iterator as the
> "end".
> 
> This generalization allows removing some "range" classes, like
> all_inferiors_range.  These classes existed only to pass some arguments
> when constructing the begin iterator.  With iterator_range, those same
> arguments are passed to the iterator_range constructed and then
> forwarded to the constructed begin iterator.
> 
> There is a small functional difference in how iterator_range works
> compared to next_adapter.  next_adapter stored the pointer it received
> as argument and constructeur an iterator in the `begin` method.
> iterator_range constructs the begin iterator and stores it as a member.
> Its `begin` method returns a copy of that iterator.
> 
> With just iterator_range, uses of next_adapter<foo> would be replaced
> with:
> 
>   using foo_iterator = next_iterator<foo>;
>   using foo_range = iterator_range<foo_iterator>;
> 
> However, I added a `next_range` wrapper as a direct replacement for
> next_adapter<foo>.  IMO, next_range is a slightly better name than
> next_adapter.
> 
> The rest of the changes are applications of this new class.

LGTM.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-06-22 16:56 ` [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it Simon Marchi
  2021-06-22 23:13   ` Lancelot SIX
@ 2021-07-05 15:44   ` Pedro Alves
  2021-07-06 19:38     ` Simon Marchi
  1 sibling, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:44 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: Simon Marchi

Hi!

This looks mostly good to me, though I'm biased...

On 2021-06-22 5:56 p.m., Simon Marchi wrote:
> From: Pedro Alves <pedro@palves.net>
> 
> GDB currently has several objects that are put in a singly linked list,
> by having the object's type have a "next" pointer directly.  For
> example, struct thread_info and struct inferior.  Because these are
> simply-linked lists, and we don't keep track of a "tail" pointer, when
> we want to append a new element on the list, we need to walk the whole
> list to find the current tail.  It would be nice to get rid of that
> walk.  Removing elements from such lists also requires a walk, to find
> the "previous" position relative to the element being removed.  To
> eliminate the need for that walk, we could make those lists
> doubly-linked, by adding a "prev" pointer alongside "next".  It would be
> nice to avoid the boilerplace associated with maintaining such a list

boilerplace -> boilerplate

> manually, though.  That is what the new intrusive_list type addresses.

...

> 
> Unlike Boost's implementation, ours is not a circular list.  An earlier
> version of the patch was circular: the instrusive_list type included an

instrusive_list -> intrusive_list

> intrusive_list_node "head".  In this design, a node contained pointers
> to the previous and next nodes, not the previous and next elements.
> This wasn't great for when debugging GDB with GDB, as it was difficult
> to get from a pointer to the node to a pointer to the element.  With the
> design proposed in this patch, nodes contain pointers to the previous
> and next elements, making it easy to traverse the list by hand and
> inspect each element.
> 

...

> 
> Add a Python pretty-printer, to help inspecting intrusive lists when
> debugging GDB with GDB.  Here's an example of the output:
> 
>     (top-gdb) p current_inferior_.m_obj.thread_list
>     $1 = intrusive list of thread_info = {0x61700002c000, 0x617000069080, 0x617000069400, 0x61700006d680, 0x61700006eb80}
> 

Did you find this output helpful in practice?  Printing the whole object is
for sure too much, but I wonder whether printing the thread id, and ptid and
perhaps the thread state wouldn't be more helpful, like:

 $1 = intrusive list of thread_info = {
   {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
   {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
   {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
 }

> It's not possible with current master, but with this patch [1] that I
> hope will be merged eventually, it's possible to index the list and
> access the pretty-printed value's children:
> 
>     (top-gdb) p current_inferior_.m_obj.thread_list[1]
>     $2 = (thread_info *) 0x617000069080
>     (top-gdb) p current_inferior_.m_obj.thread_list[1].ptid
>     $3 = {
>       m_pid = 406499,
>       m_lwp = 406503,
>       m_tid = 0
>     }

I guess in practice I'd always want to print the list
with "set print array-indexes on".  Otherwise, how would you know
which index to pass to []?  And still, with just pointers/addresses
in the output, I'm not sure I'd easily know which index to pass:

 $1 = intrusive list of struct thread_info = {[0] = 0x5555564d0f60, [1] = 0x5555572ad640, [2] = 0x5555572ad9f0}

?

I mean, one can eyeball for the right entry based on thread_info
pointer/address, but if one already has the pointer/address handy, then
one wouldn't need to search for the entry in the thread list in the first
place, right?

It does seem to make it a bit easier to iterate over the whole list
printing each element, easier than following the next->next->next
pointers.  Was that the use case you had in mind?

With the alternative output suggested above, we'd have:

 $1 = intrusive list of thread_info = {
   [0] = {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
   [1] = {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
   [2] = {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
 }

Off hand, I think I'd find this more useful.  I'm assuming that
using [] with Andrew's patch would still work the same way.

I guess this would be implemented by passing some customization
object or function to the pretty printer, that it would call to
print each element.  If no such customization is passed, then
it would just print what you have.  So I guess this could always
be done separately...

> +++ b/gdb/unittests/intrusive_list-selftests.c

Yay, unit tests!  Thanks a lot for writing them.

> @@ -0,0 +1,734 @@
> +/* Tests fpr intrusive double linked list for GDB, the GNU debugger.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program 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 of the License, or
> +   (at your option) any later version.
> +
> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +
> +#include "gdbsupport/intrusive_list.h"
> +#include "gdbsupport/selftest.h"
> +#include <unordered_set>
> +
> +/* An item type using intrusive_list_node by inheriting from it and its
> +   corresponding list type.  Put another base before intrusive_list_node
> +   so that a pointer to the node != a pointer to the item.  */
> +
> +struct other_base
> +{
> +  int n = 1;
> +};
> +
> +struct item_with_base : public other_base,
> +			public intrusive_list_node<item_with_base>
> +{
> +  item_with_base (const char *name)

explicit

> +    : name (name)
> +  {}
> +
> +  const char *const name;
> +};
> +
> +using item_with_base_list = intrusive_list<item_with_base>;
> +
> +/* An item type using intrusive_list_node as a field and its corresponding
> +   list type.  Put the other field before the node, so that a pointer to the
> +   node != a pointer to the item.  */
> +
> +struct item_with_member
> +{
> +  item_with_member (const char *name)

explicit

> diff --git a/gdbsupport/intrusive_list.h b/gdbsupport/intrusive_list.h
> new file mode 100644
> index 000000000000..8e98e5b2c1a5
> --- /dev/null
> +++ b/gdbsupport/intrusive_list.h
> @@ -0,0 +1,559 @@
> +/* Intrusive double linked list for GDB, the GNU debugger.
> +   Copyright (C) 2021 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program 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 of the License, or
> +   (at your option) any later version.
> +
> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#ifndef GDBSUPPORT_INTRUSIVE_LIST_H
> +#define GDBSUPPORT_INTRUSIVE_LIST_H
> +
> +#define UNLINKED_VALUE ((T *) -1)

UNLINKED_VALUE seems like a too-generic name for a macro.
I think it'd be better to add some prefix to minimize
potential for conflict.  INTR_LIST_UNLINKED_VALUE
or some such.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 03/11] gdb: make inferior_list use intrusive_list
  2021-06-22 16:56 ` [PATCH 03/11] gdb: make inferior_list use intrusive_list Simon Marchi
@ 2021-07-05 15:44   ` Pedro Alves
  2021-07-14  6:34     ` Tom de Vries
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:44 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: Simon Marchi

On 2021-06-22 5:56 p.m., Simon Marchi wrote:
> From: Pedro Alves <pedro@palves.net>
> 
> Change inferior_list, the global list of inferiors, to use
> intrusive_list.  I think most other changes are somewhat obvious
> fallouts from this change.
> 
> There is a small change in behavior in scoped_mock_context.  Before this
> patch, constructing a scoped_mock_context would replace the whole
> inferior list with only the new mock inferior.  Tests using two
> scoped_mock_contexts therefore needed to manually link the two inferiors
> together, as the second scoped_mock_context would bump the first mock
> inferior from the thread list.  With this patch, a scoped_mock_context
> adds its mock inferior to the inferior list on construction, and removes
> it on destruction.  This means that tests run with mock inferiors in the
> inferior list in addition to any pre-existing inferiors (there is always
> at least one).  There is no possible pid clash problem, since each
> scoped mock inferior uses its own process target, and pids are per
> process target.

LGTM, FWIW...

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 04/11] gdb: use intrusive list for step-over chain
  2021-06-22 16:56 ` [PATCH 04/11] gdb: use intrusive list for step-over chain Simon Marchi
@ 2021-07-05 15:45   ` Pedro Alves
  2021-07-06 20:59     ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:45 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:56 p.m., Simon Marchi via Gdb-patches wrote:

> 	* reference-to-pointer-iterator.h (struct
> 	reference_to_pointer_iterator): Add default assignment
> 	operators.

It's not immediately obvious why this is needed now, and wasn't before.
It should perhaps be mentioned in the commit log.  Especially if you
decide to discard the ChangeLog entry.

Otherwise LGTM.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 05/11] gdb: add setter / getter for thread_info resumed state
  2021-06-22 16:56 ` [PATCH 05/11] gdb: add setter / getter for thread_info resumed state Simon Marchi
@ 2021-07-05 15:45   ` Pedro Alves
  0 siblings, 0 replies; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:45 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:56 p.m., Simon Marchi via Gdb-patches wrote:
> A following patch will want to do things when a thread's resumed state
> changes.  Make the `resumed` field private (renamed to `m_resumed`) and
> add a getter and a setter for it.  The following patch in question will
> therefore be able to add some code to the setter.
> 
> gdb/ChangeLog:
> 
> 	* gdbthread.h (struct thread_info) <resumed>: Rename to...
> 	<m_resumed>: This.  Change all uses to use the resumed and
> 	set_resumed methods.
> 	<resumed, set_resumed>: New methods.

OK.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 06/11] gdb: make thread_info::suspend private, add getters / setters
  2021-06-22 16:56 ` [PATCH 06/11] gdb: make thread_info::suspend private, add getters / setters Simon Marchi
@ 2021-07-05 15:45   ` Pedro Alves
  0 siblings, 0 replies; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:45 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:56 p.m., Simon Marchi via Gdb-patches wrote:
> A following patch will want to take some action when a pending wait
> status is set on or removed from a thread.  Add a getter and a setter on
> thread_info for the pending waitstatus, so that we can add some code in
> the setter later.
> 
> The thing is, the pending wait status field is in the
> thread_suspend_state, along with other fields that we need to backup
> before and restore after the thread does an inferior function call.
> Therefore, make the thread_suspend_state member private
> (thread_info::suspend becomes thread_info::m_suspend), and add getters /
> setters for all of its fields:
> 
>  - pending wait status
>  - stop signal
>  - stop reason
>  - stop pc
> 
> For the pending wait status, add the additional has_pending_waitstatus
> and clear_pending_waitstatus methods.
> 
> I think this makes the thread_info interface a bit nicer, because we
> now access the fields as:
> 
>   thread->stop_pc ()
> 
> rather than
> 
>   thread->suspend.stop_pc
> 
> The stop_pc field being in the `suspend` structure is an implementation
> detail of thread_info that callers don't need to be aware of.
> 
> For the backup / restore of the thread_suspend_state structure, add
> save_suspend_to and restore_suspend_from methods.  You might wonder why
> `save_suspend_to`, as opposed to a simple getter like
> 
>   thread_suspend_state &suspend ();
> 
> I want to make it clear that this is to be used only for backing up and
> restoring the suspend state, _not_ to access fields like:
> 
>   thread->suspend ()->stop_pc
> 
> Adding some getters / setters allows adding some assertions.  I find
> that this helps understand how things are supposed to work.  Add:
> 
>  - When getting the pending status (pending_waitstatus method), ensure
>    that there is a pending status.
>  - When setting a pending status (set_pending_waitstatus method), ensure
>    there is no pending status.
> 
> There is one case I found where this wasn't true - in
> remote_target::process_initial_stop_replies - which needed adjustments
> to respect that contract.  I think it's because
> process_initial_stop_replies is kind of (ab)using the
> thread_info::suspend::waitstatus to store some statuses temporarily, for
> its internal use (statuses it doesn't intent on leaving pending).
> 
> process_initial_stop_replies pulls out stop replies received during the
> initial connection using target_wait.  It always stores the received
> event in `evthread->suspend.waitstatus`.  But it only sets
> waitstatus_pending_p, if it deems the event interesting enough to leave
> pending, to be reported to the core:
> 
>       if (ws.kind != TARGET_WAITKIND_STOPPED
> 	  || ws.value.sig != GDB_SIGNAL_0)
> 	evthread->suspend.waitstatus_pending_p = 1;
> 
> It later uses this flag a bit below, to choose which thread to make the
> "selected" one:
> 
>       if (selected == NULL
> 	  && thread->suspend.waitstatus_pending_p)
> 	selected = thread;
> 
> And ultimately that's used if the user-visible mode is all-stop, so that
> we print the stop for that interesting thread:
> 
>   /* In all-stop, we only print the status of one thread, and leave
>      others with their status pending.  */
>   if (!non_stop)
>     {
>       thread_info *thread = selected;
>       if (thread == NULL)
> 	thread = lowest_stopped;
>       if (thread == NULL)
> 	thread = first;
> 
>       print_one_stopped_thread (thread);
>     }
> 
> But in any case (all-stop or non-stop), print_one_stopped_thread needs
> to access the waitstatus value of these threads that don't have a
> pending waitstatus (those that had TARGET_WAITKIND_STOPPED +
> GDB_SIGNAL_0).  This doesn't work with the assertions I've
> put.
> 
> So, change the code to only set the thread's wait status if it is an
> interesting one that we are going to leave pending.  If the thread
> stopped due to a non-interesting event (TARGET_WAITKIND_STOPPED +
> GDB_SIGNAL_0), don't store it.  Adjust print_one_stopped_thread to
> understand that if a thread has no pending waitstatus, it's because it
> stopped with TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.
> 
> The call to set_last_target_status also uses the pending waitstatus.
> However, given that the pending waitstatus for the thread may have been
> cleared in print_one_stopped_thread (and that there might not even be a
> pending waitstatus in the first place, as explained above), it is no
> longer possible to do it at this point.  To fix that, move the call to
> set_last_target_status in print_one_stopped_thread.  I think this will
> preserve the existing behavior, because set_last_target_status is
> currently using the current thread's wait status.  And the current
> thread is the last one for which print_one_stopped_thread is called.  So
> by calling set_last_target_status in print_one_stopped_thread, we'll get
> the same result.  set_last_target_status will possibly be called
> multiple times, but only the last call will matter.  It just means
> possibly more calls to set_last_target_status, but those are cheap.
> 
> gdb/ChangeLog:
> 
> 	* gdbthread.h (class thread_info) <save_suspend_to,
> 	restore_suspend_from, stop_pc, set_stop_pc,
> 	has_pending_waitstatus, pending_waitstatus,
> 	set_pending_waitstatus, clear_pending_waitstatus, stop_signal,
> 	set_stop_signal, stop_reason, set_stop_reason): New.
> 	<suspend>: Rename to...
> 	<m_suspend>: ... this, make private.  Adjust all uses to use one
> 	of the new methods above.
> 	* thread.c (thread_info::set_pending_waitstatus,
> 	thread_info::clear_pending_waitstatus): New.
> 	* remote.c (class remote_target) <print_one_stopped_thread>: New
> 	method.
> 	(print_one_stopped_thread): Rename to...
> 	(remote_target::print_one_stopped_thread): ... this.  Assume
> 	that if thread has no pending waitstatus, it's
> 	TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.  Call
> 	set_last_target_status.
> 	(remote_target::process_initial_stop_replies): Don't set pending
> 	waitstatus if TARGET_WAITKIND_STOPPED + GDB_SIGNAL_0.  Don't
> 	call set_last_target_status.
> 	(is_pending_fork_parent): Constify param.
> 	(thread_pending_fork_status): Constify return.
> 	(is_pending_fork_parent_thread): Adjust.
> 	(remote_target::remove_new_fork_children): Adjust.

OK.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-06-22 16:57 ` [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status Simon Marchi
@ 2021-07-05 15:51   ` Pedro Alves
  2021-07-06 21:25     ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:51 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

Hi!

This LGTM.  Some comments, typos and minor things to address below.

On 2021-06-22 5:57 p.m., Simon Marchi via Gdb-patches wrote:

> In set_thread_exited, we try to remove the thread from the list, because
> keeping an exited thread in that list would make no sense (especially if
> the thread is freed).  My first implementation assumed that a process
> stratum target was always present when set_thread_exited is called.
> That's however, not the case: in some cases, targets unpush themselves
> from an inferior and then call "exit_inferior", which exits all the
> threads.  If the target is unpushed before set_thread_exited is called
> on the threads, it means we could mistakenly leave some threads in the
> list.  I tried to see how hard it would be to make it such that targets
> have to exit all threads before unpushing themselves from the inferior
> (that would seem logical to me, we don't want threads belonging to an
> inferior that has no process target).  That seem quite difficult and not
> worth the time.  Instead, I changed inferior::unpush_target to remove an
> threads of that inferior from the list.

"remove an threads" -> "remove all threads" ?

> 
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 5ea08a13ee5f..47d7f40eaa08 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -296,8 +296,7 @@ class thread_info : public refcounted_object,
>    bool resumed () const
>    { return m_resumed; }
>  
> -  void set_resumed (bool resumed)
> -  { m_resumed = resumed; }
> +  void set_resumed (bool resumed);
>  
>    /* Frontend view of the thread state.  Note that the THREAD_RUNNING/
>       THREAD_STOPPED states are different from EXECUTING.  When the
> @@ -470,6 +469,10 @@ class thread_info : public refcounted_object,
>       linked.  */
>    intrusive_list_node<thread_info> step_over_list_node;
>  
> +  /* Node for list of threads that are resumed and have a pending wait
> +     status.  */

Maybe mention that all threads in list list belong to the same
process_stratum_target ?

> +  intrusive_list_node<thread_info> resumed_with_pending_wait_status_node;
> +


> --- a/gdb/inferior.c
> +++ b/gdb/inferior.c
> @@ -89,6 +89,25 @@ inferior::inferior (int pid_)
>    m_target_stack.push (get_dummy_target ());
>  }
>  
> +/* See inferior.h.  */
> +
> +int
> +inferior::unpush_target (struct target_ops *t)
> +{
> +  /* If unpushing the process stratum target while threads exists, ensure that

"threads exists" -> "threads exist"

> +     we don't leave any threads of this inferior in the target's "resumed with
> +     pending wait status" list.  */
> +  if (t->stratum () == process_stratum)
> +    {
> +      process_stratum_target *proc_target = as_process_stratum_target (t);
> +
> +      for (thread_info *thread : this->non_exited_threads ())
> +	proc_target->maybe_remove_resumed_with_pending_wait_status (thread);

Note the target_pid_to_str call inside maybe_remove_resumed_with_pending_wait_status
adds back a dependency on current_inferior.

> +    }
> +
> +  return m_target_stack.unpush (t);
> +}
> +


> --- a/gdb/process-stratum-target.c
> +++ b/gdb/process-stratum-target.c
> @@ -106,6 +106,40 @@ process_stratum_target::follow_exec (inferior *follow_inf, ptid_t ptid,
>  
>  /* See process-stratum-target.h.  */
>  
> +void
> +process_stratum_target::maybe_add_resumed_with_pending_wait_status
> +  (thread_info *thread)
> +{
> +  gdb_assert (!thread->resumed_with_pending_wait_status_node.is_linked ());
> +
> +  if (thread->resumed () && thread->has_pending_waitstatus ())
> +    {
> +      infrun_debug_printf ("adding to resumed threads with event list: %s",
> +			   target_pid_to_str (thread->ptid).c_str ());

This here too.  Not 100% sure this target call is always done
with the right target stack selected.

> +      m_resumed_with_pending_wait_status.push_back (*thread);
> +    }
> +}
> +
> +/* See process-stratum-target.h.  */
> +

> +
> +private:
> +  /* List of threads managed by this target which simultaneously are resumed
> +     and have a pending wait status.  */

I'd suggest expanding this comment a little to mention this
is done for optimization reasons, to avoid walking
thread lists, something like that.  Or maybe say that in
the thread_info node.  Or both places.

> +  thread_info_resumed_with_pending_wait_status_list
> +    m_resumed_with_pending_wait_status;
>  };
>  
>  /* Downcast TARGET to process_stratum_target.  */
> diff --git a/gdb/thread.c b/gdb/thread.c
> index 289d33c74c3b..26974e1b8cbc 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -188,6 +188,10 @@ set_thread_exited (thread_info *tp, bool silent)
>  
>    if (tp->state != THREAD_EXITED)
>      {
> +      process_stratum_target *proc_target = tp->inf->process_target ();
> +      if (proc_target != nullptr)

I think this check needs a comment.

> +	proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
> +
>        gdb::observers::thread_exit.notify (tp, silent);
>  
>        /* Tag it as exited.  */
> @@ -295,6 +299,29 @@ thread_info::deletable () const
>  
>  /* See gdbthread.h.  */
>  
> +void
> +thread_info::set_resumed (bool resumed)
> +{
> +  if (resumed == m_resumed)
> +    return;
> +
> +  process_stratum_target *proc_target = this->inf->process_target ();
> +
> +  /* If we transition from resumed to not resumed, we might need to remove
> +     the thread from the resumed threads with pending statuses list.  */
> +  if (!resumed)
> +    proc_target->maybe_remove_resumed_with_pending_wait_status (this);
> +
> +  m_resumed = resumed;
> +
> +  /* If we transition from not resumed to resumed, we might need to add
> +     the thread to the resumed threads with pending statuses list.  */
> +  if (resumed)
> +    proc_target->maybe_add_resumed_with_pending_wait_status (this);

Longest function name award goes to...  ;-)



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 08/11] gdb: optimize check for resumed threads with pending wait status in maybe_set_commit_resumed_all_targets
  2021-06-22 16:57 ` [PATCH 08/11] gdb: optimize check for resumed threads with pending wait status in maybe_set_commit_resumed_all_targets Simon Marchi
@ 2021-07-05 15:51   ` Pedro Alves
  0 siblings, 0 replies; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:51 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:57 p.m., Simon Marchi via Gdb-patches wrote:
> Consider a test case where many threads (thousands) keep hitting a
> breakpoint whose condition evaluates to false.
> maybe_set_commit_resumed_all_targets is called at each handled event,
> when the scoped_disable_commit_resumed object in fetch_inferior_event is
> reset_and_commit-ed.  One particularly expensive check in there is
> whether the target has at least one resumed thread with a pending wait
> status (in which case, we don't want to commit the resumed threads, as
> we want to consume this status first).  It is currently implemented as
> walking all threads of the target.
> 
> Since we now maintain a per-target list of resumed threads with pending
> status, we can do this check efficiently, by checking whether that list
> is empty or not.
> 
> Add the process_stratum_target::has_resumed_with_pending_wait_status
> method for this, and use it in maybe_set_commit_resumed_all_targets.
> 
> gdb/ChangeLog:
> 
> 	  * process-stratum-target.h (class process_stratum_target)
> 	  <has_resumed_with_pending_wait_status>: New.
> 	  * infrun.c (maybe_set_commit_resumed_all_targets): Use
> 	  has_resumed_with_pending_wait_status.

OK.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 09/11] gdb: optimize selection of resumed thread with pending event
  2021-06-22 16:57 ` [PATCH 09/11] gdb: optimize selection of resumed thread with pending event Simon Marchi
@ 2021-07-05 15:51   ` Pedro Alves
  0 siblings, 0 replies; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:51 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:57 p.m., Simon Marchi via Gdb-patches wrote:
> Consider a case where many threads (thousands) keep hitting a breakpoint
> whose condition evaluates to false.  random_pending_event_thread is
> responsible for selecting a thread from an inferior among all that are
> resumed with a pending wait status.  It is currently implemented by
> walking the inferior's thread list twice: once to count the number of
> candidates and once to select a random one.
> 
> Since we now maintain a per target list of resumed threads with pending
> event, we can implement this more efficiently by walking that list and
> selecting the first thread that matches the criteria
> (random_pending_event_thread looks for an thread from a specific
> inferior, and possibly a filter ptid).  It will be faster especially in
> the common case where there isn't any resumed thread with pending
> event.  Currently, we have to iterate the thread list to figure this
> out.  With this patch, the list of resumed threads with pending event
> will be empty, so it's quick to figure out.
> 
> The random selection is kept, but is moved to
> process_stratum_target::random_resumed_with_pending_wait_status.  The
> same technique is used: do a first pass to count the number of
> candidates, and do a second pass to select a random one.  But given that
> the list of resumed threads with pending wait statuses will generally be
> short, or at least shorter than the full thread list, it should be
> quicker.
> 
> Note that this isn't completely true, in case there are multiple
> inferiors on the same target.  Imagine that inferior A has 10k resumed
> threads with pending wait statuses, and random_pending_event_thread is
> called with inferior B.  We'll need to go through the list that contains
> inferior A's threads to realize that inferior B has no resumed threads
> with pending wait status.  But I think that this is a corner /
> pathological case.  And a possible fix for this situation would be to
> make random_pending_event_thread work per-process-target, rather than
> per-inferior.
> 
> gdb/ChangeLog:
> 
> 	* process-stratum-target.h (class process_stratum_target)
> 	<random_resumed_with_pending_wait_status>: New.
> 	* process-stratum-target.c
> 	(process_stratum_target::random_resumed_with_pending_wait_status):
> 	New.
> 	* infrun.c (random_pending_event_thread): Use
> 	random_resumed_with_pending_wait_status.

OK.



^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid
  2021-06-22 16:57 ` [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid Simon Marchi
@ 2021-07-05 15:52   ` Pedro Alves
  2021-07-06 21:31     ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:52 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:57 p.m., Simon Marchi via Gdb-patches wrote:
> When debugging a large number of threads (thousands), looking up a
> thread by ptid_t using the inferior::thread_list linked list can add up.
> 
> Add inferior::thread_map, an std::unordered_map indexed by ptid_t, and
> change the find_thread_ptid function to look up a thread using
> std::unordered_map::find, instead of iterating on all of the
> inferior's threads.  This should make it faster to look up a thread
> from its ptid.
> 
> gdb/ChangeLog:
> yyyy-mm-dd  Simon Marchi  <simon.marchi@efficios.com>
>       	    Pedro Alves  <palves@palves.net>
> 
> 	* gdbarch-selftests.c (register_to_value_test): Update the mock
> 	inferior's thread map as well.
> 	* inferior.c (inferior::clear_thread_list): Clear the thread map.
> 	* inferior.h: Include <unordered_map>.
> 	(class inferior::thread_map): New field.
> 	* regcache.c (cooked_read_test): Update the mock inferior's thread
> 	map as well.
> 	* thread.c (set_thread_exited): Remove the thread from the thread
> 	map.
> 	(new_thread): Insert the thread in the ptid map.
> 	(find_thread_ptid): Lookup up the thread in the ptid map.
> 	(thread_change_ptid): Update ptid map entry.
> 

( Should this have Co-Authored-By: ? )

OK.

> Change-Id: I3a8da0a839e18dee5bb98b8b7dbeb7f3dfa8ae1c
> ---

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 11/11] gdb: optimize all_matching_threads_iterator
  2021-06-22 16:57 ` [PATCH 11/11] gdb: optimize all_matching_threads_iterator Simon Marchi
@ 2021-07-05 15:52   ` Pedro Alves
  2021-07-14  9:40     ` Tom de Vries
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-05 15:52 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-06-22 5:57 p.m., Simon Marchi via Gdb-patches wrote:
> all_matching_threads_iterator is used extensively in some pretty fast
> paths, often under the all_non_exited_threads function.
> 
> If a filter target and thread-specific ptid are given, it iterates on
> all threads of all inferiors of that target, to ultimately yield exactly
> on thread.  And this happens quite often, which means we unnecessarily
> spend time iterating on threads to find the one we are looking for.  The
> same thing happens if an inferior-specific ptid is given, although there
> the iterator yields all the threads of that inferior.
> 
> In those cases, the callers of all_non_exited_threads could have
> different behaviors depending on the kind of ptid, to avoid this
> inefficiency, but that would be very tedious.  Using
> all_non_exited_threads has the advantage that one simple implementation
> can work seamlessly on multiple threads or on one specific thread, just
> by playing with the ptid.
> 
> Instead, optimize all_matching_threads_iterator directly to detect these
> different cases and limiting what we iterate on to just what we need.
> 
>  - if filter_ptid is minus_one_ptid, do as we do now: filter inferiors
>    based on filter_target, iterate on all of the matching inferiors'
>    threads
>  - if filter_ptid is a pid-only ptid (then a filter_target must
>    necessarily be given), look up that inferior and iterate on all its
>    threads
>  - otherwise, filter_ptid is a thread-specific ptid, so look up that
>    specific thread and "iterate" only on it
> 
> For the last case, what was an iteration on all threads of the filter
> target now becomes a call to find_thread_ptid, which is quite efficient
> now thanks to inferior::ptid_thread_map.
> 
> gdb/ChangeLog:
> 
> 	* thread-iter.h (class all_matching_threads_iterator)
> 	<all_matching_threads_iterator>: Use default.
> 	<enum class mode>: New.
> 	<m_inf, m_thr>: Initialize.
> 	<m_filter_ptid>: Remove.
> 	* thread-iter.c (all_matching_threads_iterator::m_inf_matches):
> 	Don't filter on m_filter_ptid.
> 	(all_matching_threads_iterator::all_matching_threads_iterator):
> 	Choose path based on filter_ptid (all threads, all threads of
> 	inferior, single thread).
> 	(all_matching_threads_iterator::advance): Likewise.

OK.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter
  2021-07-05 15:41   ` Pedro Alves
@ 2021-07-06 19:16     ` Simon Marchi
  0 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 19:16 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

On 2021-07-05 11:41 a.m., Pedro Alves wrote:
> On 2021-06-22 5:56 p.m., Simon Marchi via Gdb-patches wrote:
>> I was always a bit confused by next_adapter, because it kind of mixes
>> the element type and the iterator type.  In reality, it is not much more
>> than a class that wraps two iterators (begin and end).  However, it
>> assumes that:
>>
>>  - you can construct the begin iterator by passing a pointer to the
>>    first element of the iterable
>>  - you can default-construct iterator to make the end iterator
>>
>> I think that by generalizing it a little bit, we can re-use it at more
>> places.
>>
>> Rename it to "iterator_range".  I think it describes a bit better: it's
>> a range made by wrapping a begin and end iterator.  Move it to its own
>> file, since it's not related to next_iterator anymore.
>>
>> iterator_range has two constructors.  The variadic one, where arguments
>> are forwarded to construct the underlying begin iterator.  The end
>> iterator is constructed through default construction.  This is a
>> generalization of what we have today.
>>
>> There is another constructor which receives already constructed begin
>> and end iterators, useful if the end iterator can't be obtained by
>> default-construction.  Or, if you wanted to make a range that does not
>> end at the end of the container, you could pass any iterator as the
>> "end".
>>
>> This generalization allows removing some "range" classes, like
>> all_inferiors_range.  These classes existed only to pass some arguments
>> when constructing the begin iterator.  With iterator_range, those same
>> arguments are passed to the iterator_range constructed and then
>> forwarded to the constructed begin iterator.
>>
>> There is a small functional difference in how iterator_range works
>> compared to next_adapter.  next_adapter stored the pointer it received
>> as argument and constructeur an iterator in the `begin` method.
>> iterator_range constructs the begin iterator and stores it as a member.
>> Its `begin` method returns a copy of that iterator.
>>
>> With just iterator_range, uses of next_adapter<foo> would be replaced
>> with:
>>
>>   using foo_iterator = next_iterator<foo>;
>>   using foo_range = iterator_range<foo_iterator>;
>>
>> However, I added a `next_range` wrapper as a direct replacement for
>> next_adapter<foo>.  IMO, next_range is a slightly better name than
>> next_adapter.
>>
>> The rest of the changes are applications of this new class.
> 
> LGTM.

Thanks, I pushed it by itself, since it's useful on its own.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-05 15:44   ` Pedro Alves
@ 2021-07-06 19:38     ` Simon Marchi
  2021-07-06 20:45       ` Simon Marchi
  2021-07-06 21:02       ` Pedro Alves
  0 siblings, 2 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 19:38 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Simon Marchi

On 2021-07-05 11:44 a.m., Pedro Alves wrote:
> Hi!
> 
> This looks mostly good to me, though I'm biased...
> 
> On 2021-06-22 5:56 p.m., Simon Marchi wrote:
>> From: Pedro Alves <pedro@palves.net>
>>
>> GDB currently has several objects that are put in a singly linked list,
>> by having the object's type have a "next" pointer directly.  For
>> example, struct thread_info and struct inferior.  Because these are
>> simply-linked lists, and we don't keep track of a "tail" pointer, when
>> we want to append a new element on the list, we need to walk the whole
>> list to find the current tail.  It would be nice to get rid of that
>> walk.  Removing elements from such lists also requires a walk, to find
>> the "previous" position relative to the element being removed.  To
>> eliminate the need for that walk, we could make those lists
>> doubly-linked, by adding a "prev" pointer alongside "next".  It would be
>> nice to avoid the boilerplace associated with maintaining such a list
> 
> boilerplace -> boilerplate

Fixed.

>> Unlike Boost's implementation, ours is not a circular list.  An earlier
>> version of the patch was circular: the instrusive_list type included an
> 
> instrusive_list -> intrusive_list

Fixed.

>> Add a Python pretty-printer, to help inspecting intrusive lists when
>> debugging GDB with GDB.  Here's an example of the output:
>>
>>     (top-gdb) p current_inferior_.m_obj.thread_list
>>     $1 = intrusive list of thread_info = {0x61700002c000, 0x617000069080, 0x617000069400, 0x61700006d680, 0x61700006eb80}
>>
> 
> Did you find this output helpful in practice?  Printing the whole object is
> for sure too much, but I wonder whether printing the thread id, and ptid and
> perhaps the thread state wouldn't be more helpful, like:

I didn't use it extensively, so I can't say if I find it really useful
or not.

>  $1 = intrusive list of thread_info = {
>    {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
>    {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
>    {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
>  }

How would you accomplish this, with a struct thread_info
pretty-printer?  This means that printing a single thread like this:

  (gdb) print *tp

would also produce the short output, I don't think we want that.  When
we print a single thread_info structure, I think it's good to have all
the fields shown.

Perhaps by defining a more specialized pretty-printer for
intrusive_list<thread_info>?  But then I'm not sure what kind of value
the children method would return.

Or can we define a pretty printer on the `struct thread_info *` type,
such that when a thread_info pointer is printed, we can print some extra
information next to the pointer value?  Like:

  (gdb) print tp
  $1 = (thread_info *) 0x61700002c000 { id=1.1, ptid=1000.1000.0, state=THREAD_RUNNING }

So presumably, when printing the list, that would give:

  $1 = intrusive list of thread_info = {
    0x61700002c000 {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING},
    0x617000069080 {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
    0x617000069400 {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
  }

But I don't even know if that is possible.  I'll give it a try and report back.

>> It's not possible with current master, but with this patch [1] that I
>> hope will be merged eventually, it's possible to index the list and
>> access the pretty-printed value's children:
>>
>>     (top-gdb) p current_inferior_.m_obj.thread_list[1]
>>     $2 = (thread_info *) 0x617000069080
>>     (top-gdb) p current_inferior_.m_obj.thread_list[1].ptid
>>     $3 = {
>>       m_pid = 406499,
>>       m_lwp = 406503,
>>       m_tid = 0
>>     }
> 
> I guess in practice I'd always want to print the list
> with "set print array-indexes on".  Otherwise, how would you know
> which index to pass to []?  And still, with just pointers/addresses
> in the output, I'm not sure I'd easily know which index to pass:
> 
>  $1 = intrusive list of struct thread_info = {[0] = 0x5555564d0f60, [1] = 0x5555572ad640, [2] = 0x5555572ad9f0}
> 
> ?
> 
> I mean, one can eyeball for the right entry based on thread_info
> pointer/address, but if one already has the pointer/address handy, then
> one wouldn't need to search for the entry in the thread list in the first
> place, right?
> 
> It does seem to make it a bit easier to iterate over the whole list
> printing each element, easier than following the next->next->next
> pointers.  Was that the use case you had in mind?

I agree with you that for a long list it can be difficult to know which
index to use.  I've tried it only with lists of 4-5 elements, in which
case it's easy.

It is indeed easier when iterating "by hand", especially when using a
node as a field.  Because then it's not `.next.next.next` but
`.node.nextnode.next.node.next`.

I think that in any case it's more useful to have the possibility to do
it than not having it, we don't lose anything by having it.  But
printing indices sounds useful indeed.

> With the alternative output suggested above, we'd have:
> 
>  $1 = intrusive list of thread_info = {
>    [0] = {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
>    [1] = {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
>    [2] = {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
>  }
> 
> Off hand, I think I'd find this more useful.  I'm assuming that
> using [] with Andrew's patch would still work the same way.
> 
> I guess this would be implemented by passing some customization
> object or function to the pretty printer, that it would call to
> print each element.  If no such customization is passed, then
> it would just print what you have.  So I guess this could always
> be done separately...

Perhaps that could be done by pretending intrusive_list is a map, making
the display_hint method return 'map' instead of 'array':

  https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing-API.html#Pretty-Printing-API

>> @@ -0,0 +1,734 @@
>> +/* Tests fpr intrusive double linked list for GDB, the GNU debugger.
>> +   Copyright (C) 2021 Free Software Foundation, Inc.
>> +
>> +   This file is part of GDB.
>> +
>> +   This program 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 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +#include "defs.h"
>> +
>> +#include "gdbsupport/intrusive_list.h"
>> +#include "gdbsupport/selftest.h"
>> +#include <unordered_set>
>> +
>> +/* An item type using intrusive_list_node by inheriting from it and its
>> +   corresponding list type.  Put another base before intrusive_list_node
>> +   so that a pointer to the node != a pointer to the item.  */
>> +
>> +struct other_base
>> +{
>> +  int n = 1;
>> +};
>> +
>> +struct item_with_base : public other_base,
>> +			public intrusive_list_node<item_with_base>
>> +{
>> +  item_with_base (const char *name)
> 
> explicit

Done.

>> +    : name (name)
>> +  {}
>> +
>> +  const char *const name;
>> +};
>> +
>> +using item_with_base_list = intrusive_list<item_with_base>;
>> +
>> +/* An item type using intrusive_list_node as a field and its corresponding
>> +   list type.  Put the other field before the node, so that a pointer to the
>> +   node != a pointer to the item.  */
>> +
>> +struct item_with_member
>> +{
>> +  item_with_member (const char *name)
> 
> explicit

Done.

>> diff --git a/gdbsupport/intrusive_list.h b/gdbsupport/intrusive_list.h
>> new file mode 100644
>> index 000000000000..8e98e5b2c1a5
>> --- /dev/null
>> +++ b/gdbsupport/intrusive_list.h
>> @@ -0,0 +1,559 @@
>> +/* Intrusive double linked list for GDB, the GNU debugger.
>> +   Copyright (C) 2021 Free Software Foundation, Inc.
>> +
>> +   This file is part of GDB.
>> +
>> +   This program 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 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +#ifndef GDBSUPPORT_INTRUSIVE_LIST_H
>> +#define GDBSUPPORT_INTRUSIVE_LIST_H
>> +
>> +#define UNLINKED_VALUE ((T *) -1)
> 
> UNLINKED_VALUE seems like a too-generic name for a macro.
> I think it'd be better to add some prefix to minimize
> potential for conflict.  INTR_LIST_UNLINKED_VALUE
> or some such.

Agreed, I'm always the one who usually asks for better naming /
scoping.  I'll use INTRUSIVE_LIST_UNLINKED_VALUE since I don't like
unnecessary abbreviations :).

Thanks,

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-06 19:38     ` Simon Marchi
@ 2021-07-06 20:45       ` Simon Marchi
  2021-07-06 21:04         ` Pedro Alves
  2021-07-06 21:02       ` Pedro Alves
  1 sibling, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 20:45 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Simon Marchi

> Or can we define a pretty printer on the `struct thread_info *` type,
> such that when a thread_info pointer is printed, we can print some extra
> information next to the pointer value?  Like:
> 
>   (gdb) print tp
>   $1 = (thread_info *) 0x61700002c000 { id=1.1, ptid=1000.1000.0, state=THREAD_RUNNING }
> 
> So presumably, when printing the list, that would give:
> 
>   $1 = intrusive list of thread_info = {
>     0x61700002c000 {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING},
>     0x617000069080 {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
>     0x617000069400 {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
>   }
> 
> But I don't even know if that is possible.  I'll give it a try and report back.

That kind of works:

    (top-gdb) p current_inferior_.m_obj .thread_list .m_front
    $5 = (struct thread_info *) 0x61700003b180 {id = 1.1, ptid = 2243033.2243033.0}
    (top-gdb) p current_inferior_.m_obj .thread_list
    $6 = intrusive list of struct thread_info = {(struct thread_info *) 0x61700003b180 {id = 1.1, ptid = 2243033.2243033.0}, (struct thread_info *) 0x61700003b500 {id = 1.2, ptid = 2243033.2243036.0}, (struct thread_info *) 0x61700003b880 {id = 1.3, ptid = 2243033.2243037.0}, (struct thread_info *) 0x61700003bc00 {id = 1.4, ptid = 2243033.2243038.0}}
    (top-gdb) set print array-indexes
    (top-gdb) p current_inferior_.m_obj .thread_list
    $7 = intrusive list of struct thread_info = {[0] = (struct thread_info *) 0x61700003b180 {id = 1.1, ptid = 2243033.2243033.0}, [1] = (struct thread_info *) 0x61700003b500 {id = 1.2, ptid = 2243033.2243036.0}, [2] = (struct thread_info *) 0x61700003b880 {id = 1.3, ptid = 2243033.2243037.0}, [3] = (struct thread_info *) 0x61700003bc00 {id = 1.4, ptid = 2243033.2243038.0}}

It looks like the output of pretty-printers with display_hint 'array'
isn't affected by "set print pretty", so it's always displayed on one
line, that's strange.

What I don't like about this solution is that I have to replicate the
'(struct thread_info *) 0x12345' part of the display, that is normally
displayed by GDB for a pointer:

    class ThreadInfoPointerPrinter:
	def __init__(self, val):
	    self._val = val

	def to_string(self):
	    ptid = self._val['ptid']
	    inf_num = self._val['inf']['num']
	    per_inf_num = self._val['per_inf_num']

	    return '(struct thread_info *) {} {{id = {}.{}, ptid = {}}}'.format(hex(int(self._val)), inf_num, per_inf_num, ptid)

It would be nice if there was a way to just append some information
after the default display.

> Perhaps that could be done by pretending intrusive_list is a map, making
> the display_hint method return 'map' instead of 'array':
> 
>   https://sourceware.org/gdb/onlinedocs/gdb/Pretty-Printing-API.html#Pretty-Printing-API

I tried this, it does work:

    $1 = intrusive list of struct thread_info = {
      [0] = 0x61700003b180,
      [1] = 0x61700003b500,
      [2] = 0x61700003b880,
      [3] = 0x61700003bc00
    }

However, I see this as going around / abusing our pretty-printing API.
If one doesn't want to see the indices, it becomes impossible (AFAIK).
If you want array-indexes, why not just set array-indexes on?

It is the same when printing an std::vector, you don't have indices:

  $1 = std::vector of length 10, capacity 16 = {0x55555556aeb0, 0x55555556aef0, 0x55555556aed0, 0x55555556af10, 0x55555556af60, 0x55555556afd0, 0x55555556aff0, 0x55555556b010, 0x55555556b030, 0x55555556b0e0}

And for a vector, it's even more natural to index with [].  There too,
it becomes useful to enable array-indexes:

    $4 = std::vector of length 10, capacity 16 = {[0] = 0x55555556aeb0, [1] = 0x55555556aef0, [2] = 0x55555556aed0, [3] = 0x55555556af10, [4] = 0x55555556af60, [5] = 0x55555556afd0, [6] = 0x55555556aff0, [7] = 0x55555556b010, [8] = 0x55555556b030, [9] = 0x55555556b0e0}

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 04/11] gdb: use intrusive list for step-over chain
  2021-07-05 15:45   ` Pedro Alves
@ 2021-07-06 20:59     ` Simon Marchi
  0 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 20:59 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

On 2021-07-05 11:45 a.m., Pedro Alves wrote:
> On 2021-06-22 5:56 p.m., Simon Marchi via Gdb-patches wrote:
> 
>> 	* reference-to-pointer-iterator.h (struct
>> 	reference_to_pointer_iterator): Add default assignment
>> 	operators.
> 
> It's not immediately obvious why this is needed now, and wasn't before.
> It should perhaps be mentioned in the commit log.  Especially if you
> decide to discard the ChangeLog entry.
> 
> Otherwise LGTM.

Hmm right.  It's to avoid this error:

      CXX    infrun.o
    cc1plus: warning: command-line option ‘-Wmissing-prototypes’ is valid for C/ObjC but not for C++
    In file included from /home/simark/src/binutils-gdb/gdb/breakpoint.h:36,
		     from /home/simark/src/binutils-gdb/gdb/gdbthread.h:26,
		     from /home/simark/src/binutils-gdb/gdb/infrun.h:21,
		     from /home/simark/src/binutils-gdb/gdb/infrun.c:23:
    /home/simark/src/binutils-gdb/gdb/../gdbsupport/safe-iterator.h: In instantiation of ‘basic_safe_iterator<Iterator>::self_type& basic_safe_iterator<Iterator>::operator++() [with Iterator = reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >; basic_safe_iterator<Iterator>::self_type = basic_safe_iterator<reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > > >]’:
    /home/simark/src/binutils-gdb/gdb/infrun.c:1883:26:   required from here
    /home/simark/src/binutils-gdb/gdb/../gdbsupport/safe-iterator.h:82:10: error: use of deleted function ‘constexpr reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >& reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >::operator=(const reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >&)’
       82 |     m_it = m_next;
	  |     ~~~~~^~~~~~~~
    In file included from /home/simark/src/binutils-gdb/gdb/thread-iter.h:25,
		     from /home/simark/src/binutils-gdb/gdb/gdbthread.h:516,
		     from /home/simark/src/binutils-gdb/gdb/infrun.h:21,
		     from /home/simark/src/binutils-gdb/gdb/infrun.c:23:
    /home/simark/src/binutils-gdb/gdb/../gdbsupport/reference-to-pointer-iterator.h:30:8: note: ‘constexpr reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >& reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >::operator=(const reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >&)’ is implicitly declared as deleted because ‘reference_to_pointer_iterator<intrusive_list_iterator<thread_info, intrusive_member_node<thread_info, &thread_info::step_over_list_node> > >’ declares a move constructor or move assignment operator
       30 | struct reference_to_pointer_iterator
	  |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

So I would say:

  Add explicit default assignment operators to
  reference_to_pointer_iterator, which are otherwise implicitly deleted.
  This is needed because to define thread_step_over_list_safe_iterator,
  we wrap reference_to_pointer_iterator inside a basic_safe_iterator,
  and basic_safe_iterator needs to be able to copy-assign the wrapped
  iterator.  The move-assignment operator is therefore not needed, only
  the copy-assignment operator is.  But for completeness, add both.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-06 19:38     ` Simon Marchi
  2021-07-06 20:45       ` Simon Marchi
@ 2021-07-06 21:02       ` Pedro Alves
  2021-07-06 21:45         ` Simon Marchi
  1 sibling, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-06 21:02 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: Simon Marchi

On 2021-07-06 8:38 p.m., Simon Marchi wrote:
> On 2021-07-05 11:44 a.m., Pedro Alves wrote:
>>>>  $1 = intrusive list of thread_info = {
>>    {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
>>    {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
>>    {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
>>  }
> 
> How would you accomplish this, with a struct thread_info
> pretty-printer?  This means that printing a single thread like this:
> 
>   (gdb) print *tp
> 
> would also produce the short output, I don't think we want that.  When
> we print a single thread_info structure, I think it's good to have all
> the fields shown.

I don't have a great answer.  It feels to me like the pretty printer code
should ask the container if it has a custom printer for the element, and only
if it doesn't would it look up the printer for the element's type.

Maybe having a printer for thread_info all the time isn't that bad.  printing
thread_info structures does dump a lot of stuff and isn't that easy to parse.
If you want to see the whole thing, there's always "print /r" to disable the
printer.

> 
> Perhaps by defining a more specialized pretty-printer for
> intrusive_list<thread_info>?  But then I'm not sure what kind of value
> the children method would return.

I guess it could return a typedef for "thread_info *" like e.g., "thread_info_p" instead
of the raw pointer, and then we'd register a printer for only the typedef.  I
think that means that indexing via thread_list[1] etc. would also return the typedef,
and thus give you the short print..

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-06 20:45       ` Simon Marchi
@ 2021-07-06 21:04         ` Pedro Alves
  2021-07-06 21:38           ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-06 21:04 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: Simon Marchi

On 2021-07-06 9:45 p.m., Simon Marchi wrote:
> I tried this, it does work:
> 
>     $1 = intrusive list of struct thread_info = {
>       [0] = 0x61700003b180,
>       [1] = 0x61700003b500,
>       [2] = 0x61700003b880,
>       [3] = 0x61700003bc00
>     }
> 
> However, I see this as going around / abusing our pretty-printing API.
> If one doesn't want to see the indices, it becomes impossible (AFAIK).
> If you want array-indexes, why not just set array-indexes on?

I think you misunderstood me earlier -- I meant that I would probably switch on
"set array-indexes on" all the time, as I'd find it difficult to use the
pointer-only array without it, not that the printer would not respect
the setting.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-07-05 15:51   ` Pedro Alves
@ 2021-07-06 21:25     ` Simon Marchi
  2021-07-07 12:01       ` Pedro Alves
  0 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 21:25 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

>> In set_thread_exited, we try to remove the thread from the list, because
>> keeping an exited thread in that list would make no sense (especially if
>> the thread is freed).  My first implementation assumed that a process
>> stratum target was always present when set_thread_exited is called.
>> That's however, not the case: in some cases, targets unpush themselves
>> from an inferior and then call "exit_inferior", which exits all the
>> threads.  If the target is unpushed before set_thread_exited is called
>> on the threads, it means we could mistakenly leave some threads in the
>> list.  I tried to see how hard it would be to make it such that targets
>> have to exit all threads before unpushing themselves from the inferior
>> (that would seem logical to me, we don't want threads belonging to an
>> inferior that has no process target).  That seem quite difficult and not
>> worth the time.  Instead, I changed inferior::unpush_target to remove an
>> threads of that inferior from the list.
> 
> "remove an threads" -> "remove all threads" ?

Yes, fixed.

>> @@ -470,6 +469,10 @@ class thread_info : public refcounted_object,
>>       linked.  */
>>    intrusive_list_node<thread_info> step_over_list_node;
>>  
>> +  /* Node for list of threads that are resumed and have a pending wait
>> +     status.  */
> 
> Maybe mention that all threads in list list belong to the same
> process_stratum_target ?

Sounds good, changed to:

  /* Node for list of threads that are resumed and have a pending wait status.

     The list head for this is in process_stratum_target, hence all threads in
     this list belong to that process target.  */
  intrusive_list_node<thread_info> resumed_with_pending_wait_status_node;

>> @@ -89,6 +89,25 @@ inferior::inferior (int pid_)
>>    m_target_stack.push (get_dummy_target ());
>>  }
>>  
>> +/* See inferior.h.  */
>> +
>> +int
>> +inferior::unpush_target (struct target_ops *t)
>> +{
>> +  /* If unpushing the process stratum target while threads exists, ensure that
> 
> "threads exists" -> "threads exist"

Done.

>> +     we don't leave any threads of this inferior in the target's "resumed with
>> +     pending wait status" list.  */
>> +  if (t->stratum () == process_stratum)
>> +    {
>> +      process_stratum_target *proc_target = as_process_stratum_target (t);
>> +
>> +      for (thread_info *thread : this->non_exited_threads ())
>> +	proc_target->maybe_remove_resumed_with_pending_wait_status (thread);
> 
> Note the target_pid_to_str call inside maybe_remove_resumed_with_pending_wait_status
> adds back a dependency on current_inferior.

Arggg, we don't want that.  Since that is in a process_stratum_target
method, I'll change the message to call the pid_to_str method of the
current object instead:

      infrun_debug_printf ("removing from resumed threads with event list: %s",
			   this->pid_to_str (thread->ptid).c_str ());

Does that sound good?

>> --- a/gdb/process-stratum-target.c
>> +++ b/gdb/process-stratum-target.c
>> @@ -106,6 +106,40 @@ process_stratum_target::follow_exec (inferior *follow_inf, ptid_t ptid,
>>  
>>  /* See process-stratum-target.h.  */
>>  
>> +void
>> +process_stratum_target::maybe_add_resumed_with_pending_wait_status
>> +  (thread_info *thread)
>> +{
>> +  gdb_assert (!thread->resumed_with_pending_wait_status_node.is_linked ());
>> +
>> +  if (thread->resumed () && thread->has_pending_waitstatus ())
>> +    {
>> +      infrun_debug_printf ("adding to resumed threads with event list: %s",
>> +			   target_pid_to_str (thread->ptid).c_str ());
> 
> This here too.  Not 100% sure this target call is always done
> with the right target stack selected.

I'd make the same change to use this->pid_to_str here too.

> 
>> +      m_resumed_with_pending_wait_status.push_back (*thread);
>> +    }
>> +}
>> +
>> +/* See process-stratum-target.h.  */
>> +
> 
>> +
>> +private:
>> +  /* List of threads managed by this target which simultaneously are resumed
>> +     and have a pending wait status.  */
> 
> I'd suggest expanding this comment a little to mention this
> is done for optimization reasons, to avoid walking
> thread lists, something like that.  Or maybe say that in
> the thread_info node.  Or both places.

Done:

  /* List of threads managed by this target which simultaneously are resumed
     and have a pending wait status.

     This is done for optimization reasons, it would be possible to walk the
     inferior thread lists to find these threads.  But since this is something
     we need to do quite frequently in the hot path, maintaining this list
     avoids walking the thread lists repeatedly.  */

I would prefer to avoid repeating the same thing at two places, because
it's a recipe for the two places getting out of sync.  Since the node
comment in thread_info now points to here (says the list head is in
process_stratum_target), people looking for more information about this
list should have no problem finding the comment here.

> 
>> +  thread_info_resumed_with_pending_wait_status_list
>> +    m_resumed_with_pending_wait_status;
>>  };
>>  
>>  /* Downcast TARGET to process_stratum_target.  */
>> diff --git a/gdb/thread.c b/gdb/thread.c
>> index 289d33c74c3b..26974e1b8cbc 100644
>> --- a/gdb/thread.c
>> +++ b/gdb/thread.c
>> @@ -188,6 +188,10 @@ set_thread_exited (thread_info *tp, bool silent)
>>  
>>    if (tp->state != THREAD_EXITED)
>>      {
>> +      process_stratum_target *proc_target = tp->inf->process_target ();
>> +      if (proc_target != nullptr)
> 
> I think this check needs a comment.

Done:

      /* Some targets unpush themselves from the inferior's target stack before
         clearing the inferior's thread list (which marks all threads as exited,
         and therefore leads to this function).  In this case, the inferior's
         process target will be nullptr when we arrive here.

         See also the comment in inferior::unpush_target.  */

And I also added a cross-reference to here from the comment in
inferior::unpush_target.

>> +  /* If we transition from not resumed to resumed, we might need to add
>> +     the thread to the resumed threads with pending statuses list.  */
>> +  if (resumed)
>> +    proc_target->maybe_add_resumed_with_pending_wait_status (this);
> 
> Longest function name award goes to...  ;-)

Indeed!  If you have a name that is shorter but just as clear, I'm open
for suggestion.  But I prefer names that are non-ambiguous and use the
right terminology over names that are short just for convenience's sake.

Thanks!

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid
  2021-07-05 15:52   ` Pedro Alves
@ 2021-07-06 21:31     ` Simon Marchi
  2021-07-07 12:13       ` Pedro Alves
  0 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 21:31 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

> ( Should this have Co-Authored-By: ? )

Yes, and probably not just this patch.  I'll go over the series and add
them where needed.

When picking up somebody else's patch like this, or the intrusive_list
patch, is it better to keep the original author as the Author and add
ourselves with Co-Authored-By, or set the "final" Author as the git
Author and add previous Authors with Co-Authored-By?

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-06 21:04         ` Pedro Alves
@ 2021-07-06 21:38           ` Simon Marchi
  0 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 21:38 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Simon Marchi

On 2021-07-06 5:04 p.m., Pedro Alves wrote:
> On 2021-07-06 9:45 p.m., Simon Marchi wrote:
>> I tried this, it does work:
>>
>>     $1 = intrusive list of struct thread_info = {
>>       [0] = 0x61700003b180,
>>       [1] = 0x61700003b500,
>>       [2] = 0x61700003b880,
>>       [3] = 0x61700003bc00
>>     }
>>
>> However, I see this as going around / abusing our pretty-printing API.
>> If one doesn't want to see the indices, it becomes impossible (AFAIK).
>> If you want array-indexes, why not just set array-indexes on?
> 
> I think you misunderstood me earlier -- I meant that I would probably switch on
> "set array-indexes on" all the time, as I'd find it difficult to use the
> pointer-only array without it, not that the printer would not respect
> the setting.

Ah, yes sorry.  I understood it as if it was a bad thing that array
indices were not printed by default, and that it should be fixed in the
printer.  So, nothing to see here :).

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-06 21:02       ` Pedro Alves
@ 2021-07-06 21:45         ` Simon Marchi
  2021-07-07 11:46           ` Pedro Alves
  0 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-07-06 21:45 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Simon Marchi

On 2021-07-06 5:02 p.m., Pedro Alves wrote:
> On 2021-07-06 8:38 p.m., Simon Marchi wrote:
>> On 2021-07-05 11:44 a.m., Pedro Alves wrote:
>>>>>  $1 = intrusive list of thread_info = {
>>>    {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
>>>    {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
>>>    {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
>>>  }
>>
>> How would you accomplish this, with a struct thread_info
>> pretty-printer?  This means that printing a single thread like this:
>>
>>   (gdb) print *tp
>>
>> would also produce the short output, I don't think we want that.  When
>> we print a single thread_info structure, I think it's good to have all
>> the fields shown.
> 
> I don't have a great answer.  It feels to me like the pretty printer code
> should ask the container if it has a custom printer for the element, and only
> if it doesn't would it look up the printer for the element's type.

That would be a new feature that doesn't exist today, I think: a
different printer for a type when it's printed as a container element
than when it's printed standalone.

> Maybe having a printer for thread_info all the time isn't that bad.  printing
> thread_info structures does dump a lot of stuff and isn't that easy to parse.
> If you want to see the whole thing, there's always "print /r" to disable the
> printer.
> 
>>
>> Perhaps by defining a more specialized pretty-printer for
>> intrusive_list<thread_info>?  But then I'm not sure what kind of value
>> the children method would return.
> 
> I guess it could return a typedef for "thread_info *" like e.g., "thread_info_p" instead
> of the raw pointer, and then we'd register a printer for only the typedef.  I
> think that means that indexing via thread_list[1] etc. would also return the typedef,
> and thus give you the short print..

I think these are all good ideas for improvements, but I'd rather keep
them for later (if someone wants to implement them, I'm not sure I
will).  We could bike-shed for a while on how to display a thread_info,
what to include / what to exclude, etc.  I think that my original
proposal is strictly better than what we have today, in the sense that
today you just can't print the whole list of threads, so we don't lose
anything.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-06 21:45         ` Simon Marchi
@ 2021-07-07 11:46           ` Pedro Alves
  2021-07-07 13:52             ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-07 11:46 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: Simon Marchi

On 2021-07-06 10:45 p.m., Simon Marchi wrote:
> On 2021-07-06 5:02 p.m., Pedro Alves wrote:
>> On 2021-07-06 8:38 p.m., Simon Marchi wrote:
>>> On 2021-07-05 11:44 a.m., Pedro Alves wrote:
>>>>>>  $1 = intrusive list of thread_info = {
>>>>    {id = 1.1, ptid = 1000.1000.0, state = THREAD_RUNNING}, 
>>>>    {id = 1.3, ptid = 1000.1002.0, state = THREAD_STOPPED},
>>>>    {id = 1.5, ptid = 1000.3672.0, state = THREAD_STOPPED}
>>>>  }
>>>
>>> How would you accomplish this, with a struct thread_info
>>> pretty-printer?  This means that printing a single thread like this:
>>>
>>>   (gdb) print *tp
>>>
>>> would also produce the short output, I don't think we want that.  When
>>> we print a single thread_info structure, I think it's good to have all
>>> the fields shown.
>>
>> I don't have a great answer.  It feels to me like the pretty printer code
>> should ask the container if it has a custom printer for the element, and only
>> if it doesn't would it look up the printer for the element's type.
> 
> That would be a new feature that doesn't exist today, I think: a
> different printer for a type when it's printed as a container element
> than when it's printed standalone.

Yeah.

> I think these are all good ideas for improvements, but I'd rather keep
> them for later (if someone wants to implement them, I'm not sure I
> will).  We could bike-shed for a while on how to display a thread_info,
> what to include / what to exclude, etc.  I think that my original
> proposal is strictly better than what we have today, in the sense that
> today you just can't print the whole list of threads, so we don't lose
> anything.

The discussion about the thread_info pretty printer, yes, agreed.

However, the discussion on the list printer itself, one point that we should
settle discuss a bit more is whether it is really the right approach to make it
show children as pointers.  

Showing pointers really looks not useful to me.  The only thing I think I get
out of it is that there are elements in the list.

I mean, AFAICT, even the std::list printer shows objects, not pointers,
for instance:

 (gdb) p my_list
 $1 = std::__cxx11::list = {[0] = 1, [1] = 2, [2] = 3}

Hmm, funny, it shows the indexes, even though I don't have
array-indexes off.  Guess it must be using "map", but I haven't checked.

Imagine we were using std::list for thread_info objects.

But I don't think that that discussion should block the main change
from going in.  I'd support moving the printer out to a separate patch,
even.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-07-06 21:25     ` Simon Marchi
@ 2021-07-07 12:01       ` Pedro Alves
  2021-07-12 22:28         ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Pedro Alves @ 2021-07-07 12:01 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-07-06 10:25 p.m., Simon Marchi wrote:

>>> +     we don't leave any threads of this inferior in the target's "resumed with
>>> +     pending wait status" list.  */
>>> +  if (t->stratum () == process_stratum)
>>> +    {
>>> +      process_stratum_target *proc_target = as_process_stratum_target (t);
>>> +
>>> +      for (thread_info *thread : this->non_exited_threads ())
>>> +	proc_target->maybe_remove_resumed_with_pending_wait_status (thread);
>>
>> Note the target_pid_to_str call inside maybe_remove_resumed_with_pending_wait_status
>> adds back a dependency on current_inferior.
> 
> Arggg, we don't want that.  Since that is in a process_stratum_target
> method, I'll change the message to call the pid_to_str method of the
> current object instead:
> 
>       infrun_debug_printf ("removing from resumed threads with event list: %s",
> 			   this->pid_to_str (thread->ptid).c_str ());
> 
> Does that sound good?

It does not.  That would mean a target higher on the stack wouldn't have a chance
at printing the thread.

>>
>>> +      m_resumed_with_pending_wait_status.push_back (*thread);
>>> +    }
>>> +}
>>> +
>>> +/* See process-stratum-target.h.  */
>>> +
>>
>>> +
>>> +private:
>>> +  /* List of threads managed by this target which simultaneously are resumed
>>> +     and have a pending wait status.  */
>>
>> I'd suggest expanding this comment a little to mention this
>> is done for optimization reasons, to avoid walking
>> thread lists, something like that.  Or maybe say that in
>> the thread_info node.  Or both places.
> 
> Done:
> 
>   /* List of threads managed by this target which simultaneously are resumed
>      and have a pending wait status.
> 
>      This is done for optimization reasons, it would be possible to walk the
>      inferior thread lists to find these threads.  But since this is something
>      we need to do quite frequently in the hot path, maintaining this list
>      avoids walking the thread lists repeatedly.  */
> 
> I would prefer to avoid repeating the same thing at two places, because
> it's a recipe for the two places getting out of sync.  Since the node
> comment in thread_info now points to here (says the list head is in
> process_stratum_target), people looking for more information about this
> list should have no problem finding the comment here.
> 

Looks good.

>>
>>> +  thread_info_resumed_with_pending_wait_status_list
>>> +    m_resumed_with_pending_wait_status;
>>>  };
>>>  
>>>  /* Downcast TARGET to process_stratum_target.  */
>>> diff --git a/gdb/thread.c b/gdb/thread.c
>>> index 289d33c74c3b..26974e1b8cbc 100644
>>> --- a/gdb/thread.c
>>> +++ b/gdb/thread.c
>>> @@ -188,6 +188,10 @@ set_thread_exited (thread_info *tp, bool silent)
>>>  
>>>    if (tp->state != THREAD_EXITED)
>>>      {
>>> +      process_stratum_target *proc_target = tp->inf->process_target ();
>>> +      if (proc_target != nullptr)
>>
>> I think this check needs a comment.
> 
> Done:
> 
>       /* Some targets unpush themselves from the inferior's target stack before
>          clearing the inferior's thread list (which marks all threads as exited,
>          and therefore leads to this function).  In this case, the inferior's
>          process target will be nullptr when we arrive here.
> 
>          See also the comment in inferior::unpush_target.  */
> 
> And I also added a cross-reference to here from the comment in
> inferior::unpush_target.

Great.

> 
>>> +  /* If we transition from not resumed to resumed, we might need to add
>>> +     the thread to the resumed threads with pending statuses list.  */
>>> +  if (resumed)
>>> +    proc_target->maybe_add_resumed_with_pending_wait_status (this);
>>
>> Longest function name award goes to...  ;-)
> 
> Indeed!  If you have a name that is shorter but just as clear, I'm open
> for suggestion.  But I prefer names that are non-ambiguous and use the
> right terminology over names that are short just for convenience's sake.

Sure, as a preference, though that shouldn't be a too-strict rule IMO, otherwise
with very long function names we can end up with awkward looking code as soon as we
need to indent a caller a couple levels.  In this case, luckily that didn't happen,
so I'm not really objecting.

"add_resumed_pending_status" or "add_resumed_pending_ws" would work as well for
me, for example.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid
  2021-07-06 21:31     ` Simon Marchi
@ 2021-07-07 12:13       ` Pedro Alves
  0 siblings, 0 replies; 49+ messages in thread
From: Pedro Alves @ 2021-07-07 12:13 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-07-06 10:31 p.m., Simon Marchi wrote:
>> ( Should this have Co-Authored-By: ? )
> 
> Yes, and probably not just this patch.  I'll go over the series and add
> them where needed.
> 
> When picking up somebody else's patch like this, or the intrusive_list
> patch, is it better to keep the original author as the Author and add
> ourselves with Co-Authored-By, or set the "final" Author as the git
> Author and add previous Authors with Co-Authored-By?

I don't know whether there's an established de facto rule.  I think I'd normally
tend to go more by who's the main author of the change, though that's a
judgement call.  It wouldn't make sense to me to tweak someone else's change
in a minor way, add a couple lines, and then make myself "main git author".
If the workload was more balanced, I'd just keep the original author and add
myself to Co-Authored-By.  E.g., today my version of Luis's tag verification
change, the actual code change I posted was all written by me from scratch, but
the essence of the change isn't that different from Luis's original, so I kept
him as main author.

In this particular thread map patch, I don't even remember who did what in
the patch, I'm fine with how you had it.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it
  2021-07-07 11:46           ` Pedro Alves
@ 2021-07-07 13:52             ` Simon Marchi
  0 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-07 13:52 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches; +Cc: Simon Marchi

On 2021-07-07 7:46 a.m., Pedro Alves wrote:
>> I think these are all good ideas for improvements, but I'd rather keep
>> them for later (if someone wants to implement them, I'm not sure I
>> will).  We could bike-shed for a while on how to display a thread_info,
>> what to include / what to exclude, etc.  I think that my original
>> proposal is strictly better than what we have today, in the sense that
>> today you just can't print the whole list of threads, so we don't lose
>> anything.
> 
> The discussion about the thread_info pretty printer, yes, agreed.
> 
> However, the discussion on the list printer itself, one point that we should
> settle discuss a bit more is whether it is really the right approach to make it
> show children as pointers.  
> 
> Showing pointers really looks not useful to me.  The only thing I think I get
> out of it is that there are elements in the list.
> 
> I mean, AFAICT, even the std::list printer shows objects, not pointers,
> for instance:
> 
>  (gdb) p my_list
>  $1 = std::__cxx11::list = {[0] = 1, [1] = 2, [2] = 3}
> 
> Hmm, funny, it shows the indexes, even though I don't have
> array-indexes off.  Guess it must be using "map", but I haven't checked.

In fact, it doesn't specify any display_hint.  The [%d] comes from here:

   https://gitlab.com/gnutools/gcc/-/blob/trunk/libstdc%2B%2B-v3/python/libstdcxx/v6/printers.py#L313

So it's as if the children of the list are members of a structure, with
names [0], [1], and so on.  That doesn't look totally right to me but...
I'll pretend I didn't see it.

> Imagine we were using std::list for thread_info objects.
> 
> But I don't think that that discussion should block the main change
> from going in.  I'd support moving the printer out to a separate patch,
> even.

Well, the printer mostly works, so I think it's ok to include it.  I
agree with you that printing pointers is not ideal.  For objects smaller
than thread_info, it would certainly be useful to see the objects and
not pointers.  So I will make the quick change to make the list children
be objects and pointers.  Sure, printing the thread list will be very
verbose, but the same argument I made earlier applies: it's not a
regression, since printing the whole list is simply not possible today.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-07-07 12:01       ` Pedro Alves
@ 2021-07-12 22:28         ` Simon Marchi
  2021-07-12 22:34           ` Simon Marchi
  2021-07-13 12:21           ` Pedro Alves
  0 siblings, 2 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-12 22:28 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

>> Arggg, we don't want that.  Since that is in a process_stratum_target
>> method, I'll change the message to call the pid_to_str method of the
>> current object instead:
>>
>>       infrun_debug_printf ("removing from resumed threads with event list: %s",
>> 			   this->pid_to_str (thread->ptid).c_str ());
>>
>> Does that sound good?
> 
> It does not.  That would mean a target higher on the stack wouldn't have a chance
> at printing the thread.

Since this is only for debug message purposes, and I find it clearer to
see the spelled-out ptid anyway (the pid, lwp and tid values), I added a
to_string method to ptid_t and used that here:

    std::string
    ptid_t::to_string () const
    {
      return string_printf ("%d.%d.%d", m_pid, m_lwp, m_tid);
    }

>>>> +  /* If we transition from not resumed to resumed, we might need to add
>>>> +     the thread to the resumed threads with pending statuses list.  */
>>>> +  if (resumed)
>>>> +    proc_target->maybe_add_resumed_with_pending_wait_status (this);
>>>
>>> Longest function name award goes to...  ;-)
>>
>> Indeed!  If you have a name that is shorter but just as clear, I'm open
>> for suggestion.  But I prefer names that are non-ambiguous and use the
>> right terminology over names that are short just for convenience's sake.
> 
> Sure, as a preference, though that shouldn't be a too-strict rule IMO, otherwise
> with very long function names we can end up with awkward looking code as soon as we
> need to indent a caller a couple levels.  In this case, luckily that didn't happen,
> so I'm not really objecting.
> 
> "add_resumed_pending_status" or "add_resumed_pending_ws" would work as well for
> me, for example.

Ah you see, when I haven't touched some GDB code for a while, I would
see "ws" and wonder what that is again... is GDB using websockets now ;)
?

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-07-12 22:28         ` Simon Marchi
@ 2021-07-12 22:34           ` Simon Marchi
  2021-07-13 12:21           ` Pedro Alves
  1 sibling, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-12 22:34 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

On 2021-07-12 6:28 p.m., Simon Marchi via Gdb-patches wrote:
> Since this is only for debug message purposes, and I find it clearer to
> see the spelled-out ptid anyway (the pid, lwp and tid values), I added a
> to_string method to ptid_t and used that here:
> 
>     std::string
>     ptid_t::to_string () const
>     {
>       return string_printf ("%d.%d.%d", m_pid, m_lwp, m_tid);
>     }

The compiler was kind enough to remind me that the last two should be
%ld.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 00/11] Various thread lists optimizations
  2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
                   ` (10 preceding siblings ...)
  2021-06-22 16:57 ` [PATCH 11/11] gdb: optimize all_matching_threads_iterator Simon Marchi
@ 2021-07-13  0:47 ` Simon Marchi
  11 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-13  0:47 UTC (permalink / raw)
  To: gdb-patches



On 2021-06-22 12:56 p.m., Simon Marchi wrote:
> This series contains various optimizations written after profiling
> ROCm-GDB [1].  A typical ROCm program may have thousands of threads
> (waves), so everything that iterates often on the whole thread list pops
> up on the profile report.
> 
> Conceptually, the optimizations are:
> 
>  - maintain a ptid -> thread map in each inferior, to speed up the
>    thread lookups per ptid with find_thread_ptid
>  - make all_matching_threads_iterator a bit smarter, depending on the
>    filter_ptid passed, it can avoid iterating on all threads.  When the
>    filter_ptid points to a specific thread, it can use find_thread_ptid
>    to directly find the thread of interest, which is fast thanks to the
>    previous point
>  - maintain a per-process-target list of threads that are resumed and
>    have a pending event.  This helps speed up two hot path cases: when
>    we check if we want to re-enable commit-resumed, and when we want to
>    fetch a pending event.
> 
> Patches up to and including patch 6 are groundwork.  Notably, patch 2
> (thanks to Pedro) adds an intrusive_list type, which allows using
> intrusive linked lists in a very C++-y way without writing a ton of
> boilerplate.  This list type is useful for the patch that maintains a
> list of resumed threads with pending events, but we also converted the
> per-inferior thread list, the inferior list and the thread
> step-over-list to use it.  It helped iron out a few bugs and ensure the
> list works well for our purposes.
> 
> When debugging a ROCm test program whose threads continuously hit a
> condition breakpoint evaluation to false, the speedup observed with
> ROCm-GDB is around 5x (a run takes about 30 seconds before and 6 seconds
> after).
> 
> With x86-64 / Linux, it's less noticeable, because the time we spend in
> syscalls to fetch events and resume threads still dominates.  Still,
> when trying a program with 1000 threads hitting 100 times each a false
> conditional breakpoint, it goes from ~20 seconds to ~16 seconds.  But I
> don't really expect anything that noticeable in everyday use cases
> though.
> 
> [1] https://github.com/ROCm-Developer-Tools/ROCgdb
> 
> Pedro Alves (2):
>   gdb: introduce intrusive_list, make thread_info use it
>   gdb: make inferior_list use intrusive_list
> 
> Simon Marchi (9):
>   gdb: introduce iterator_range, remove next_adapter
>   gdb: use intrusive list for step-over chain
>   gdb: add setter / getter for thread_info resumed state
>   gdb: make thread_info::suspend private, add getters / setters
>   gdb: maintain per-process-target list of resumed threads with pending
>     wait status
>   gdb: optimize check for resumed threads with pending wait status in
>     maybe_set_commit_resumed_all_targets
>   gdb: optimize selection of resumed thread with pending event
>   gdb: maintain ptid -> thread map, optimize find_thread_ptid
>   gdb: optimize all_matching_threads_iterator
> 
>  gdb/Makefile.in                            |   1 +
>  gdb/ada-tasks.c                            |   4 +-
>  gdb/breakpoint.c                           |   7 +-
>  gdb/breakpoint.h                           |  10 +-
>  gdb/elf-none-tdep.c                        |   2 +-
>  gdb/fbsd-tdep.c                            |   6 +-
>  gdb/gcore.c                                |   4 +-
>  gdb/gdb-gdb.py.in                          |  91 ++-
>  gdb/gdb_bfd.h                              |   4 +-
>  gdb/gdbthread.h                            | 192 ++++-
>  gdb/infcmd.c                               |  33 +-
>  gdb/inferior-iter.h                        |  94 +--
>  gdb/inferior.c                             | 105 ++-
>  gdb/inferior.h                             |  34 +-
>  gdb/inflow.c                               |   2 +-
>  gdb/infrun.c                               | 476 ++++++------
>  gdb/infrun.h                               |   4 +-
>  gdb/linux-fork.c                           |   3 +-
>  gdb/linux-nat.c                            |  12 +-
>  gdb/linux-tdep.c                           |   2 +-
>  gdb/objfiles.h                             |   6 +-
>  gdb/process-stratum-target.c               |  77 ++
>  gdb/process-stratum-target.h               |  26 +
>  gdb/progspace.c                            |  11 +-
>  gdb/progspace.h                            |  45 +-
>  gdb/psymtab.h                              |   2 +-
>  gdb/python/py-inferior.c                   |   2 +-
>  gdb/record-btrace.c                        |   3 +-
>  gdb/record-full.c                          |   3 +-
>  gdb/regcache.c                             |   6 +-
>  gdb/remote.c                               |  68 +-
>  gdb/scoped-mock-context.h                  |  15 +-
>  gdb/solist.h                               |   2 +
>  gdb/symtab.h                               |  15 +-
>  gdb/thread-iter.c                          | 147 +++-
>  gdb/thread-iter.h                          |  61 +-
>  gdb/thread.c                               | 216 +++---
>  gdb/top.h                                  |   6 +-
>  gdb/unittests/intrusive_list-selftests.c   | 818 +++++++++++++++++++++
>  gdbsupport/intrusive_list.h                | 586 +++++++++++++++
>  gdbsupport/iterator-range.h                |  60 ++
>  gdbsupport/next-iterator.h                 |  32 +-
>  gdbsupport/reference-to-pointer-iterator.h |  82 +++
>  43 files changed, 2574 insertions(+), 801 deletions(-)
>  create mode 100644 gdb/unittests/intrusive_list-selftests.c
>  create mode 100644 gdbsupport/intrusive_list.h
>  create mode 100644 gdbsupport/iterator-range.h
>  create mode 100644 gdbsupport/reference-to-pointer-iterator.h
> 

I pushed this series after addressing Pedro's comments.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status
  2021-07-12 22:28         ` Simon Marchi
  2021-07-12 22:34           ` Simon Marchi
@ 2021-07-13 12:21           ` Pedro Alves
  1 sibling, 0 replies; 49+ messages in thread
From: Pedro Alves @ 2021-07-13 12:21 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 2021-07-12 11:28 p.m., Simon Marchi wrote:
>>> Arggg, we don't want that.  Since that is in a process_stratum_target
>>> method, I'll change the message to call the pid_to_str method of the
>>> current object instead:
>>>
>>>       infrun_debug_printf ("removing from resumed threads with event list: %s",
>>> 			   this->pid_to_str (thread->ptid).c_str ());
>>>
>>> Does that sound good?
>>
>> It does not.  That would mean a target higher on the stack wouldn't have a chance
>> at printing the thread.
> 
> Since this is only for debug message purposes, and I find it clearer to
> see the spelled-out ptid anyway (the pid, lwp and tid values), I added a
> to_string method to ptid_t and used that here:
> 
>     std::string
>     ptid_t::to_string () const
>     {
>       return string_printf ("%d.%d.%d", m_pid, m_lwp, m_tid);
>     }

+1.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 03/11] gdb: make inferior_list use intrusive_list
  2021-07-05 15:44   ` Pedro Alves
@ 2021-07-14  6:34     ` Tom de Vries
  2021-07-14 16:11       ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Tom de Vries @ 2021-07-14  6:34 UTC (permalink / raw)
  To: Pedro Alves, Simon Marchi, gdb-patches; +Cc: Simon Marchi

On 7/5/21 5:44 PM, Pedro Alves wrote:
> On 2021-06-22 5:56 p.m., Simon Marchi wrote:
>> From: Pedro Alves <pedro@palves.net>
>>
>> Change inferior_list, the global list of inferiors, to use
>> intrusive_list.  I think most other changes are somewhat obvious
>> fallouts from this change.
>>
>> There is a small change in behavior in scoped_mock_context.  Before this
>> patch, constructing a scoped_mock_context would replace the whole
>> inferior list with only the new mock inferior.  Tests using two
>> scoped_mock_contexts therefore needed to manually link the two inferiors
>> together, as the second scoped_mock_context would bump the first mock
>> inferior from the thread list.  With this patch, a scoped_mock_context
>> adds its mock inferior to the inferior list on construction, and removes
>> it on destruction.  This means that tests run with mock inferiors in the
>> inferior list in addition to any pre-existing inferiors (there is always
>> at least one).  There is no possible pid clash problem, since each
>> scoped mock inferior uses its own process target, and pids are per
>> process target.
> 

Starting this commit, I see this FAIL:
...
(gdb) PASS: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: no
threads left
info inferiors
  Num  Description       Connection           Executable
* 1    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  2    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  3    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  4    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  5    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  6    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  7    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  8    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  9    <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  10   <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

  11   <null>
/home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads

(gdb) FAIL: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: only
inferior 1 left
...

The test fails because it expects only inferior 1.

Is this a regression?

Thanks,
- Tom

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 11/11] gdb: optimize all_matching_threads_iterator
  2021-07-05 15:52   ` Pedro Alves
@ 2021-07-14  9:40     ` Tom de Vries
  0 siblings, 0 replies; 49+ messages in thread
From: Tom de Vries @ 2021-07-14  9:40 UTC (permalink / raw)
  To: Pedro Alves, Simon Marchi, gdb-patches

On 7/5/21 5:52 PM, Pedro Alves wrote:
> On 2021-06-22 5:57 p.m., Simon Marchi via Gdb-patches wrote:
>> all_matching_threads_iterator is used extensively in some pretty fast
>> paths, often under the all_non_exited_threads function.
>>
>> If a filter target and thread-specific ptid are given, it iterates on
>> all threads of all inferiors of that target, to ultimately yield exactly
>> on thread.  And this happens quite often, which means we unnecessarily
>> spend time iterating on threads to find the one we are looking for.  The
>> same thing happens if an inferior-specific ptid is given, although there
>> the iterator yields all the threads of that inferior.
>>
>> In those cases, the callers of all_non_exited_threads could have
>> different behaviors depending on the kind of ptid, to avoid this
>> inefficiency, but that would be very tedious.  Using
>> all_non_exited_threads has the advantage that one simple implementation
>> can work seamlessly on multiple threads or on one specific thread, just
>> by playing with the ptid.
>>
>> Instead, optimize all_matching_threads_iterator directly to detect these
>> different cases and limiting what we iterate on to just what we need.
>>
>>  - if filter_ptid is minus_one_ptid, do as we do now: filter inferiors
>>    based on filter_target, iterate on all of the matching inferiors'
>>    threads
>>  - if filter_ptid is a pid-only ptid (then a filter_target must
>>    necessarily be given), look up that inferior and iterate on all its
>>    threads
>>  - otherwise, filter_ptid is a thread-specific ptid, so look up that
>>    specific thread and "iterate" only on it
>>
>> For the last case, what was an iteration on all threads of the filter
>> target now becomes a call to find_thread_ptid, which is quite efficient
>> now thanks to inferior::ptid_thread_map.
>>
>> gdb/ChangeLog:
>>
>> 	* thread-iter.h (class all_matching_threads_iterator)
>> 	<all_matching_threads_iterator>: Use default.
>> 	<enum class mode>: New.
>> 	<m_inf, m_thr>: Initialize.
>> 	<m_filter_ptid>: Remove.
>> 	* thread-iter.c (all_matching_threads_iterator::m_inf_matches):
>> 	Don't filter on m_filter_ptid.
>> 	(all_matching_threads_iterator::all_matching_threads_iterator):
>> 	Choose path based on filter_ptid (all threads, all threads of
>> 	inferior, single thread).
>> 	(all_matching_threads_iterator::advance): Likewise.
> 
> OK.
> 

FTR, this caused an internal-error, filed at
https://sourceware.org/bugzilla/show_bug.cgi?id=28086 .

Thanks,
- Tom

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 03/11] gdb: make inferior_list use intrusive_list
  2021-07-14  6:34     ` Tom de Vries
@ 2021-07-14 16:11       ` Simon Marchi
  2021-07-14 20:15         ` [PATCH] gdb: make all_inferiors_safe actually work Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-07-14 16:11 UTC (permalink / raw)
  To: Tom de Vries, Pedro Alves, gdb-patches; +Cc: Simon Marchi



On 2021-07-14 2:34 a.m., Tom de Vries wrote:
> On 7/5/21 5:44 PM, Pedro Alves wrote:
>> On 2021-06-22 5:56 p.m., Simon Marchi wrote:
>>> From: Pedro Alves <pedro@palves.net>
>>>
>>> Change inferior_list, the global list of inferiors, to use
>>> intrusive_list.  I think most other changes are somewhat obvious
>>> fallouts from this change.
>>>
>>> There is a small change in behavior in scoped_mock_context.  Before this
>>> patch, constructing a scoped_mock_context would replace the whole
>>> inferior list with only the new mock inferior.  Tests using two
>>> scoped_mock_contexts therefore needed to manually link the two inferiors
>>> together, as the second scoped_mock_context would bump the first mock
>>> inferior from the thread list.  With this patch, a scoped_mock_context
>>> adds its mock inferior to the inferior list on construction, and removes
>>> it on destruction.  This means that tests run with mock inferiors in the
>>> inferior list in addition to any pre-existing inferiors (there is always
>>> at least one).  There is no possible pid clash problem, since each
>>> scoped mock inferior uses its own process target, and pids are per
>>> process target.
>>
> 
> Starting this commit, I see this FAIL:
> ...
> (gdb) PASS: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: no
> threads left
> info inferiors
>   Num  Description       Connection           Executable
> * 1    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   2    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   3    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   4    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   5    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   6    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   7    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   8    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   9    <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   10   <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
>   11   <null>
> /home/vries/gdb_versions/devel/build/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads
> 
> (gdb) FAIL: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: only
> inferior 1 left
> ...
> 
> The test fails because it expects only inferior 1.
> 
> Is this a regression?

I see this regression.  I might have dismissed those failures thinking
that they were part of the tests that are always racy, not sure.  But I
see it failing consistently starting with this patch.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH] gdb: make all_inferiors_safe actually work
  2021-07-14 16:11       ` Simon Marchi
@ 2021-07-14 20:15         ` Simon Marchi
  2021-07-15 10:15           ` Tom de Vries
  0 siblings, 1 reply; 49+ messages in thread
From: Simon Marchi @ 2021-07-14 20:15 UTC (permalink / raw)
  To: gdb-patches

The test gdb.threads/fork-plus-threads.exp fails since 08bdefb58b78
("gdb: make inferior_list use intrusive_list"):

    FAIL: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: only inferior 1 left

Looking at the log, we see that we are left with a bunch of inferiors in
the detach-on-fork=off case:

    info inferiors^M
      Num  Description       Connection           Executable        ^M
    * 1    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      2    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      3    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      4    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      5    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      6    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      7    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      8    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      9    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      10   <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
      11   <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
    (gdb) FAIL: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: only inferior 1 left

when we expect to have just one.  The problem is prune_inferiors not
pruning inferiors.  And this is caused by all_inferiors_safe not
actually iterating on inferiors.  The current implementation:

  inline all_inferiors_safe_range
  all_inferiors_safe ()
  {
    return {};
  }

default-constructs an all_inferiors_safe_range, which default-constructs
an all_inferiors_safe_iterator as its m_begin field, which
default-constructs a all_inferiors_iterator.  A default-constructed
all_inferiors_iterator is an end iterator, which means we have
constructed an (end,end) all_inferiors_safe_range.

We actually need to pass down the list on which we want to iterator
(that is the inferior_list global), so that all_inferiors_iterator's
first constructor is chosen.  We also pass nullptr as the proc_target
filter.  In this case, we don't do any filtering, but if in the future
all_inferiors_safe needed to allow filtering on process target (like
all_inferiors does), we could pass down a process target pointer.

basic_safe_iterator's constructor needs to be changed to allow
constructing the wrapped iterator with multiple arguments, not just one.

With this, gdb.threads/fork-plus-threads.exp is passing once again for
me.

Change-Id: I650552ede596e3590c4b7606ce403690a0278a01
---
 gdb/inferior.h             |  2 +-
 gdbsupport/safe-iterator.h | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/gdb/inferior.h b/gdb/inferior.h
index 6662a3bde463..94fbac0fc571 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -676,7 +676,7 @@ extern intrusive_list<inferior> inferior_list;
 inline all_inferiors_safe_range
 all_inferiors_safe ()
 {
-  return {};
+  return all_inferiors_safe_range (nullptr, inferior_list);
 }
 
 /* Returns a range representing all inferiors, suitable to use with
diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
index 5cfc5b6ee692..53868d3b3eec 100644
--- a/gdbsupport/safe-iterator.h
+++ b/gdbsupport/safe-iterator.h
@@ -48,11 +48,11 @@ class basic_safe_iterator
   typedef typename Iterator::iterator_category iterator_category;
   typedef typename Iterator::difference_type difference_type;
 
-  /* Construct using the given argument; the end iterator is default
-     constructed.  */
-  template<typename Arg>
-  explicit basic_safe_iterator (Arg &&arg)
-    : m_it (std::forward<Arg> (arg)),
+  /* Construct the begin iterator using the given arguments; the end iterator is
+     default constructed.  */
+  template<typename... Args>
+  explicit basic_safe_iterator (Args &&...args)
+    : m_it (std::forward<Args> (args)...),
       m_next (m_it)
   {
     if (m_it != m_end)
-- 
2.32.0


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] gdb: make all_inferiors_safe actually work
  2021-07-14 20:15         ` [PATCH] gdb: make all_inferiors_safe actually work Simon Marchi
@ 2021-07-15 10:15           ` Tom de Vries
  2021-07-17 12:54             ` Simon Marchi
  0 siblings, 1 reply; 49+ messages in thread
From: Tom de Vries @ 2021-07-15 10:15 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches

On 7/14/21 10:15 PM, Simon Marchi wrote:
> The test gdb.threads/fork-plus-threads.exp fails since 08bdefb58b78
> ("gdb: make inferior_list use intrusive_list"):
> 
>     FAIL: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: only inferior 1 left
> 
> Looking at the log, we see that we are left with a bunch of inferiors in
> the detach-on-fork=off case:
> 
>     info inferiors^M
>       Num  Description       Connection           Executable        ^M
>     * 1    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       2    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       3    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       4    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       5    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       6    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       7    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       8    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       9    <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       10   <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>       11   <null>                                 /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads ^M
>     (gdb) FAIL: gdb.threads/fork-plus-threads.exp: detach-on-fork=off: only inferior 1 left
> 
> when we expect to have just one.  The problem is prune_inferiors not
> pruning inferiors.  And this is caused by all_inferiors_safe not
> actually iterating on inferiors.  The current implementation:
> 
>   inline all_inferiors_safe_range
>   all_inferiors_safe ()
>   {
>     return {};
>   }
> 
> default-constructs an all_inferiors_safe_range, which default-constructs
> an all_inferiors_safe_iterator as its m_begin field, which
> default-constructs a all_inferiors_iterator.  A default-constructed
> all_inferiors_iterator is an end iterator, which means we have
> constructed an (end,end) all_inferiors_safe_range.
> 
> We actually need to pass down the list on which we want to iterator
> (that is the inferior_list global), so that all_inferiors_iterator's
> first constructor is chosen.  We also pass nullptr as the proc_target
> filter.  In this case, we don't do any filtering, but if in the future
> all_inferiors_safe needed to allow filtering on process target (like
> all_inferiors does), we could pass down a process target pointer.
> 
> basic_safe_iterator's constructor needs to be changed to allow
> constructing the wrapped iterator with multiple arguments, not just one.
> 
> With this, gdb.threads/fork-plus-threads.exp is passing once again for
> me.
> 

For me as well.

LGTM.

One suggestion: I'd do
s%/home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads%fork-plus-threads%
in the log message for readability.

Thanks,
- Tom

> Change-Id: I650552ede596e3590c4b7606ce403690a0278a01
> ---
>  gdb/inferior.h             |  2 +-
>  gdbsupport/safe-iterator.h | 10 +++++-----
>  2 files changed, 6 insertions(+), 6 deletions(-)
> 
> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index 6662a3bde463..94fbac0fc571 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -676,7 +676,7 @@ extern intrusive_list<inferior> inferior_list;
>  inline all_inferiors_safe_range
>  all_inferiors_safe ()
>  {
> -  return {};
> +  return all_inferiors_safe_range (nullptr, inferior_list);
>  }
>  
>  /* Returns a range representing all inferiors, suitable to use with
> diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
> index 5cfc5b6ee692..53868d3b3eec 100644
> --- a/gdbsupport/safe-iterator.h
> +++ b/gdbsupport/safe-iterator.h
> @@ -48,11 +48,11 @@ class basic_safe_iterator
>    typedef typename Iterator::iterator_category iterator_category;
>    typedef typename Iterator::difference_type difference_type;
>  
> -  /* Construct using the given argument; the end iterator is default
> -     constructed.  */
> -  template<typename Arg>
> -  explicit basic_safe_iterator (Arg &&arg)
> -    : m_it (std::forward<Arg> (arg)),
> +  /* Construct the begin iterator using the given arguments; the end iterator is
> +     default constructed.  */
> +  template<typename... Args>
> +  explicit basic_safe_iterator (Args &&...args)
> +    : m_it (std::forward<Args> (args)...),
>        m_next (m_it)
>    {
>      if (m_it != m_end)
> 

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] gdb: make all_inferiors_safe actually work
  2021-07-15 10:15           ` Tom de Vries
@ 2021-07-17 12:54             ` Simon Marchi
  0 siblings, 0 replies; 49+ messages in thread
From: Simon Marchi @ 2021-07-17 12:54 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches

On 2021-07-15 6:15 a.m., Tom de Vries wrote:
> For me as well.
> 
> LGTM.
> 
> One suggestion: I'd do
> s%/home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.threads/fork-plus-threads/fork-plus-threads%fork-plus-threads%
> in the log message for readability.

Do you mean in the snippet from gdb.log I pasted?  I replaced those
with

  <snip>/fork-plus-threads

to make it clear that they were hand-edited.

Pushed with that fixed, thanks.

Simon

^ permalink raw reply	[flat|nested] 49+ messages in thread

end of thread, other threads:[~2021-07-17 12:55 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-22 16:56 [PATCH 00/11] Various thread lists optimizations Simon Marchi
2021-06-22 16:56 ` [PATCH 01/11] gdb: introduce iterator_range, remove next_adapter Simon Marchi
2021-07-05 15:41   ` Pedro Alves
2021-07-06 19:16     ` Simon Marchi
2021-06-22 16:56 ` [PATCH 02/11] gdb: introduce intrusive_list, make thread_info use it Simon Marchi
2021-06-22 23:13   ` Lancelot SIX
2021-06-23  0:48     ` Simon Marchi
2021-07-05 15:44   ` Pedro Alves
2021-07-06 19:38     ` Simon Marchi
2021-07-06 20:45       ` Simon Marchi
2021-07-06 21:04         ` Pedro Alves
2021-07-06 21:38           ` Simon Marchi
2021-07-06 21:02       ` Pedro Alves
2021-07-06 21:45         ` Simon Marchi
2021-07-07 11:46           ` Pedro Alves
2021-07-07 13:52             ` Simon Marchi
2021-06-22 16:56 ` [PATCH 03/11] gdb: make inferior_list use intrusive_list Simon Marchi
2021-07-05 15:44   ` Pedro Alves
2021-07-14  6:34     ` Tom de Vries
2021-07-14 16:11       ` Simon Marchi
2021-07-14 20:15         ` [PATCH] gdb: make all_inferiors_safe actually work Simon Marchi
2021-07-15 10:15           ` Tom de Vries
2021-07-17 12:54             ` Simon Marchi
2021-06-22 16:56 ` [PATCH 04/11] gdb: use intrusive list for step-over chain Simon Marchi
2021-07-05 15:45   ` Pedro Alves
2021-07-06 20:59     ` Simon Marchi
2021-06-22 16:56 ` [PATCH 05/11] gdb: add setter / getter for thread_info resumed state Simon Marchi
2021-07-05 15:45   ` Pedro Alves
2021-06-22 16:56 ` [PATCH 06/11] gdb: make thread_info::suspend private, add getters / setters Simon Marchi
2021-07-05 15:45   ` Pedro Alves
2021-06-22 16:57 ` [PATCH 07/11] gdb: maintain per-process-target list of resumed threads with pending wait status Simon Marchi
2021-07-05 15:51   ` Pedro Alves
2021-07-06 21:25     ` Simon Marchi
2021-07-07 12:01       ` Pedro Alves
2021-07-12 22:28         ` Simon Marchi
2021-07-12 22:34           ` Simon Marchi
2021-07-13 12:21           ` Pedro Alves
2021-06-22 16:57 ` [PATCH 08/11] gdb: optimize check for resumed threads with pending wait status in maybe_set_commit_resumed_all_targets Simon Marchi
2021-07-05 15:51   ` Pedro Alves
2021-06-22 16:57 ` [PATCH 09/11] gdb: optimize selection of resumed thread with pending event Simon Marchi
2021-07-05 15:51   ` Pedro Alves
2021-06-22 16:57 ` [PATCH 10/11] gdb: maintain ptid -> thread map, optimize find_thread_ptid Simon Marchi
2021-07-05 15:52   ` Pedro Alves
2021-07-06 21:31     ` Simon Marchi
2021-07-07 12:13       ` Pedro Alves
2021-06-22 16:57 ` [PATCH 11/11] gdb: optimize all_matching_threads_iterator Simon Marchi
2021-07-05 15:52   ` Pedro Alves
2021-07-14  9:40     ` Tom de Vries
2021-07-13  0:47 ` [PATCH 00/11] Various thread lists optimizations Simon Marchi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).