diff mbox series

[committed] analyzer: add region::tracked_p to optimize state objects [PR104954]

Message ID 20220325011115.686754-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer: add region::tracked_p to optimize state objects [PR104954] | expand

Commit Message

David Malcolm March 25, 2022, 1:11 a.m. UTC
PR analyzer/104954 tracks that -fanalyzer was taking a very long time
on a particular source file in the Linux kernel:
  drivers/gpu/drm/amd/display/dc/calcs/dce_calcs.c

One issue occurs with the repeated use of dynamic debug lines e.g. via
the DC_LOG_BANDWIDTH_CALCS macro, such as in print_bw_calcs_dceip in
drivers/gpu/drm/amd/display/dc/calcs/calcs_logger.h:

  DC_LOG_BANDWIDTH_CALCS("#####################################################################");
  DC_LOG_BANDWIDTH_CALCS("struct bw_calcs_dceip");
  DC_LOG_BANDWIDTH_CALCS("#####################################################################");

  [...snip dozens of lines...]

  DC_LOG_BANDWIDTH_CALCS("[bw_fixed] dmif_request_buffer_size: %d",
                         bw_fixed_to_int(dceip->dmif_request_buffer_size));

When this is configured to use __dynamic_pr_debug, each of these becomes
code like:

  do {
    static struct _ddebug __attribute__((__aligned__(8)))
    __attribute__((__section__("__dyndbg"))) __UNIQUE_ID_ddebug277 = {
      [...snip...]
    };
    if (arch_static_branch(&__UNIQUE_ID_ddebug277.key, false))
      __dynamic_pr_debug(&__UNIQUE_ID_ddebug277, [...the message...]);
  } while (0);

The analyzer was naively seeing each call to __dynamic_pr_debug, noting
that the __UNIQUE_ID_nnnn object escapes.  At each call, as successive
__UNIQUE_ID_nnnn object escapes, there are N escaped objects, and thus N
need clobbering, and so we have O(N^2) clobbering of escaped objects overall,
leading to huge amounts of pointless work: print_bw_calcs_data has 225
uses of DC_LOG_BANDWIDTH_CALCS, many of which are in loops.

