public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r14-4006] diagnostics: support multithreaded diagnostic paths
@ 2023-09-14 20:29 David Malcolm
  0 siblings, 0 replies; only message in thread
From: David Malcolm @ 2023-09-14 20:29 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:3a1e9f3ed7aa49adad02190ace0614e0b37fc089

commit r14-4006-g3a1e9f3ed7aa49adad02190ace0614e0b37fc089
Author: David Malcolm <dmalcolm@redhat.com>
Date:   Thu Sep 14 16:28:45 2023 -0400

    diagnostics: support multithreaded diagnostic paths
    
    This patch extends the existing diagnostic_path class so that as well
    as list of events, there is a list of named threads, with each event
    being associated with one of the threads.
    
    No GCC diagnostics take advantage of this, but GCC plugins may find a
    use for this; an example is provided in the testsuite.
    
    Given that there is still a single list of events within a
    diagnostic_path, the events in a diagnostic_path have a specific global
    ordering even if they are in multiple threads.
    
    Within the SARIF serialization, the patch adds the "executionOrder"
    property to threadFlowLocation objects (SARIF v2.1.0 3.38.11).  This is
    1-based in order to match the human-readable numbering of events shown
    in messages emitted by pretty-printer.cc's "%@".
    
    With -fdiagnostics-path-format=separate-events, the threads are not
    shown.
    
    With -fdiagnostics-path-format=inline-events, the threads and the
    per-thread stack activity are tracked and visalized separately.  An
    example can be seen in the testsuite.
    
    gcc/analyzer/ChangeLog:
            * checker-event.h (checker_event::get_thread_id): New.
            * checker-path.h (class checker_path): Implement thread-related
            vfuncs via a single simple_diagnostic_thread instance named
            "main".
    
    gcc/ChangeLog:
            * diagnostic-event-id.h (diagnostic_thread_id_t): New typedef.
            * diagnostic-format-sarif.cc (class sarif_thread_flow): New.
            (sarif_thread_flow::sarif_thread_flow): New.
            (sarif_builder::make_code_flow_object): Reimplement, creating
            per-thread threadFlow objects, populating them with the relevant
            events.
            (sarif_builder::make_thread_flow_object): Delete, moving the
            code into sarif_builder::make_code_flow_object.
            (sarif_builder::make_thread_flow_location_object): Add
            "path_event_idx" param.  Use it to set "executionOrder"
            property.
            * diagnostic-path.h (diagnostic_event::get_thread_id): New
            pure-virtual vfunc.
            (class diagnostic_thread): New.
            (diagnostic_path::num_threads): New pure-virtual vfunc.
            (diagnostic_path::get_thread):  New pure-virtual vfunc.
            (diagnostic_path::multithreaded_p): New decl.
            (simple_diagnostic_event::simple_diagnostic_event): Add optional
            thread_id param.
            (simple_diagnostic_event::get_thread_id): New accessor.
            (simple_diagnostic_event::m_thread_id): New.
            (class simple_diagnostic_thread): New.
            (simple_diagnostic_path::simple_diagnostic_path): Move definition
            to diagnostic.cc.
            (simple_diagnostic_path::num_threads): New.
            (simple_diagnostic_path::get_thread): New.
            (simple_diagnostic_path::add_thread): New.
            (simple_diagnostic_path::add_thread_event): New.
            (simple_diagnostic_path::m_threads): New.
            * diagnostic-show-locus.cc (layout::layout): Add pretty_printer
            param for overriding the context's printer.
            (diagnostic_show_locus): Likwise.
            * diagnostic.cc (simple_diagnostic_path::simple_diagnostic_path):
            Move here from diagnostic-path.h.  Add main thread.
            (simple_diagnostic_path::num_threads): New.
            (simple_diagnostic_path::get_thread): New.
            (simple_diagnostic_path::add_thread): New.
            (simple_diagnostic_path::add_thread_event): New.
            (simple_diagnostic_event::simple_diagnostic_event): Add thread_id
            param and use it to initialize m_thread_id.  Reformat.
            * diagnostic.h: Add pretty_printer param for overriding the
            context's printer.
            * tree-diagnostic-path.cc: Add #define INCLUDE_VECTOR.
            (can_consolidate_events): Compare thread ids.
            (class per_thread_summary): New.
            (event_range::event_range): Add per_thread_summary arg.
            (event_range::print): Add "pp" param and use it rather than dc's
            printer.
            (event_range::m_thread_id): New field.
            (event_range::m_per_thread_summary): New field.
            (path_summary::multithreaded_p): New.
            (path_summary::get_events_for_thread_id): New.
            (path_summary::m_per_thread_summary): New field.
            (path_summary::m_thread_id_to_events): New field.
            (path_summary::get_or_create_events_for_thread_id): New.
            (path_summary::path_summary): Create per_thread_summary instances
            as needed and associate the event_range instances with them.
            (base_indent): Move here from print_path_summary_as_text.
            (per_frame_indent): Likewise.
            (class thread_event_printer): New, adapted from parts of
            print_path_summary_as_text.
            (print_path_summary_as_text): Make static.  Reimplement to
            moving most of existing code to class thread_event_printer,
            capturing state as per-thread as appropriate.
            (default_tree_diagnostic_path_printer): Add missing 'break' on
            final case.
    
    gcc/testsuite/ChangeLog:
            * gcc.dg/plugin/diagnostic-test-paths-multithreaded-inline-events.c:
            New test.
            * gcc.dg/plugin/diagnostic-test-paths-multithreaded-sarif.c: New
            test.
            * gcc.dg/plugin/diagnostic-test-paths-multithreaded-separate-events.c:
            New test.
            * gcc.dg/plugin/diagnostic_plugin_test_paths.c: Add support for
            generating multithreaded paths.
            * gcc.dg/plugin/plugin.exp: Add the new tests.
    
    Signed-off-by: David Malcolm <dmalcolm@redhat.com>

Diff:
---
 gcc/analyzer/checker-event.h                       |   4 +
 gcc/analyzer/checker-path.h                        |  17 +-
 gcc/diagnostic-event-id.h                          |   5 +
 gcc/diagnostic-format-sarif.cc                     |  86 ++--
 gcc/diagnostic-path.h                              |  55 ++-
 gcc/diagnostic-show-locus.cc                       |  16 +-
 gcc/diagnostic.cc                                  |  78 +++-
 gcc/diagnostic.h                                   |   3 +-
 ...nostic-test-paths-multithreaded-inline-events.c |  72 ++++
 .../diagnostic-test-paths-multithreaded-sarif.c    |  35 ++
 ...stic-test-paths-multithreaded-separate-events.c |  18 +
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   |  94 ++++-
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   3 +
 gcc/tree-diagnostic-path.cc                        | 432 ++++++++++++++-------
 14 files changed, 734 insertions(+), 184 deletions(-)

diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h
index 5dd25cb0775..7ba92f19650 100644
--- a/gcc/analyzer/checker-event.h
+++ b/gcc/analyzer/checker-event.h
@@ -113,6 +113,10 @@ public:
       return NULL;
   }
   meaning get_meaning () const override;
+  diagnostic_thread_id_t get_thread_id () const final override
+  {
+    return 0;
+  }
 
   /* Additional functionality.  */
 
diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h
index 627f64e5320..93c807c3d24 100644
--- a/gcc/analyzer/checker-path.h
+++ b/gcc/analyzer/checker-path.h
@@ -30,7 +30,11 @@ namespace ana {
 class checker_path : public diagnostic_path
 {
 public:
-  checker_path (logger *logger) : diagnostic_path (), m_logger (logger) {}
+  checker_path (logger *logger)
+  : diagnostic_path (),
+    m_thread ("main"),
+    m_logger (logger)
+  {}
 
   /* Implementation of diagnostic_path vfuncs.  */
 
@@ -43,6 +47,15 @@ public:
   {
     return *m_events[idx];
   }
+  unsigned num_threads () const final override
+  {
+    return 1;
+  }
+  const diagnostic_thread &
+  get_thread (diagnostic_thread_id_t) const final override
+  {
+    return m_thread;
+  }
 
   checker_event *get_checker_event (int idx)
   {
@@ -120,6 +133,8 @@ public:
 private:
   DISABLE_COPY_AND_ASSIGN(checker_path);
 
+  simple_diagnostic_thread m_thread;
+
   /* The events that have occurred along this path.  */
   auto_delete_vec<checker_event> m_events;
 
diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h
index 84f4b65611e..c5f5d60ddc1 100644
--- a/gcc/diagnostic-event-id.h
+++ b/gcc/diagnostic-event-id.h
@@ -58,4 +58,9 @@ class diagnostic_event_id_t
    The %@ format code requires that known_p be true for the event ID. */
 typedef diagnostic_event_id_t *diagnostic_event_id_ptr;
 
+/* A type for compactly referring to a particular thread within a
+   diagnostic_path.  Typically there is just one thread per path,
+   with id 0.  */
+typedef unsigned diagnostic_thread_id_t;
+
 #endif /* ! GCC_DIAGNOSTIC_EVENT_ID_H */
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 1eff71962d7..f56c4ce033d 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -94,6 +94,23 @@ public:
 			  sarif_builder *builder);
 };
 
+/* Subclass of sarif_object for SARIF threadFlow objects
+   (SARIF v2.1.0 section 3.37) for PATH.  */
+
+class sarif_thread_flow : public sarif_object
+{
+public:
+  sarif_thread_flow (const diagnostic_thread &thread);
+
+  void add_location (json::object *thread_flow_loc_obj)
+  {
+    m_locations_arr->append (thread_flow_loc_obj);
+  }
+
+private:
+  json::array *m_locations_arr;
+};
+
 /* A class for managing SARIF output (for -fdiagnostics-format=sarif-stderr
    and -fdiagnostics-format=sarif-file).
 
@@ -168,9 +185,9 @@ private:
   json::object *
   make_logical_location_object (const logical_location &logical_loc) const;
   json::object *make_code_flow_object (const diagnostic_path &path);
-  json::object *make_thread_flow_object (const diagnostic_path &path);
   json::object *
-  make_thread_flow_location_object (const diagnostic_event &event);
+  make_thread_flow_location_object (const diagnostic_event &event,
+				    int path_event_idx);
   json::array *maybe_make_kinds_array (diagnostic_event::meaning m) const;
   json::object *maybe_make_physical_location_object (location_t loc);
   json::object *make_artifact_location_object (location_t loc);
@@ -365,6 +382,19 @@ sarif_ice_notification::sarif_ice_notification (diagnostic_context *context,
   set ("level", new json::string ("error"));
 }
 
+/* class sarif_thread_flow : public sarif_object.  */
+
+sarif_thread_flow::sarif_thread_flow (const diagnostic_thread &thread)
+{
+  /* "id" property (SARIF v2.1.0 section 3.37.2).  */
+  label_text name (thread.get_name (false));
+  set ("id", new json::string (name.get ()));
+
+  /* "locations" property (SARIF v2.1.0 section 3.37.6).  */
+  m_locations_arr = new json::array ();
+  set ("locations", m_locations_arr);
+}
+
 /* class sarif_builder.  */
 
 /* sarif_builder's ctor.  */
@@ -1091,41 +1121,44 @@ sarif_builder::make_code_flow_object (const diagnostic_path &path)
 {
   json::object *code_flow_obj = new json::object ();
 
-  /* "threadFlows" property (SARIF v2.1.0 section 3.36.3).
-     Currently we only support one thread per result.  */
+  /* "threadFlows" property (SARIF v2.1.0 section 3.36.3).  */
   json::array *thread_flows_arr = new json::array ();
-  json::object *thread_flow_obj = make_thread_flow_object (path);
-  thread_flows_arr->append (thread_flow_obj);
-  code_flow_obj->set ("threadFlows", thread_flows_arr);
 
-  return code_flow_obj;
-}
-
-/* Make a threadFlow object (SARIF v2.1.0 section 3.37) for PATH.  */
-
-json::object *
-sarif_builder::make_thread_flow_object (const diagnostic_path &path)
-{
-  json::object *thread_flow_obj = new json::object ();
-
-  /* "locations" property (SARIF v2.1.0 section 3.37.6).  */
-  json::array *locations_arr = new json::array ();
+  /* Walk the events, consolidating into per-thread threadFlow objects,
+     using the index with PATH as the overall executionOrder.  */
+  hash_map<int_hash<diagnostic_thread_id_t, -1, -2>,
+	   sarif_thread_flow *> thread_id_map;
   for (unsigned i = 0; i < path.num_events (); i++)
     {
       const diagnostic_event &event = path.get_event (i);
+      const diagnostic_thread_id_t thread_id = event.get_thread_id ();
+      sarif_thread_flow *thread_flow_obj;
+
+      if (sarif_thread_flow **slot = thread_id_map.get (thread_id))
+	thread_flow_obj = *slot;
+      else
+	{
+	  const diagnostic_thread &thread = path.get_thread (thread_id);
+	  thread_flow_obj = new sarif_thread_flow (thread);
+	  thread_flows_arr->append (thread_flow_obj);
+	  thread_id_map.put (thread_id, thread_flow_obj);
+	}
+
+      /* Add event to thread's threadFlow object.  */
       json::object *thread_flow_loc_obj
-	= make_thread_flow_location_object (event);
-      locations_arr->append (thread_flow_loc_obj);
+	= make_thread_flow_location_object (event, i);
+      thread_flow_obj->add_location (thread_flow_loc_obj);
     }
-  thread_flow_obj->set ("locations", locations_arr);
+  code_flow_obj->set ("threadFlows", thread_flows_arr);
 
-  return thread_flow_obj;
+  return code_flow_obj;
 }
 
 /* Make a threadFlowLocation object (SARIF v2.1.0 section 3.38) for EVENT.  */
 
 json::object *
-sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev)
+sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev,
+						 int path_event_idx)
 {
   json::object *thread_flow_loc_obj = new json::object ();
 
@@ -1142,6 +1175,11 @@ sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev)
   thread_flow_loc_obj->set ("nestingLevel",
 			    new json::integer_number (ev.get_stack_depth ()));
 
