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.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r14-4006-g3a1e9f3ed7aa49.

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.
---
 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 +-
 ...c-test-paths-multithreaded-inline-events.c |  72 +++
 ...iagnostic-test-paths-multithreaded-sarif.c |  35 ++
 ...test-paths-multithreaded-separate-events.c |  18 +
 .../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(-)
 create mode 100644 
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-inline-events.c
 create mode 100644 
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-sarif.c
 create mode 100644 
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-multithreaded-separate-events.c

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;
     }
 }
 
-- 
2.26.3

Reply via email to