This patch adds a way to identify declarations that aren't interesting
to the analyzer, so that we don't attempt to create binding_clusters
for them (i.e. we don't store any state for them in our state objects).
This is implemented by adding a new region::tracked_p, implemented for
declarations by walking the existing IPA data the first time the
analyzer sees a declaration, setting it to false for global vars that
have no loads/stores/aliases, and "sufficiently safe" address-of
ipa-refs.

The patch gives a large speedup of -fanalyzer on the above kernel
source file:
                           Before  After
Total cc1 wallclock time:    180s    36s
analyzer wallclock time:     162s    17s
% spent in analyzer:          90%    47%

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r12-7809-g5f6197d7c197f9.

gcc/analyzer/ChangeLog:
	PR analyzer/104954
	* analyzer.opt (-fdump-analyzer-untracked): New option.
	* engine.cc (impl_run_checkers): Handle it.
	* region-model-asm.cc (region_model::on_asm_stmt): Don't attempt
	to clobber regions with !tracked_p ().
	* region-model-manager.cc (dump_untracked_region): New.
	(region_model_manager::dump_untracked_regions): New.
	(frame_region::dump_untracked_regions): New.
	* region-model.h (region_model_manager::dump_untracked_regions):
	New decl.
	* region.cc (ipa_ref_requires_tracking): New.
	(symnode_requires_tracking_p): New.
	(decl_region::calc_tracked_p): New.
	* region.h (region::tracked_p): New vfunc.
	(frame_region::dump_untracked_regions): New decl.
	(class decl_region): Note that this is also used fo SSA names.
	(decl_region::decl_region): Initialize m_tracked.
	(decl_region::tracked_p): New.
	(decl_region::calc_tracked_p): New decl.
	(decl_region::m_tracked): New.
	* store.cc (store::get_or_create_cluster): Assert that we
	don't try to create clusters for base regions that aren't
	trackable.
	(store::mark_as_escaped): Don't mark base regions that we're not
	tracking.

gcc/ChangeLog:
	PR analyzer/104954
	* doc/invoke.texi (Static Analyzer Options): Add
	-fdump-analyzer-untracked.

gcc/testsuite/ChangeLog:
	PR analyzer/104954
	* gcc.dg/analyzer/asm-x86-dyndbg-1.c: New test.
	* gcc.dg/analyzer/asm-x86-dyndbg-2.c: New test.
	* gcc.dg/analyzer/many-unused-locals.c: New test.
	* gcc.dg/analyzer/untracked-1.c: New test.
	* gcc.dg/analyzer/unused-local-1.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/analyzer.opt                     |   4 +
 gcc/analyzer/engine.cc                        |   3 +
 gcc/analyzer/region-model-asm.cc              |   3 +-
 gcc/analyzer/region-model-manager.cc          |  41 ++++++
 gcc/analyzer/region-model.h                   |   2 +
 gcc/analyzer/region.cc                        |  88 ++++++++++++
 gcc/analyzer/region.h                         |  24 +++-
 gcc/analyzer/store.cc                         |   6 +-
 gcc/doc/invoke.texi                           |   5 +
 .../gcc.dg/analyzer/asm-x86-dyndbg-1.c        | 126 ++++++++++++++++++
 .../gcc.dg/analyzer/asm-x86-dyndbg-2.c        |  77 +++++++++++
 .../gcc.dg/analyzer/many-unused-locals.c      |  69 ++++++++++
 gcc/testsuite/gcc.dg/analyzer/untracked-1.c   |  99 ++++++++++++++
 .../gcc.dg/analyzer/unused-local-1.c          |  22 +++
 14 files changed, 565 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/many-unused-locals.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/untracked-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/unused-local-1.c
diff mbox series

Patch

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index b9d2ece273c..a0ba2c94fb3 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -250,4 +250,8 @@  fdump-analyzer-supergraph
 Common RejectNegative Var(flag_dump_analyzer_supergraph)
 Dump the analyzer supergraph to a SRCFILE.supergraph.dot file.
 
+fdump-analyzer-untracked
+Common RejectNegative Var(flag_dump_analyzer_untracked)
+Emit custom warnings with internal details intended for analyzer developers.
+
 ; This comment is to ensure we retain the blank line above.
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 0f40e064a22..d8b61955aa6 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -5811,6 +5811,9 @@  impl_run_checkers (logger *logger)
   if (flag_dump_analyzer_json)
     dump_analyzer_json (sg, eg);
 
+  if (flag_dump_analyzer_untracked)
+    eng.get_model_manager ()->dump_untracked_regions ();
+
   delete purge_map;
 }
 
diff --git a/gcc/analyzer/region-model-asm.cc b/gcc/analyzer/region-model-asm.cc
index 3bf2a02432c..c7389f0e38d 100644
--- a/gcc/analyzer/region-model-asm.cc
+++ b/gcc/analyzer/region-model-asm.cc
@@ -267,7 +267,8 @@  region_model::on_asm_stmt (const gasm *stmt, region_model_context *ctxt)
        iter != reachable_regs.end_mutable_base_regs (); ++iter)
     {
       const region *base_reg = *iter;
-      if (base_reg->symbolic_for_unknown_ptr_p ())
+      if (base_reg->symbolic_for_unknown_ptr_p ()
+	  || !base_reg->tracked_p ())
 	continue;
 
       binding_cluster *cluster = m_store.get_or_create_cluster (base_reg);
diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc
index bc8f554580b..61c7fb32628 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -1740,6 +1740,47 @@  store_manager::log_stats (logger *logger, bool show_objs) const
 		m_symbolic_binding_key_mgr);
 }
 
+/* Emit a warning showing DECL_REG->tracked_p () for use in DejaGnu tests
+   (using -fdump-analyzer-untracked).  */
+
+static void
+dump_untracked_region (const decl_region *decl_reg)
+{
+  tree decl = decl_reg->get_decl ();
+  if (TREE_CODE (decl) != VAR_DECL)
+    return;
+  warning_at (DECL_SOURCE_LOCATION (decl), 0,
+	      "track %qD: %s",
+	      decl, (decl_reg->tracked_p () ? "yes" : "no"));
+}
+
+/* Implementation of -fdump-analyzer-untracked.  */
+
+void
+region_model_manager::dump_untracked_regions () const
+{
+  for (auto iter : m_globals_map)
+    {
+      const decl_region *decl_reg = iter.second;
+      dump_untracked_region (decl_reg);
+    }
+  for (auto frame_iter : m_frame_regions)
+    {
+      const frame_region *frame_reg = frame_iter.second;
+      frame_reg->dump_untracked_regions ();
+    }
+}
+
+void
+frame_region::dump_untracked_regions () const
+{
+  for (auto iter : m_locals)
+    {
+      const decl_region *decl_reg = iter.second;
+      dump_untracked_region (decl_reg);
+    }
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index d9e21432032..13a2ea94a3f 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -349,6 +349,8 @@  public:
 
   logger *get_logger () const { return m_logger; }
 
+  void dump_untracked_regions () const;
+
 private:
   bool too_complex_p (const complexity &c) const;
   bool reject_if_too_complex (svalue *sval);
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 2020ef4e4b5..9c9e043c658 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -1167,6 +1167,94 @@  decl_region::get_svalue_for_initializer (region_model_manager *mgr) const
   return m.get_rvalue (path_var (init, 0), NULL);
 }
 