+  /* "executionOrder" property (SARIF v2.1.0 3.38.11).
+     Offset by 1 to match the human-readable values emitted by %@.  */
+  thread_flow_loc_obj->set ("executionOrder",
+			    new json::integer_number (path_event_idx + 1));
+
   /* It might be nice to eventually implement the following for -fanalyzer:
      - the "stack" property (SARIF v2.1.0 section 3.38.5)
      - the "state" property (SARIF v2.1.0 section 3.38.9)
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
index 9d9d6296eb0..d39872abb9f 100644
--- a/gcc/diagnostic-path.h
+++ b/gcc/diagnostic-path.h
@@ -155,6 +155,20 @@ class diagnostic_event
   virtual const logical_location *get_logical_location () const = 0;
 
   virtual meaning get_meaning () const = 0;
+
+  virtual diagnostic_thread_id_t get_thread_id () const = 0;
+};
+
+/* Abstract base class representing a thread of execution within
+   a diagnostic_path.
+   Each diagnostic_event is associated with one thread.
+   Typically there is just one thread per diagnostic_path. */
+
+class diagnostic_thread
+{
+public:
+  virtual ~diagnostic_thread () {}
+  virtual label_text get_name (bool can_colorize) const = 0;
 };
 
 /* Abstract base class for getting at a sequence of events.  */
@@ -165,8 +179,12 @@ class diagnostic_path
   virtual ~diagnostic_path () {}
   virtual unsigned num_events () const = 0;
   virtual const diagnostic_event & get_event (int idx) const = 0;
+  virtual unsigned num_threads () const = 0;
+  virtual const diagnostic_thread &
+  get_thread (diagnostic_thread_id_t) const = 0;
 
   bool interprocedural_p () const;
+  bool multithreaded_p () const;
 
 private:
   bool get_first_event_in_a_function (unsigned *out_idx) const;
@@ -180,7 +198,8 @@ class simple_diagnostic_event : public diagnostic_event
 {
  public:
   simple_diagnostic_event (location_t loc, tree fndecl, int depth,
-			   const char *desc);
+			   const char *desc,
+			   diagnostic_thread_id_t thread_id = 0);
   ~simple_diagnostic_event ();
 
   location_t get_location () const final override { return m_loc; }
@@ -198,12 +217,32 @@ class simple_diagnostic_event : public diagnostic_event
   {
     return meaning ();
   }
+  diagnostic_thread_id_t get_thread_id () const final override
+  {
+    return m_thread_id;
+  }
 
  private:
   location_t m_loc;
   tree m_fndecl;
   int m_depth;
   char *m_desc; // has been i18n-ed and formatted
+  diagnostic_thread_id_t m_thread_id;
+};
+
+/* A simple implementation of diagnostic_thread.  */
+
+class simple_diagnostic_thread : public diagnostic_thread
+{
+public:
+  simple_diagnostic_thread (const char *name) : m_name (name) {}
+  label_text get_name (bool) const final override
+  {
+    return label_text::borrow (m_name);
+  }
+
+private:
+  const char *m_name; // has been i18n-ed and formatted
 };
 
 /* A simple implementation of diagnostic_path, as a vector of
@@ -212,17 +251,27 @@ class simple_diagnostic_event : public diagnostic_event
 class simple_diagnostic_path : public diagnostic_path
 {
  public:
-  simple_diagnostic_path (pretty_printer *event_pp)
-  : m_event_pp (event_pp) {}
+  simple_diagnostic_path (pretty_printer *event_pp);
 
   unsigned num_events () const final override;
   const diagnostic_event & get_event (int idx) const final override;
+  unsigned num_threads () const final override;
+  const diagnostic_thread &
+  get_thread (diagnostic_thread_id_t) const final override;
+
+  diagnostic_thread_id_t add_thread (const char *name);
 
   diagnostic_event_id_t add_event (location_t loc, tree fndecl, int depth,
 				   const char *fmt, ...)
     ATTRIBUTE_GCC_DIAG(5,6);
+  diagnostic_event_id_t
+  add_thread_event (diagnostic_thread_id_t thread_id,
+		    location_t loc, tree fndecl, int depth,
+		    const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(6,7);
 
  private:
+  auto_delete_vec<simple_diagnostic_thread> m_threads;
   auto_delete_vec<simple_diagnostic_event> m_events;
 
   /* (for use by add_event).  */
diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index 0514815b51f..31ef85151fd 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -367,7 +367,8 @@ class layout
  public:
   layout (diagnostic_context *context,
 	  rich_location *richloc,
-	  diagnostic_t diagnostic_kind);
+	  diagnostic_t diagnostic_kind,
+	  pretty_printer *pp = nullptr);
 
   bool maybe_add_location_range (const location_range *loc_range,
 				 unsigned original_idx,
@@ -1183,9 +1184,10 @@ make_policy (const diagnostic_context &dc,
 
 layout::layout (diagnostic_context * context,
 		rich_location *richloc,
-		diagnostic_t diagnostic_kind)
+		diagnostic_t diagnostic_kind,
+		pretty_printer *pp)
 : m_context (context),
-  m_pp (context->printer),
+  m_pp (pp ? pp : context->printer),
   m_policy (make_policy (*context, *richloc)),
   m_primary_loc (richloc->get_range (0)->m_loc),
   m_exploc (richloc->get_expanded_location (0), m_policy,
@@ -2825,12 +2827,14 @@ gcc_rich_location::add_location_if_nearby (location_t loc,
 }
 
 /* Print the physical source code corresponding to the location of
-   this diagnostic, with additional annotations.  */
+   this diagnostic, with additional annotations.
+   If PP is non-null, then use it rather than CONTEXT's printer.  */
 
 void
 diagnostic_show_locus (diagnostic_context * context,
 		       rich_location *richloc,
-		       diagnostic_t diagnostic_kind)
+		       diagnostic_t diagnostic_kind,
+		       pretty_printer *pp)
 {
   location_t loc = richloc->get_loc ();
   /* Do nothing if source-printing has been disabled.  */
@@ -2851,7 +2855,7 @@ diagnostic_show_locus (diagnostic_context * context,
 
   context->last_location = loc;
 
-  layout layout (context, richloc, diagnostic_kind);
+  layout layout (context, richloc, diagnostic_kind, pp);
   for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
        line_span_idx++)
     {
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 65c0cfbf11a..00183b10700 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -2404,6 +2404,14 @@ diagnostics_text_art_charset_init (diagnostic_context *context,
     }
 }
 
+/* class simple_diagnostic_path : public diagnostic_path.  */
+
+simple_diagnostic_path::simple_diagnostic_path (pretty_printer *event_pp)
+  : m_event_pp (event_pp)
+{
+  add_thread ("main");
+}
+
 /* Implementation of diagnostic_path::num_events vfunc for
    simple_diagnostic_path: simply get the number of events in the vec.  */
 
@@ -2422,6 +2430,25 @@ simple_diagnostic_path::get_event (int idx) const
   return *m_events[idx];
 }
 
+unsigned
+simple_diagnostic_path::num_threads () const
+{
+  return m_threads.length ();
+}
+
+const diagnostic_thread &
+simple_diagnostic_path::get_thread (diagnostic_thread_id_t idx) const
+{
+  return *m_threads[idx];
+}
+
+diagnostic_thread_id_t
+simple_diagnostic_path::add_thread (const char *name)
+{
+  m_threads.safe_push (new simple_diagnostic_thread (name));
+  return m_threads.length () - 1;
+}
+
 /* Add an event to this path at LOC within function FNDECL at
    stack depth DEPTH.
 
@@ -2464,15 +2491,56 @@ simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth,
   return diagnostic_event_id_t (m_events.length () - 1);
 }
 
+diagnostic_event_id_t
+simple_diagnostic_path::add_thread_event (diagnostic_thread_id_t thread_id,
+					  location_t loc,
+					  tree fndecl,
+					  int depth,
+					  const char *fmt, ...)
+{
+  pretty_printer *pp = m_event_pp;
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  simple_diagnostic_event *new_event
+    = new simple_diagnostic_event (loc, fndecl, depth, pp_formatted_text (pp),
+				   thread_id);
+  m_events.safe_push (new_event);
+
+  pp_clear_output_area (pp);
+
+  return diagnostic_event_id_t (m_events.length () - 1);
+}
+
 /* struct simple_diagnostic_event.  */
 
 /* simple_diagnostic_event's ctor.  */
 
-simple_diagnostic_event::simple_diagnostic_event (location_t loc,
-						  tree fndecl,
-						  int depth,
-						  const char *desc)
-: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
+simple_diagnostic_event::
+simple_diagnostic_event (location_t loc,
+			 tree fndecl,
+			 int depth,
+			 const char *desc,
+			 diagnostic_thread_id_t thread_id)
+: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc)),
+  m_thread_id (thread_id)
 {
 }
 
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 00b828f230d..4ec83a988d5 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -509,7 +509,8 @@ extern void diagnostic_finish (diagnostic_context *);
 extern void diagnostic_report_current_module (diagnostic_context *, location_t);
 extern void diagnostic_show_locus (diagnostic_context *,
 				   rich_location *richloc,
-				   diagnostic_t diagnostic_kind);
+				   diagnostic_t diagnostic_kind,
+				   pretty_printer *pp = nullptr);
 extern void diagnostic_show_any_path (diagnostic_context *, diagnostic_info *);
 
 /* Because we read source files a second time after the frontend did it the
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-inline-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-inline-events.c
new file mode 100644
index 00000000000..333ef735944
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-inline-events.c
@@ -0,0 +1,72 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+extern void acquire_lock_a(void);
+extern void acquire_lock_b(void);
+
+void foo ()
+{
+  acquire_lock_a ();
+  acquire_lock_b ();
+}
+
+void bar ()
+{
+  acquire_lock_b ();
+  acquire_lock_a (); /* { dg-warning "deadlock due to inconsistent lock acquisition order" } */
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |   acquire_lock_a ();
+      |   ^~~~~~~~~~~~~~~~~
+Thread: 'Thread 1'
+  'foo': event 1
+    |
+    |   NN | {
+    |      | ^
+    |      | |
+    |      | (1) entering 'foo'
+    |
+    +--> 'foo': event 2
+           |
+           |   NN |   acquire_lock_a ();
+           |      |   ^~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (2) lock a is now held by thread 1
+           |
+
+Thread: 'Thread 2'
+  'bar': event 3
+    |
+    |   NN | {
+    |      | ^
+    |      | |
+    |      | (3) entering 'bar'
+    |
+    +--> 'bar': event 4
+           |
+           |   NN |   acquire_lock_b ();
+           |      |   ^~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (4) lock b is now held by thread 2
+           |
+
+Thread: 'Thread 1'
+         'foo': event 5
+           |
+           |   NN |   acquire_lock_b ();
+           |      |   ^~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (5) deadlocked due to waiting for lock b in thread 1...
+           |
+
+Thread: 'Thread 2'
+         'bar': event 6
+           |
+           |   NN |   acquire_lock_a ();
+           |      |   ^~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (6) ...whilst waiting for lock a in thread 2
+           |
+     { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-sarif.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-sarif.c
new file mode 100644
index 00000000000..727d1bb6469
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-sarif.c
@@ -0,0 +1,35 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=sarif-file" } */
+
+extern void acquire_lock_a(void);
+extern void acquire_lock_b(void);
+
+void foo ()
+{
+  acquire_lock_a ();
+  acquire_lock_b ();
+}
+
+void bar ()
+{
+  acquire_lock_b ();
+  acquire_lock_a ();
+}
+
+/* Verify that some JSON was written to a file with the expected name.  */
+/* { dg-final { verify-sarif-file } } */
+
+/* We expect various properties.
+   The indentation here reflects the expected hierarchy, though these tests
+   don't check for that, merely the string fragments we expect.
+
+   { dg-final { scan-sarif-file {"version": "2.1.0"} } }
+     { dg-final { scan-sarif-file {"text": "deadlock due to inconsistent lock acquisition order"} } }
+     { dg-final { scan-sarif-file {"id": "Thread 1"} } }
+       { dg-final { scan-sarif-file {"executionOrder": 1} } }
+       { dg-final { scan-sarif-file {"executionOrder": 2} } }
+       { dg-final { scan-sarif-file {"executionOrder": 5} } }
+     { dg-final { scan-sarif-file {"id": "Thread 2"} } }
+       { dg-final { scan-sarif-file {"executionOrder": 3} } }
+       { dg-final { scan-sarif-file {"executionOrder": 4} } }
+       { dg-final { scan-sarif-file {"executionOrder": 6} } }  */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-separate-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-separate-events.c
new file mode 100644
index 00000000000..914918bb9e1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-separate-events.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+extern void acquire_lock_a(void);
+extern void acquire_lock_b(void);
+
+void foo ()
+{ /* { dg-message "\\(1\\) entering 'foo'" } */
+  acquire_lock_a (); /* { dg-message "\\(2\\) lock a is now held by thread 1" } */
+  acquire_lock_b (); /* { dg-message "\\(5\\) deadlocked due to waiting for lock b in thread 1\.\.\." } */
+}
+
+void bar ()
+{ /* { dg-message "\\(3\\) entering 'bar'" } */
+  acquire_lock_b (); /* { dg-message "\\(4\\) lock b is now held by thread 2" } */
+  acquire_lock_a (); /* { dg-warning "deadlock due to inconsistent lock acquisition order" } */
+  /* { dg-message "\\(6\\) \.\.\.whilst waiting for lock a in thread 2" "" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
index 8d97fe8e8d9..62558bede95 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -192,7 +192,7 @@ struct event_location_t
 /* If FUN's name matches FUNCNAME, write the function and its start location
    into *OUT_ENTRY.  */
 
-static void
+static bool
 check_for_named_function (function *fun, const char *funcname,
 			  event_location_t *out_entry)
 {
@@ -200,9 +200,10 @@ check_for_named_function (function *fun, const char *funcname,
   gcc_assert (funcname);
 
   if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname))
-    return;
+    return false;
 
   *out_entry = event_location_t (fun, fun->function_start_locus);
+  return true;
 }
 
 
@@ -215,12 +216,21 @@ class test_diagnostic_path : public simple_diagnostic_path
   : simple_diagnostic_path (event_pp)
   {
   }
+  void add_event_2 (event_location_t evloc, int stack_depth,
+		    const char *desc,
+		    diagnostic_thread_id_t thread_id = 0)
+  {
+    gcc_assert (evloc.m_fun);
+    add_thread_event (thread_id, evloc.m_loc, evloc.m_fun->decl,
+		      stack_depth, desc);
+  }
   void add_entry (event_location_t evloc, int stack_depth,
-		  const char *funcname)
+		  const char *funcname,
+		  diagnostic_thread_id_t thread_id = 0)
   {
     gcc_assert (evloc.m_fun);
-    add_event (evloc.m_loc, evloc.m_fun->decl, stack_depth,
-	       "entering %qs", funcname);
+    add_thread_event (thread_id, evloc.m_loc, evloc.m_fun->decl, stack_depth,
+		      "entering %qs", funcname);
   }
 
   void add_call (event_location_t call_evloc, int caller_stack_depth,
@@ -422,12 +432,86 @@ example_3 ()
     }
 }
 
+/* Example 4: a multithreaded path.  */
+
+static void
+example_4 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  event_location_t entry_to_foo;
+  event_location_t entry_to_bar;
+  event_location_t call_to_acquire_lock_a_in_foo;
+  event_location_t call_to_acquire_lock_b_in_foo;
+  event_location_t call_to_acquire_lock_a_in_bar;
+  event_location_t call_to_acquire_lock_b_in_bar;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  bool in_foo = check_for_named_function (fun, "foo",
+						  &entry_to_foo);
+	  bool in_bar = check_for_named_function (fun, "bar",
+						  &entry_to_bar);
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      event_location_t *evloc = NULL;
+	      gcall *call = NULL;
+	      if (call = check_for_named_call (stmt, "acquire_lock_a", 0))
+		evloc = (in_foo
+			 ? &call_to_acquire_lock_a_in_foo
+			 : &call_to_acquire_lock_a_in_bar);
+	      else if (call
+		       = check_for_named_call (stmt, "acquire_lock_b", 0))
+		evloc = (in_foo
+			 ? &call_to_acquire_lock_b_in_foo
+			 : &call_to_acquire_lock_b_in_bar);
+	      if (evloc)
+		evloc->set (call, fun);
+	    }
+	}
+    }
+
+  if (call_to_acquire_lock_a_in_foo.m_fun)
+    {
+      auto_diagnostic_group d;
+
+      gcc_rich_location richloc (call_to_acquire_lock_a_in_bar.m_loc);
+      test_diagnostic_path path (global_dc->printer);
+      diagnostic_thread_id_t thread_1 = path.add_thread ("Thread 1");
+      diagnostic_thread_id_t thread_2 = path.add_thread ("Thread 2");
+      path.add_entry (entry_to_foo, 0, "foo", thread_1);
+      path.add_event_2 (call_to_acquire_lock_a_in_foo, 1,
+			"lock a is now held by thread 1", thread_1);
+      path.add_entry (entry_to_bar, 0, "bar", thread_2);
+      path.add_event_2 (call_to_acquire_lock_b_in_bar, 1,
+			"lock b is now held by thread 2", thread_2);
+      path.add_event_2 (call_to_acquire_lock_b_in_foo, 1,
+			"deadlocked due to waiting for lock b in thread 1...",
+			thread_1);
+      path.add_event_2 (call_to_acquire_lock_a_in_bar, 1,
+			"...whilst waiting for lock a in thread 2",
+			thread_2);
+      richloc.set_path (&path);
+
+      diagnostic_metadata m;
+      warning_meta (&richloc, m, 0,
+		    "deadlock due to inconsistent lock acquisition order");
+    }
+}
+
 unsigned int
 pass_test_show_path::execute (function *)
 {
   example_1 ();
   example_2 ();
   example_3 ();
+  example_4 ();
 
   return 0;
 }
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index ed72912309c..f098a327d31 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -108,6 +108,9 @@ set plugin_test_list [list \
 	  diagnostic-test-paths-3.c \
 	  diagnostic-test-paths-4.c \
 	  diagnostic-test-paths-5.c \
+	  diagnostic-test-paths-multithreaded-inline-events.c \
+	  diagnostic-test-paths-multithreaded-sarif.c \
+	  diagnostic-test-paths-multithreaded-separate-events.c \
 	  diagnostic-path-format-plain.c \
 	  diagnostic-path-format-none.c \
 	  diagnostic-path-format-separate-events.c \
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index 988ed7f6726..84148da53af 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -83,6 +84,9 @@ can_consolidate_events (const diagnostic_event &e1,
 			const diagnostic_event &e2,
 			bool check_locations)
 {
+  if (e1.get_thread_id () != e2.get_thread_id ())
+    return false;
+
   if (e1.get_fndecl () != e2.get_fndecl ())
     return false;
 
@@ -109,20 +113,67 @@ can_consolidate_events (const diagnostic_event &e1,
   return true;
 }
 
-/* A range of consecutive events within a diagnostic_path,
-   all with the same fndecl and stack_depth, and which are suitable
+struct event_range;
+struct path_summary;
+class thread_event_printer;
+
+/* A bundle of information about all of the events in a diagnostic_path
+   relating to a specific path, for use by path_summary.  */
+
+class per_thread_summary
+{
+public:
+  per_thread_summary (label_text name, unsigned swimlane_idx)
+  : m_name (std::move (name)),
+    m_swimlane_idx (swimlane_idx),
+    m_min_depth (INT_MAX),
+    m_max_depth (INT_MIN)
+  {}
+
+  void update_depth_limits (int stack_depth)
+  {
+    if (stack_depth < m_min_depth)
+      m_min_depth = stack_depth;
+    if (stack_depth > m_max_depth)
+      m_max_depth = stack_depth;
+  }
+
+  const char *get_name () const { return m_name.get (); }
+  unsigned get_swimlane_index () const { return m_swimlane_idx; }
+
+private:
+  friend struct path_summary;
+  friend class thread_event_printer;
+
+  const label_text m_name;
+
+  /* The "swimlane index" is the order in which this per_thread_summary
+     was created, for use when printing the events.  */
+  const unsigned m_swimlane_idx;
+
+  // The event ranges specific to this thread:
+  auto_vec<event_range *> m_event_ranges;
+  int m_min_depth;
+  int m_max_depth;
+};
+
+/* A range of consecutive events within a diagnostic_path, all within the
+   same thread, and with the same fndecl and stack_depth, and which are suitable
    to print with a single call to diagnostic_show_locus.  */
 struct event_range
 {
   event_range (const diagnostic_path *path, unsigned start_idx,
-	       const diagnostic_event &initial_event)
+	       const diagnostic_event &initial_event,
+	       const per_thread_summary &t)
   : m_path (path),
     m_initial_event (initial_event),
     m_fndecl (initial_event.get_fndecl ()),
     m_stack_depth (initial_event.get_stack_depth ()),
     m_start_idx (start_idx), m_end_idx (start_idx),
     m_path_label (path, start_idx),
-    m_richloc (initial_event.get_location (), &m_path_label)
+    m_richloc (initial_event.get_location (), &m_path_label),
+    m_thread_id (initial_event.get_thread_id ()),
+    m_per_thread_summary (t)
   {}
 
   bool maybe_add_event (const diagnostic_event &new_ev, unsigned idx,
@@ -142,7 +193,7 @@ struct event_range
   /* Print the events in this range to DC, typically as a single
      call to the printer's diagnostic_show_locus.  */
 
-  void print (diagnostic_context *dc)
+  void print (diagnostic_context *dc, pretty_printer *pp)
   {
     location_t initial_loc = m_initial_event.get_location ();
 
@@ -172,7 +223,6 @@ struct event_range
 	    const diagnostic_event &iter_event = m_path->get_event (i);
 	    diagnostic_event_id_t event_id (i);
 	    label_text event_text (iter_event.get_desc (true));
-	    pretty_printer *pp = dc->printer;
 	    pp_printf (pp, " %@: %s", &event_id, event_text.get ());
 	    pp_newline (pp);
 	  }
@@ -180,7 +230,7 @@ struct event_range
       }
 
     /* Call diagnostic_show_locus to show the events using labels.  */
-    diagnostic_show_locus (dc, &m_richloc, DK_DIAGNOSTIC_PATH);
+    diagnostic_show_locus (dc, &m_richloc, DK_DIAGNOSTIC_PATH, pp);
 
     /* If we have a macro expansion, show the expansion to the user.  */
     if (linemap_location_from_macro_expansion_p (line_table, initial_loc))
@@ -198,19 +248,49 @@ struct event_range
   unsigned m_end_idx;
   path_label m_path_label;
   gcc_rich_location m_richloc;
+  diagnostic_thread_id_t m_thread_id;
+  const per_thread_summary &m_per_thread_summary;
 };
 
 /* A struct for grouping together the events in a diagnostic_path into
-   ranges of events, partitioned by stack frame (i.e. by fndecl and
-   stack depth).  */
+   ranges of events, partitioned by thread and by stack frame (i.e. by fndecl
+   and stack depth).  */
 
 struct path_summary
 {
   path_summary (const diagnostic_path &path, bool check_rich_locations);
 
   unsigned get_num_ranges () const { return m_ranges.length (); }
+  bool multithreaded_p () const { return m_per_thread_summary.length () > 1; }
+
+  const per_thread_summary &get_events_for_thread_id (diagnostic_thread_id_t tid)
+  {
+    per_thread_summary **slot = m_thread_id_to_events.get (tid);
+    gcc_assert (slot);
+    gcc_assert (*slot);
+    return **slot;
+  }
 
   auto_delete_vec <event_range> m_ranges;
+  auto_delete_vec <per_thread_summary> m_per_thread_summary;
+  hash_map<int_hash<diagnostic_thread_id_t, -1, -2>,
+	   per_thread_summary *> m_thread_id_to_events;
+
+private:
+  per_thread_summary &
+  get_or_create_events_for_thread_id (const diagnostic_path &path,
+				      diagnostic_thread_id_t tid)
+  {
+    if (per_thread_summary **slot = m_thread_id_to_events.get (tid))
+      return **slot;
+
+    const diagnostic_thread &thread = path.get_thread (tid);
+    per_thread_summary *pts = new per_thread_summary (thread.get_name (false),
+						m_per_thread_summary.length ());
+    m_thread_id_to_events.put (tid, pts);
+    m_per_thread_summary.safe_push (pts);
+    return *pts;
+  }
 };
 
 /* path_summary's ctor.  */
@@ -224,12 +304,19 @@ path_summary::path_summary (const diagnostic_path &path,
   for (unsigned idx = 0; idx < num_events; idx++)
     {
       const diagnostic_event &event = path.get_event (idx);
+      const diagnostic_thread_id_t thread_id = event.get_thread_id ();
+      per_thread_summary &pts
+	= get_or_create_events_for_thread_id (path, thread_id);
+
+      pts.update_depth_limits (event.get_stack_depth ());
+
       if (cur_event_range)
 	if (cur_event_range->maybe_add_event (event, idx, check_rich_locations))
 	  continue;
 
-      cur_event_range = new event_range (&path, idx, event);
+      cur_event_range = new event_range (&path, idx, event, pts);
       m_ranges.safe_push (cur_event_range);
+      pts.m_event_ranges.safe_push (cur_event_range);
     }
 }
 
@@ -259,6 +346,184 @@ print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
     pp_string (pp, n);
 }
 