+/* Subroutine of symnode_requires_tracking_p; return true if REF
+   within CONTEXT_FNDECL might imply that we should be tracking the
+   value of a decl.  */
+
+static bool
+ipa_ref_requires_tracking (const ipa_ref *ref, tree context_fndecl)
+{
+  /* If we have a load/store/alias of the symbol, then we'll track
+     the decl's value.  */
+  if (ref->use != IPA_REF_ADDR)
+    return true;
+
+  if (ref->stmt == NULL)
+    return true;
+
+  switch (ref->stmt->code)
+    {
+    default:
+      return true;
+    case GIMPLE_CALL:
+      {
+	cgraph_node *context_cnode = cgraph_node::get (context_fndecl);
+	cgraph_edge *edge = context_cnode->get_edge (ref->stmt);
+	if (!edge)
+	  return true;
+	if (edge->callee == NULL)
+	  return true; /* e.g. call through function ptr.  */
+	if (edge->callee->definition)
+	  return true;
+	/* If we get here, then this ref is a pointer passed to
+	   a function we don't have the definition for.  */
+	return false;
+      }
+      break;
+    case GIMPLE_ASM:
+      {
+	const gasm *asm_stmt = as_a <const gasm *> (ref->stmt);
+	if (gimple_asm_noutputs (asm_stmt) > 0)
+	  return true;
+	if (gimple_asm_nclobbers (asm_stmt) > 0)
+	  return true;
+	/* If we get here, then this ref is the decl being passed
+	   by pointer to asm with no outputs.  */
+	return false;
+      }
+      break;
+    }
+}
+
+/* Determine if the decl for SYMNODE should have binding_clusters
+   in our state objects; return false to optimize away tracking
+   certain decls in our state objects, as an optimization.  */
+
+static bool
+symnode_requires_tracking_p (symtab_node *symnode)
+{
+  gcc_assert (symnode);
+  if (symnode->externally_visible)
+    return true;
+  tree context_fndecl = DECL_CONTEXT (symnode->decl);
+  if (context_fndecl == NULL)
+    return true;
+  if (TREE_CODE (context_fndecl) != FUNCTION_DECL)
+    return true;
+  for (auto ref : symnode->ref_list.referring)
+    if (ipa_ref_requires_tracking (ref, context_fndecl))
+      return true;
+
+  /* If we get here, then we don't have uses of this decl that require
+     tracking; we never read from it or write to it explicitly.  */
+  return false;
+}
+
+/* Subroutine of decl_region ctor: determine whether this decl_region
+   can have binding_clusters; return false to optimize away tracking
+   of certain decls in our state objects, as an optimization.  */
+
+bool
+decl_region::calc_tracked_p (tree decl)
+{
+  /* Precondition of symtab_node::get.  */
+  if (TREE_CODE (decl) == VAR_DECL
+      && (TREE_STATIC (decl) || DECL_EXTERNAL (decl) || in_lto_p))
+    if (symtab_node *symnode = symtab_node::get (decl))
+      return symnode_requires_tracking_p (symnode);
+  return true;
+}
+
 /* class field_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for field_region.  */
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index dbeb485e81f..5150be76d0b 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -197,6 +197,11 @@  public:
 
   bool symbolic_for_unknown_ptr_p () const;
 
+  /* For most base regions it makes sense to track the bindings of the region
+     within the store.  As an optimization, some are not tracked (to avoid
+     bloating the store object with redundant binding clusters).  */
+  virtual bool tracked_p () const { return true; }
+
   const complexity &get_complexity () const { return m_complexity; }
 
   bool is_named_decl_p (const char *decl_name) const;
@@ -319,6 +324,9 @@  public:
 
   unsigned get_num_locals () const { return m_locals.elements (); }
 
+  /* Implemented in region-model-manager.cc.  */
+  void dump_untracked_regions () const;
+
  private:
   const frame_region *m_calling_frame;
   function *m_fun;
@@ -633,13 +641,15 @@  template <> struct default_hash_traits<symbolic_region::key_t>
 namespace ana {
 
 /* Concrete region subclass representing the memory occupied by a
-   variable (whether for a global or a local).  */
+   variable (whether for a global or a local).
+   Also used for representing SSA names, as if they were locals.  */
 
 class decl_region : public region
 {
 public:
   decl_region (unsigned id, const region *parent, tree decl)
-  : region (complexity (parent), id, parent, TREE_TYPE (decl)), m_decl (decl)
+  : region (complexity (parent), id, parent, TREE_TYPE (decl)), m_decl (decl),
+    m_tracked (calc_tracked_p (decl))
   {}
 
   enum region_kind get_kind () const FINAL OVERRIDE { return RK_DECL; }
@@ -648,6 +658,8 @@  public:
 
   void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
 
+  bool tracked_p () const FINAL OVERRIDE { return m_tracked; }
+
   tree get_decl () const { return m_decl; }
   int get_stack_depth () const;
 
@@ -657,7 +669,15 @@  public:
   const svalue *get_svalue_for_initializer (region_model_manager *mgr) const;
 
 private:
+  static bool calc_tracked_p (tree decl);
+
   tree m_decl;
+
+  /* Cached result of calc_tracked_p, so that we can quickly determine when
+     we don't to track a binding_cluster for this decl (to avoid bloating
+     store objects).
+     This can be debugged using -fdump-analyzer-untracked.  */
+  bool m_tracked;
 };
 
 } // namespace ana
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index ec11433dffc..62733b9777f 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -2654,6 +2654,9 @@  store::get_or_create_cluster (const region *base_reg)
   /* We shouldn't create clusters for dereferencing an UNKNOWN ptr.  */
   gcc_assert (!base_reg->symbolic_for_unknown_ptr_p ());
 
+  /* We shouldn't create clusters for base regions that aren't trackable.  */
+  gcc_assert (base_reg->tracked_p ());
+
   if (binding_cluster **slot = m_cluster_map.get (base_reg))
     return *slot;
 
@@ -2742,7 +2745,8 @@  store::mark_as_escaped (const region *base_reg)
   gcc_assert (base_reg);
   gcc_assert (base_reg->get_base_region () == base_reg);
 
-  if (base_reg->symbolic_for_unknown_ptr_p ())
+  if (base_reg->symbolic_for_unknown_ptr_p ()
+      || !base_reg->tracked_p ())
     return;
 
   binding_cluster *cluster = get_or_create_cluster (base_reg);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 8b16b35ec12..afb21d9154c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -439,6 +439,7 @@  Objective-C and Objective-C++ Dialects}.
 -fdump-analyzer-state-purge @gol
 -fdump-analyzer-stderr @gol
 -fdump-analyzer-supergraph @gol
+-fdump-analyzer-untracked @gol
 -Wno-analyzer-double-fclose @gol
 -Wno-analyzer-double-free @gol
 -Wno-analyzer-exposure-through-output-file @gol
@@ -10212,6 +10213,10 @@  control flow graphs in the program, with interprocedural edges for
 calls and returns.  The second dump contains annotations showing nodes
 in the ``exploded graph'' and diagnostics associated with them.
 
+@item -fdump-analyzer-untracked
+@opindex fdump-analyzer-untracked
+Emit custom warnings with internal details intended for analyzer developers.
+
 @end table
 
 @node Debugging Options