+static const int base_indent = 2;
+static const int per_frame_indent = 2;
+
+/* A bundle of state for printing event_range instances for a particular
+   thread.  */
+
+class thread_event_printer
+{
+public:
+  thread_event_printer (const per_thread_summary &t, bool show_depths)
+  : m_per_thread_summary (t),
+    m_show_depths (show_depths),
+    m_cur_indent (base_indent),
+    m_vbar_column_for_depth (),
+    m_num_printed (0)
+  {
+  }
+
+  /* Get the previous event_range within this thread, if any.  */
+  const event_range *get_any_prev_range () const
+  {
+    if (m_num_printed > 0)
+      return m_per_thread_summary.m_event_ranges[m_num_printed - 1];
+    else
+      return nullptr;
+  }
+
+  /* Get the next event_range within this thread, if any.  */
+  const event_range *get_any_next_range () const
+  {
+    if (m_num_printed < m_per_thread_summary.m_event_ranges.length () - 1)
+      return m_per_thread_summary.m_event_ranges[m_num_printed + 1];
+    else
+      return nullptr;
+  }
+
+  void print_swimlane_for_event_range (diagnostic_context *dc,
+				       pretty_printer *pp,
+				       event_range *range)
+  {
+    const char *const line_color = "path";
+    const char *start_line_color
+      = colorize_start (pp_show_color (pp), line_color);
+    const char *end_line_color = colorize_stop (pp_show_color (pp));
+
+    write_indent (pp, m_cur_indent);
+    if (const event_range *prev_range = get_any_prev_range ())
+      {
+	if (range->m_stack_depth > prev_range->m_stack_depth)
+	  {
+	    /* Show pushed stack frame(s).  */
+	    const char *push_prefix = "+--> ";
+	    pp_string (pp, start_line_color);
+	    pp_string (pp, push_prefix);
+	    pp_string (pp, end_line_color);
+	    m_cur_indent += strlen (push_prefix);
+	  }
+      }
+    if (range->m_fndecl)
+      {
+	print_fndecl (pp, range->m_fndecl, true);
+	pp_string (pp, ": ");
+      }
+    if (range->m_start_idx == range->m_end_idx)
+      pp_printf (pp, "event %i",
+		 range->m_start_idx + 1);
+    else
+      pp_printf (pp, "events %i-%i",
+		 range->m_start_idx + 1, range->m_end_idx + 1);
+    if (m_show_depths)
+      pp_printf (pp, " (depth %i)", range->m_stack_depth);
+    pp_newline (pp);
+
+    /* Print a run of events.  */
+    {
+      write_indent (pp, m_cur_indent + per_frame_indent);
+      pp_string (pp, start_line_color);
+      pp_string (pp, "|");
+      pp_string (pp, end_line_color);
+      pp_newline (pp);
+
+      char *saved_prefix = pp_take_prefix (pp);
+      char *prefix;
+      {
+	pretty_printer tmp_pp;
+	write_indent (&tmp_pp, m_cur_indent + per_frame_indent);
+	pp_string (&tmp_pp, start_line_color);
+	pp_string (&tmp_pp, "|");
+	pp_string (&tmp_pp, end_line_color);
+	prefix = xstrdup (pp_formatted_text (&tmp_pp));
+      }
+      pp_set_prefix (pp, prefix);
+      pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+      range->print (dc, pp);
+      pp_set_prefix (pp, saved_prefix);
+
+      write_indent (pp, m_cur_indent + per_frame_indent);
+      pp_string (pp, start_line_color);
+      pp_string (pp, "|");
+      pp_string (pp, end_line_color);
+      pp_newline (pp);
+    }
+
+    if (const event_range *next_range = get_any_next_range ())
+      {
+	if (range->m_stack_depth > next_range->m_stack_depth)
+	  {
+	    if (m_vbar_column_for_depth.get (next_range->m_stack_depth))
+	      {
+		/* Show returning from stack frame(s), by printing
+		   something like:
+		   "                   |\n"
+		   "     <------------ +\n"
+		   "     |\n".  */
+		int vbar_for_next_frame
+		  = *m_vbar_column_for_depth.get (next_range->m_stack_depth);
+
+		int indent_for_next_frame
+		  = vbar_for_next_frame - per_frame_indent;
+		write_indent (pp, vbar_for_next_frame);
+		pp_string (pp, start_line_color);
+		pp_character (pp, '<');
+		for (int i = indent_for_next_frame + per_frame_indent;
+		     i < m_cur_indent + per_frame_indent - 1; i++)
+		  pp_character (pp, '-');
+		pp_character (pp, '+');
+		pp_string (pp, end_line_color);
+		pp_newline (pp);
+		m_cur_indent = indent_for_next_frame;
+
+		write_indent (pp, vbar_for_next_frame);
+		pp_string (pp, start_line_color);
+		pp_character (pp, '|');
+		pp_string (pp, end_line_color);
+		pp_newline (pp);
+	      }
+	    else
+	      {
+		/* Handle disjoint paths (e.g. a callback at some later
+		   time).  */
+		m_cur_indent = base_indent;
+	      }
+	  }
+	else if (range->m_stack_depth < next_range->m_stack_depth)
+	  {
+	    /* Prepare to show pushed stack frame.  */
+	    gcc_assert (range->m_stack_depth != EMPTY);
+	    gcc_assert (range->m_stack_depth != DELETED);
+	    m_vbar_column_for_depth.put (range->m_stack_depth,
+					 m_cur_indent + per_frame_indent);
+	    m_cur_indent += per_frame_indent;
+	  }
+      }
+
+    m_num_printed++;
+  }
+
+  int get_cur_indent () const { return m_cur_indent; }
+
+private:
+  const per_thread_summary &m_per_thread_summary;
+  bool m_show_depths;
+
+  /* Print the ranges.  */
+  int m_cur_indent;
+
+  /* Keep track of column numbers of existing '|' characters for
+     stack depths we've already printed.  */
+  static const int EMPTY = -1;
+  static const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+  hash_map <vbar_hash, int> m_vbar_column_for_depth;
+
+  /* How many event ranges within this swimlane have we printed.
+     This is the index of the next event_range to print.  */
+  unsigned  m_num_printed;
+};
+
 /* Print path_summary PS to DC, giving an overview of the interprocedural
    calls and returns.
 
@@ -292,145 +557,33 @@ print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
 
    For events with UNKNOWN_LOCATION, print a summary of each the event.  */
 
-void
+static void
 print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc,
 			    bool show_depths)
 {
   pretty_printer *pp = dc->printer;
 
-  const int per_frame_indent = 2;
-
-  const char *const line_color = "path";
-  const char *start_line_color
-    = colorize_start (pp_show_color (pp), line_color);
-  const char *end_line_color = colorize_stop (pp_show_color (pp));
+  std::vector<thread_event_printer> thread_event_printers;
+  for (auto t : ps->m_per_thread_summary)
+    thread_event_printers.push_back (thread_event_printer (*t, show_depths));
 
-  /* Keep track of column numbers of existing '|' characters for
-     stack depths we've already printed.  */
-  const int EMPTY = -1;
-  const int DELETED = -2;
-  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
-  hash_map <vbar_hash, int> vbar_column_for_depth;
-
-  /* Print the ranges.  */
-  const int base_indent = 2;
-  int cur_indent = base_indent;
   unsigned i;
   event_range *range;
   FOR_EACH_VEC_ELT (ps->m_ranges, i, range)
     {
-      write_indent (pp, cur_indent);
-      if (i > 0)
-	{
-	  const event_range *prev_range = ps->m_ranges[i - 1];
-	  if (range->m_stack_depth > prev_range->m_stack_depth)
-	    {
-	      /* Show pushed stack frame(s).  */
-	      const char *push_prefix = "+--> ";
-	      pp_string (pp, start_line_color);
-	      pp_string (pp, push_prefix);
-	      pp_string (pp, end_line_color);
-	      cur_indent += strlen (push_prefix);
-	    }
-	}
-      if (range->m_fndecl)
-	{
-	  print_fndecl (pp, range->m_fndecl, true);
-	  pp_string (pp, ": ");
-	}
-      if (range->m_start_idx == range->m_end_idx)
-	pp_printf (pp, "event %i",
-		   range->m_start_idx + 1);
-      else
-	pp_printf (pp, "events %i-%i",
-		   range->m_start_idx + 1, range->m_end_idx + 1);
-      if (show_depths)
-	pp_printf (pp, " (depth %i)", range->m_stack_depth);
-      pp_newline (pp);
-
-      /* Print a run of events.  */
-      {
-	write_indent (pp, cur_indent + per_frame_indent);
-	pp_string (pp, start_line_color);
-	pp_string (pp, "|");
-	pp_string (pp, end_line_color);
-	pp_newline (pp);
-
-	char *saved_prefix = pp_take_prefix (pp);
-	char *prefix;
-	{
-	  pretty_printer tmp_pp;
-	  write_indent (&tmp_pp, cur_indent + per_frame_indent);
-	  pp_string (&tmp_pp, start_line_color);
-	  pp_string (&tmp_pp, "|");
-	  pp_string (&tmp_pp, end_line_color);
-	  prefix = xstrdup (pp_formatted_text (&tmp_pp));
-	}
-	pp_set_prefix (pp, prefix);
-	pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-	range->print (dc);
-	pp_set_prefix (pp, saved_prefix);
-
-	write_indent (pp, cur_indent + per_frame_indent);
-	pp_string (pp, start_line_color);
-	pp_string (pp, "|");
-	pp_string (pp, end_line_color);
-	pp_newline (pp);
-      }
-
-      if (i < ps->m_ranges.length () - 1)
-	{
-	  const event_range *next_range = ps->m_ranges[i + 1];
-
-	  if (range->m_stack_depth > next_range->m_stack_depth)
-	    {
-	      if (vbar_column_for_depth.get (next_range->m_stack_depth))
-		{
-		  /* Show returning from stack frame(s), by printing
-		     something like:
-		     "                   |\n"
-		     "     <------------ +\n"
-		     "     |\n".  */
-		  int vbar_for_next_frame
-		    = *vbar_column_for_depth.get (next_range->m_stack_depth);
-
-		  int indent_for_next_frame
-		    = vbar_for_next_frame - per_frame_indent;
-		  write_indent (pp, vbar_for_next_frame);
-		  pp_string (pp, start_line_color);
-		  pp_character (pp, '<');
-		  for (int i = indent_for_next_frame + per_frame_indent;
-		       i < cur_indent + per_frame_indent - 1; i++)
-		    pp_character (pp, '-');
-		  pp_character (pp, '+');
-		  pp_string (pp, end_line_color);
-		  pp_newline (pp);
-		  cur_indent = indent_for_next_frame;
-
-		  write_indent (pp, vbar_for_next_frame);
-		  pp_string (pp, start_line_color);
-		  pp_character (pp, '|');
-		  pp_string (pp, end_line_color);
-		  pp_newline (pp);
-		}
-	      else
-		{
-		  /* Handle disjoint paths (e.g. a callback at some later
-		     time).  */
-		  cur_indent = base_indent;
-		}
-	    }
-	  else if (range->m_stack_depth < next_range->m_stack_depth)
-	    {
-	      /* Prepare to show pushed stack frame.  */
-	      gcc_assert (range->m_stack_depth != EMPTY);
-	      gcc_assert (range->m_stack_depth != DELETED);
-	      vbar_column_for_depth.put (range->m_stack_depth,
-					 cur_indent + per_frame_indent);
-	      cur_indent += per_frame_indent;
-	    }
-
-	}
+      const int swimlane_idx
+	= range->m_per_thread_summary.get_swimlane_index ();
+      if (ps->multithreaded_p ())
+	if (i == 0 || ps->m_ranges[i - 1]->m_thread_id != range->m_thread_id)
+	  {
+	    if (i > 0)
+	      pp_newline (pp);
+	    pp_printf (pp, "Thread: %qs",
+		       range->m_per_thread_summary.get_name ());
+	    pp_newline (pp);
+	  }
+      thread_event_printer &tep = thread_event_printers[swimlane_idx];
+      tep.print_swimlane_for_event_range (dc, pp, range);
     }
 }
 
@@ -497,6 +650,7 @@ default_tree_diagnostic_path_printer (diagnostic_context *context,
 	pp_flush (context->printer);
 	pp_set_prefix (context->printer, saved_prefix);
       }
+      break;
     }
 }

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2023-09-14 20:29 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-09-14 20:29 [gcc r14-4006] diagnostics: support multithreaded diagnostic paths David Malcolm

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).