diff --git a/gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-1.c b/gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-1.c
new file mode 100644
index 00000000000..cce3f6b80bc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-1.c
@@ -0,0 +1,126 @@ 
+/* Test reduced from use of dynamic_pr_debug on Linux kernel, to verify that
+   we treat the static struct _ddebug as not needing to be tracked by the
+   analyzer, thus optimizing away bloat in the analyzer's state tracking.  */
+
+/* { dg-do compile { target x86_64-*-* } } */
+/* { dg-additional-options "-fdump-analyzer-untracked" } */
+
+/* Adapted from various files in the Linux kernel, all of which have:  */
+/* SPDX-License-Identifier: GPL-2.0 */
+
+typedef _Bool			bool;
+#define true 1
+#define false 0
+
+typedef struct {
+	int counter;
+} atomic_t;
+
+/* Adapted from include/linux/compiler_attributes.h  */
+#define __always_inline                 inline __attribute__((__always_inline__))
+
+/* Adapted from include/linux/compiler-gcc.h */
+#define asm_volatile_goto(x...)	do { asm goto(x); asm (""); } while (0)
+
+/* Adapted from include/linux/jump_label.h, which has:  */
+
+struct static_key {
+	atomic_t enabled;
+	union {
+		/* [...snip...] */
+		struct jump_entry *entries;
+		/* [...snip...] */
+	};
+};
+
+struct static_key_true {
+	struct static_key key;
+};
+
+struct static_key_false {
+	struct static_key key;
+};
+
+extern bool ____wrong_branch_error(void);
+
+/* Adapted from arch/x86/include/asm/jump_label.h */
+
+#define JUMP_TABLE_ENTRY				\
+	".pushsection __jump_table,  \"aw\" \n\t"	\
+	/*_ASM_ALIGN*/ "\n\t"				\
+	".long 1b - . \n\t"				\
+	".long %l[l_yes] - . \n\t"			\
+	/*_ASM_PTR*/ "%c0 + %c1 - .\n\t"		\
+	".popsection \n\t"
+
+static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch)
+{
+	asm_volatile_goto("1:"
+		/*".byte " __stringify(BYTES_NOP5) "\n\t" */
+		JUMP_TABLE_ENTRY
+		: :  "i" (key), "i" (branch) : : l_yes);
+
+	return false;
+l_yes:
+	return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key * const key, const bool branch)
+{
+	asm_volatile_goto("1:"
+		"jmp %l[l_yes]\n\t"
+		JUMP_TABLE_ENTRY
+		: :  "i" (key), "i" (branch) : : l_yes);
+
+	return false;
+l_yes:
+	return true;
+}
+
+/* Adapted from include/linux/dynamic_debug.h  */
+
+struct _ddebug {
+	/* [...snip...] */
+	const char *function;
+	const char *filename;
+	const char *format;
+	unsigned int lineno:18;
+	/* [...snip...] */
+	unsigned int flags:8;
+	union {
+		struct static_key_true dd_key_true;
+		struct static_key_false dd_key_false;
+	} key;
+} __attribute__((aligned(8)));
+
+extern void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...);
+
+static void expanded_dynamic_pr_debug(void) {
+  do {
+    static struct _ddebug __attribute__((__aligned__(8)))
+    __attribute__((__section__("__dyndbg"))) __UNIQUE_ID_ddebug277 = { /* { dg-warning "track '__UNIQUE_ID_ddebug277': no" } */
+        .function = __func__,
+        .filename = __FILE__,
+        .format = ("hello world"),
+        .lineno = __LINE__,
+        .flags = 0};
+    if (({
+          bool branch;
+          if (__builtin_types_compatible_p(
+                  typeof(*&__UNIQUE_ID_ddebug277.key.dd_key_false),
+                  struct static_key_true))
+            branch = arch_static_branch_jump(
+                &(&__UNIQUE_ID_ddebug277.key.dd_key_false)->key, false);
+          else if (__builtin_types_compatible_p(
+                       typeof(*&__UNIQUE_ID_ddebug277.key.dd_key_false),
+                       struct static_key_false))
+            branch = arch_static_branch(
+                &(&__UNIQUE_ID_ddebug277.key.dd_key_false)->key, false);
+          else
+            branch = ____wrong_branch_error();
+          __builtin_expect(!!(branch), 0);
+        }))
+      __dynamic_pr_debug(&__UNIQUE_ID_ddebug277,
+			 "hello world");
+  } while (0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-2.c b/gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-2.c
new file mode 100644
index 00000000000..8111709206f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/asm-x86-dyndbg-2.c
@@ -0,0 +1,77 @@ 
+/* Test reduced from use of dynamic_pr_debug on Linux kernel, to verify that
+   we treat the static struct _ddebug as not needing to be tracked by the
+   analyzer, thus optimizing away bloat in the analyzer's state tracking.  */
+
+/* { dg-do compile { target x86_64-*-* } } */
+/* { dg-additional-options "-fdump-analyzer-untracked" } */
+
+/* Adapted from various files in the Linux kernel, all of which have:  */
+/* SPDX-License-Identifier: GPL-2.0 */
+
+typedef _Bool			bool;
+#define true 1
+#define false 0
+
+typedef struct {} atomic_t;
+
+/* Adapted from include/linux/compiler_attributes.h  */
+#define __always_inline                 inline __attribute__((__always_inline__))
+
+/* Adapted from include/linux/compiler-gcc.h */
+#define asm_volatile_goto(x...)	do { asm goto(x); asm (""); } while (0)
+
+/* Adapted from include/linux/jump_label.h, which has:  */
+
+struct static_key {};
+
+/* Adapted from arch/x86/include/asm/jump_label.h */
+
+static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch)
+{
+	asm_volatile_goto("1:"
+		: :  "i" (key), "i" (branch) : : l_yes);
+
+	return false;
+l_yes:
+	return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key * const key, const bool branch)
+{
+	asm_volatile_goto("1:"
+		: :  "i" (key), "i" (branch) : : l_yes);
+
+	return false;
+l_yes:
+	return true;
+}
+
+/* Adapted from include/linux/dynamic_debug.h  */
+
+struct _ddebug {
+	/* [...snip...] */
+	const char *function;
+	const char *filename;
+	const char *format;
+	unsigned int lineno:18;
+	/* [...snip...] */
+	unsigned int flags:8;
+	struct static_key key;
+} __attribute__((aligned(8)));
+
+extern void __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...);
+
+static void expanded_dynamic_pr_debug(void) {
+  do {
+    static struct _ddebug __attribute__((__aligned__(8)))
+    __attribute__((__section__("__dyndbg"))) __UNIQUE_ID_ddebug277 = { /* { dg-warning "track '__UNIQUE_ID_ddebug277': no" } */
+        .function = __func__,
+        .filename = __FILE__,
+        .format = ("hello world"),
+        .lineno = __LINE__,
+        .flags = 0};
+    if (arch_static_branch(&__UNIQUE_ID_ddebug277.key, false))
+      __dynamic_pr_debug(&__UNIQUE_ID_ddebug277,
+			 "hello world");
+  } while (0);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/many-unused-locals.c b/gcc/testsuite/gcc.dg/analyzer/many-unused-locals.c
new file mode 100644
index 00000000000..6b5c85557c9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/many-unused-locals.c
@@ -0,0 +1,69 @@ 
+struct st
+{
+  const char *m_filename;
+  int m_line;
+  const char *m_function;
+};
+
+extern void debug (struct st *);
+
+#define TEST_x_1(NAME)							\
+  do									\
+    {									\
+      static struct st NAME = { __FILE__, __LINE__, __func__ };		\
+      debug (&NAME);							\
+    }									\
+  while (0)
+
+#define TEST_x_10(PREFIX)						\
+  do									\
+    {									\
+      TEST_x_1(PREFIX ## _1);						\
+      TEST_x_1(PREFIX ## _2);						\
+      TEST_x_1(PREFIX ## _3);						\
+      TEST_x_1(PREFIX ## _4);						\
+      TEST_x_1(PREFIX ## _5);						\
+      TEST_x_1(PREFIX ## _6);						\
+      TEST_x_1(PREFIX ## _7);						\
+      TEST_x_1(PREFIX ## _8);						\
+      TEST_x_1(PREFIX ## _9);						\
+      TEST_x_1(PREFIX ## _10);						\
+    }									\
+  while(0)
+
+#define TEST_x_100(PREFIX)						\
+  do									\
+    {									\
+      TEST_x_10(PREFIX ## _1);						\
+      TEST_x_10(PREFIX ## _2);						\
+      TEST_x_10(PREFIX ## _3);						\
+      TEST_x_10(PREFIX ## _4);						\
+      TEST_x_10(PREFIX ## _5);						\
+      TEST_x_10(PREFIX ## _6);						\
+      TEST_x_10(PREFIX ## _7);						\
+      TEST_x_10(PREFIX ## _8);						\
+      TEST_x_10(PREFIX ## _9);						\
+      TEST_x_10(PREFIX ## _10);						\
+    }									\
+  while(0)
+
+#define TEST_x_1000(PREFIX)						\
+  do									\
+    {									\
+      TEST_x_100(PREFIX ## _1);						\
+      TEST_x_100(PREFIX ## _2);						\
+      TEST_x_100(PREFIX ## _3);						\
+      TEST_x_100(PREFIX ## _4);						\
+      TEST_x_100(PREFIX ## _5);						\
+      TEST_x_100(PREFIX ## _6);						\
+      TEST_x_100(PREFIX ## _7);						\
+      TEST_x_100(PREFIX ## _8);						\
+      TEST_x_100(PREFIX ## _9);						\
+      TEST_x_100(PREFIX ## _10);						\
+    }									\
+  while(0)
+
+void test_many (void)
+{
+  TEST_x_1000(s);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/untracked-1.c b/gcc/testsuite/gcc.dg/analyzer/untracked-1.c
new file mode 100644
index 00000000000..b7536c399fd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/untracked-1.c
@@ -0,0 +1,99 @@ 
+/* { dg-additional-options "-fdump-analyzer-untracked" } */
+
+struct st
+{
+  const char *m_filename;
+  int m_line;
+};
+
+typedef struct boxed_int { int value; } boxed_int;
+
+extern void extern_fn (struct st *);
+static void __attribute__((noinline)) internal_fn (struct st *) {}
+extern int extern_get_int (void);
+
+void test_0 (void)
+{
+  /* Not ever referenced; will get optimized away before
+     analyzer ever sees it, so no message.  */
+  static struct st s1 = { __FILE__, __LINE__ };
+}
+
+void test_1 (void)
+{
+  static struct st s1 = { __FILE__, __LINE__ }; /* { dg-warning "track 's1': no" } */
+  extern_fn (&s1);
+}
+
+static struct st s2 = { __FILE__, __LINE__ }; /* { dg-warning "track 's2': yes" } */
+
+void test_2 (void)
+{
+  extern_fn (&s2);
+}
+
+void test_3 (void)
+{
+  struct st s3 = { __FILE__, __LINE__ }; /* { dg-warning "track 's3': yes" } */
+  extern_fn (&s3);
+}
+
+extern void called_by_test_4 (int *);
+
+int test_4 (void)
+{
+  int i; /* { dg-warning "track 'i': yes" } */
+  called_by_test_4 (&i);
+  return i;
+}
+
+void test_5 (int i)
+{
+  boxed_int bi5 = { i }; /* { dg-warning "track 'bi5': yes" } */
+}
+
+int test_6 (int i)
+{
+  static boxed_int bi6; /* { dg-warning "track 'bi6': yes" } */
+  bi6.value = i;
+  return bi6.value;
+}
+
+int test_7 (void)
+{
+  boxed_int bi7; /* { dg-warning "track 'bi7': yes" } */
+  return bi7.value; /* { dg-warning "use of uninitialized value 'bi7.value'" "uninit" } */
+}
+
+void test_8 (void)
+{
+  static struct st s8 = { __FILE__, __LINE__ }; /* { dg-warning "track 's8': no" } */
+  extern_fn (&s8);
+  extern_fn (&s8);
+}
+
+void test_9 (void)
+{
+  static struct st s9 = { __FILE__, __LINE__ }; /* { dg-warning "track 's9': yes" } */
+  internal_fn (&s9);
+}
+
+int test_10 (void)
+{
+  static struct st s10 = { __FILE__, __LINE__ }; /* { dg-warning "track 's10': yes" } */
+  extern_fn (&s10);
+  return s10.m_line;
+}
+
+int test_11 (void)
+{
+  static struct st s10 = { __FILE__, __LINE__ }; /* { dg-warning "track 's10': yes" } */
+  s10.m_line = extern_get_int ();
+  return 42;
+}
+
+int test_12 (void (*fnptr) (struct st *))
+{
+  static struct st s12 = { __FILE__, __LINE__ }; /* { dg-warning "track 's12': yes" } */
+  fnptr (&s12);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/unused-local-1.c b/gcc/testsuite/gcc.dg/analyzer/unused-local-1.c
new file mode 100644
index 00000000000..361cdced6e3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/unused-local-1.c
@@ -0,0 +1,22 @@ 
+/* { dg-additional-options "-fdump-analyzer-untracked" } */
+
+struct st
+{
+  const char *m_filename;
+  int m_line;
+  const char *m_function;
+};
+
+extern void debug (struct st *);
+
+void test (void)
+{
+  {
+    static struct st s1 = { __FILE__, __LINE__, __func__ }; /* { dg-warning "track 's1': no" } */
+    debug (&s1);
+  }
+  {
+    static struct st s2 = { __FILE__, __LINE__, __func__ }; /* { dg-warning "track 's2': no" } */
+    debug (&s2);
+  }
+}