diff --git a/gcc/btfout.cc b/gcc/btfout.cc
index 0af0bd39fc7..93d56492bbe 100644
--- a/gcc/btfout.cc
+++ b/gcc/btfout.cc
@@ -833,7 +833,10 @@ output_btf_types (ctf_container_ref ctfc)
 {
   size_t i;
   size_t num_types;
-  num_types = ctfc->ctfc_types->elements ();
+  if (flag_prune_btf)
+    num_types = max_translated_id;
+  else
+    num_types = ctfc->ctfc_types->elements ();
 
   if (num_types)
     {
@@ -962,6 +965,211 @@ btf_early_add_func_records (ctf_container_ref ctfc)
     }
 }
 
+/* The set of types used directly in the source program, and any types manually
+   marked as used.  This is the set of types which will be emitted when
+   pruning (-fprune-btf) is enabled.  */
+static GTY (()) hash_set<ctf_dtdef_ref> *btf_minimal_types;
+
+/* Fixup used to avoid unnecessary pointer chasing for types.  A fixup is
+   created when a structure or union member is a pointer to another struct
+   or union type.  In such cases, avoid emitting full type information for
+   the pointee struct or union type (which may be quite large), unless that
+   type is used directly elsewhere.  */
+struct btf_fixup
+{
+  ctf_dtdef_ref pointer_dtd; /* Type node to which the fixup is applied.  */
+  ctf_dtdef_ref pointee_dtd; /* Original type node referred to by pointer_dtd.
+				If this concrete type is not otherwise used,
+				then a forward is created.  */
+};
+
+/* Stores fixups while processing types.  */
+static vec<struct btf_fixup> fixups;
+
+/* For fixups where the underlying type is not used in the end, a BTF_KIND_FWD
+   is created and emitted.  This vector stores them.  */
+static GTY (()) vec<ctf_dtdef_ref, va_gc> *forwards;
+
+/* Recursively add type DTD and any types it references to the used set.
+   Return a type that should be used for references to DTD - usually DTD itself,
+   but may be NULL if DTD corresponds to a type which will not be emitted.
+   CHECK_PTR is true if one of the predecessors in recursive calls is a struct
+   or union member.  SEEN_PTR is true if CHECK_PTR is true AND one of the
+   predecessors was a pointer type.  These two flags are used to avoid chasing
+   pointers to struct/union only used from pointer members.  For such types, we
+   will emit a forward instead of the full type information.  */
+
+static ctf_dtdef_ref
+btf_minimal_add_type (ctf_container_ref ctfc, ctf_dtdef_ref dtd,
+		      bool check_ptr, bool seen_ptr)
+{
+  if (dtd == NULL)
+    return NULL;
+
+  uint32_t ctf_kind = CTF_V2_INFO_KIND (dtd->dtd_data.ctti_info);
+  uint32_t kind = get_btf_kind (ctf_kind);
+
+  /* Check whether the type has already been added.  */
+  if (btf_minimal_types->contains (dtd))
+    {
+      /* It's possible the type was already added as a fixup, but that we now
+	 have a concrete use of it.  */
+      switch (kind)
+	{
+	case BTF_KIND_PTR:
+	case BTF_KIND_TYPEDEF:
+	case BTF_KIND_CONST:
+	case BTF_KIND_VOLATILE:
+	case BTF_KIND_RESTRICT:
+	  if (check_ptr)
+	    /* Type was previously added as a fixup, and that's OK.  */
+	    return dtd;
+	  else
+	    {
+	      /* The type was previously added as a fixup, but now we have
+		 a concrete use of it.  Remove the fixup.  */
+	      for (size_t i = 0; i < fixups.length (); i++)
+		if (fixups[i].pointer_dtd == dtd)
+		  fixups.unordered_remove (i);
+
+	      /* Add the concrete base type.  */
+	      dtd->ref_type = btf_minimal_add_type (ctfc, dtd->ref_type,
+						    check_ptr, seen_ptr);
+	      return dtd;
+	    }
+	default:
+	  return dtd;
+	}
+    }
+
+  if (ctf_kind == CTF_K_SLICE)
+    {
+      /* Bitfield.  Add the underlying type to the used set, but leave
+	 the reference to the bitfield.  The slice type won't be emitted,
+	 but we need the information in it when writing out the bitfield
+	 encoding.  */
+      btf_minimal_add_type (ctfc, dtd->dtd_u.dtu_slice.cts_type,
+			    check_ptr, seen_ptr);
+      return dtd;
+    }
+
+  /* Skip redundant definitions of void and types with no BTF encoding.  */
+  if ((kind == BTF_KIND_INT && dtd->dtd_data.ctti_size == 0)
+      || (kind == BTF_KIND_UNKN))
+    return NULL;
+
+  /* Add the type itself, and assign its id.
+     Do this before recursing to handle things like linked list structures.  */
+  gcc_assert (ctfc->ctfc_nextid <= BTF_MAX_TYPE);
+  dtd->dtd_type = ctfc->ctfc_nextid++;
+  btf_minimal_types->add (dtd);
+  ctf_add_string (ctfc, dtd->dtd_name, &(dtd->dtd_data.ctti_name), CTF_STRTAB);
+  ctfc->ctfc_num_types++;
+  ctfc->ctfc_num_vlen_bytes += btf_calc_num_vbytes (dtd);
+
+  /* Recursively add types referenced by this type.  */
+  switch (kind)
+    {
+    case BTF_KIND_INT:
+    case BTF_KIND_FLOAT:
+    case BTF_KIND_FWD:
+      /* Leaf kinds which do not refer to any other types.  */
+      break;
+
+    case BTF_KIND_FUNC:
+    case BTF_KIND_VAR:
+      /* Root kinds; no type we are visiting may refer to these.  */
+      gcc_unreachable ();
+
+    case BTF_KIND_PTR:
+    case BTF_KIND_TYPEDEF:
+    case BTF_KIND_CONST:
+    case BTF_KIND_VOLATILE:
+    case BTF_KIND_RESTRICT:
+      {
+	/* These type kinds refer to exactly one other type.  */
+	if (check_ptr && !seen_ptr)
+	  seen_ptr = (kind == BTF_KIND_PTR);
+
+	/* Try to avoid chasing pointers to struct/union types if the
+	   underlying type isn't used.  */
+	if (check_ptr && seen_ptr)
+	  {
+	    ctf_dtdef_ref ref = dtd->ref_type;
+	    uint32_t ref_kind = btf_dtd_kind (ref);
+
+	    if ((ref_kind == BTF_KIND_STRUCT || ref_kind == BTF_KIND_UNION)
+		&& !btf_minimal_types->contains (ref))
+	      {
+		struct btf_fixup fixup;
+		fixup.pointer_dtd = dtd;
+		fixup.pointee_dtd = ref;
+		fixups.safe_push (fixup);
+		break;
+	      }
+	  }
+
+	/* Add the type to which this type refers.  */
+	dtd->ref_type = btf_minimal_add_type (ctfc, dtd->ref_type,
+					      check_ptr, seen_ptr);
+	break;
+      }
+    case BTF_KIND_ARRAY:
+      {
+	/* Add element and index types.  */
+	ctf_arinfo_t *arr = &(dtd->dtd_u.dtu_arr);
+	arr->ctr_contents = btf_minimal_add_type (ctfc, arr->ctr_contents,
+						  false, false);
+	arr->ctr_index = btf_minimal_add_type (ctfc, arr->ctr_index,
+					       false, false);
+	break;
+      }
+    case BTF_KIND_STRUCT:
+    case BTF_KIND_UNION:
+    case BTF_KIND_ENUM:
+    case BTF_KIND_ENUM64:
+      {
+	/* Add members.  */
+	ctf_dmdef_t *dmd;
+	for (dmd = dtd->dtd_u.dtu_members;
+	     dmd != NULL; dmd = (ctf_dmdef_t *) ctf_dmd_list_next (dmd))
+	  {
+	    /* Add member type for struct/union members.  For enums, only the
+	       enumerator names are needed.  */
+	    if (kind == BTF_KIND_STRUCT || kind == BTF_KIND_UNION)
+	      dmd->dmd_type = btf_minimal_add_type (ctfc, dmd->dmd_type,
+						    true, false);
+	    ctf_add_string (ctfc, dmd->dmd_name, &(dmd->dmd_name_offset),
+			    CTF_STRTAB);
+	  }
+	break;
+      }
+    case BTF_KIND_FUNC_PROTO:
+      {
+	/* Add return type.  */
+	dtd->ref_type = btf_minimal_add_type (ctfc, dtd->ref_type,
+					      false, false);
+
+	/* Add arg types.  */
+	ctf_func_arg_t * farg;
+	for (farg = dtd->dtd_u.dtu_argv;
+	     farg != NULL; farg = (ctf_func_arg_t *) ctf_farg_list_next (farg))
+	  {
+	    farg->farg_type = btf_minimal_add_type (ctfc, farg->farg_type,
+						    false, false);
+	    /* Note: argument names are stored in the auxilliary string table,
+	       since CTF does not include arg names.  That table has not been
+	       cleared, so no need to re-add argument names here.  */
+	  }
+	break;
+      }
+    default:
+      return NULL;
+    }
+
+  return dtd;
+}
+
 /* Initial entry point of BTF generation, called at early_finish () after
    CTF information has possibly been output.  Translate all CTF information
    to BTF, and do any processing that must be done early, such as creating
@@ -977,6 +1185,27 @@ btf_early_finish (void)
 
   btf_early_add_const_void (tu_ctfc);
   btf_early_add_func_records (tu_ctfc);
+
+  /* Note: from here on, destructive changes are made to the TU CTFC to
+     translate CTF to BTF.  These fields are reset to count BTF types etc.  */
+  tu_ctfc->ctfc_num_types = 0;
+  tu_ctfc->ctfc_num_vlen_bytes = 0;
+  tu_ctfc->ctfc_vars_list_count = 0;
+
+  if (flag_prune_btf)
+    {
+      btf_minimal_types
+	= hash_set<ctf_dtdef_ref>::create_ggc (tu_ctfc->ctfc_types->elements ());
+      tu_ctfc->ctfc_nextid = 1;
+      fixups.create (1);
+
+      /* Empty the string table, which was already populated with strings for
+	 all types translated from DWARF.  We may only need a very small subset
+	 of these strings; those will be re-added below.  */
+      ctfc_delete_strtab (&tu_ctfc->ctfc_strtable);
+      init_ctf_strtable (&tu_ctfc->ctfc_strtable);
+      tu_ctfc->ctfc_strlen++;
+    }
 }
 
 /* Push a BTF datasec entry ENTRY into the datasec named SECNAME,
@@ -1134,6 +1363,51 @@ btf_emit_variable_p (ctf_container_ref ctfc, varpool_node *var,
   return true;
 }
 
+
+/* Recursively mark T and all children types as used to prevent them
+   being pruned.  */
+
+static void
+btf_mark_full_type_used (ctf_container_ref ctfc, tree t)
+{
+  ctf_dtdef_ref dtd = ctf_lookup_tree_type (ctfc, t);
+  if (!dtd)
+    return;
+
+  if (dtd->visited_children_p)
+    return;
+
+  btf_minimal_add_type (ctfc, dtd, false, false);
+  dtd->visited_children_p = true;
+
+  /* Note that it is only necessary here to visit descendents which may be
+     pruned by replacing with a forward, i.e. pointed-to struct/union types.  */
+  switch (TREE_CODE (t))
+    {
+    case POINTER_TYPE:
+    case REFERENCE_TYPE:
+      btf_mark_full_type_used (ctfc, TREE_TYPE (t));
+      break;
+
+    case RECORD_TYPE:
+    case UNION_TYPE:
+      {
+	tree member = TYPE_FIELDS (t);
+	while (member != NULL_TREE)
+	  {
+	    if (DECL_ABSTRACT_ORIGIN (member))
+	      continue;
+
+	    btf_mark_full_type_used (ctfc, TREE_TYPE (member));
+	    member = DECL_CHAIN (member);
+	  }
+	break;
+      }
+    default:
+      break;
+    }
+}
+
 /* Add BTF_KIND_VAR records for variables.  */
 
 static void
@@ -1159,6 +1433,14 @@ btf_late_add_vars (ctf_container_ref ctfc)
 
       /* Add a BTF_KIND_DATASEC entry for the variable.  */
       btf_datasec_add_var (ctfc, var, dvd);
+
+      /* Special case: for BPF CO-RE, the .maps section is special.
+	 Full type info for anything in .maps is always required.  */
+      const char *section = var->get_section ();
+      if (section && (strcmp (section, ".maps") == 0)
+	  && btf_with_core_debuginfo_p () && flag_prune_btf)
+	btf_mark_full_type_used (ctfc, TREE_TYPE (var->decl));
+
     }
 }
 
@@ -1257,6 +1539,88 @@ btf_late_assign_datasec_ids (ctf_container_ref ctfc)
     }
 }
 
+/* Callback used for assembling the only-used-types list.  Note that this is
+   the same as btf_type_list_cb above, but the hash_set traverse requires a
+   different function signature.  */
+
+static bool
+btf_minimal_type_list_cb (const ctf_dtdef_ref& dtd, ctf_container_ref ctfc)
+{
+  ctfc->ctfc_types_list[dtd->dtd_type] = dtd;
+  return true;
+}
+
+/* Collect the set of types reachable from global variables and functions.
+   This is the minimal set of types, used when generating pruned BTF.  */
+
+static void
+btf_late_collect_pruned_types (ctf_container_ref ctfc)
+{
+  vec_alloc (forwards, 1);
+
+  /* Add types used from functions.  */
+  ctf_dtdef_ref dtd;
+  size_t i;
+  FOR_EACH_VEC_ELT (*funcs, i, dtd)
+    {
+      btf_minimal_add_type (ctfc, dtd->ref_type, false, false);
+      ctf_add_string (ctfc, dtd->dtd_name, &(dtd->dtd_data.ctti_name),
+		      CTF_STRTAB);
+    }
+
+  /* Add types used from global variables.  */
+  for (i = 0; i < ctfc->ctfc_vars_list_count; i++)
+    {
+      ctf_dvdef_ref dvd = ctfc->ctfc_vars_list[i];
+      btf_minimal_add_type (ctfc, dvd->dvd_type, false, false);
+      ctf_add_string (ctfc, dvd->dvd_name, &(dvd->dvd_name_offset), CTF_STRTAB);
+    }
+
+  /* Process fixups. If the base type was never added, create a forward for it
+     and adjust the reference to point to that.  If it was added, then nothing
+     needs to change.  */
+  for (i = 0; i < fixups.length (); i++)
+    {
+      struct btf_fixup *fx = &fixups[i];
+      if (!btf_minimal_types->contains (fx->pointee_dtd))
+	{
+	  /* The underlying type is not used.  Create a forward.  */
+	  ctf_dtdef_ref fwd = ggc_cleared_alloc<ctf_dtdef_t> ();
+	  ctf_id_t id = ctfc->ctfc_nextid++;
+	  gcc_assert (id <= BTF_MAX_TYPE);
+
+	  bool union_p = (btf_dtd_kind (fx->pointee_dtd) == BTF_KIND_UNION);
+
+	  fwd->dtd_name = fx->pointee_dtd->dtd_name;
+	  fwd->dtd_data.ctti_info = CTF_TYPE_INFO (CTF_K_FORWARD, union_p, 0);
+	  fwd->dtd_type = id;
+	  ctfc->ctfc_num_types++;
+	  ctfc->ctfc_num_vlen_bytes += btf_calc_num_vbytes (fwd);
+	  ctf_add_string (ctfc, fwd->dtd_name, &(fwd->dtd_data.ctti_name),
+			  CTF_STRTAB);
+
+	  /* Update the pointer to point to the forward.  */
+	  fx->pointer_dtd->ref_type = fwd;
+	  vec_safe_push (forwards, fwd);
+	}
+    }
+
+  /* Construct the resulting pruned type list.  */
+  ctfc->ctfc_types_list
+    = ggc_vec_alloc<ctf_dtdef_ref> (btf_minimal_types->elements () + 1
+				    + vec_safe_length (forwards));
+
+  btf_minimal_types->traverse<ctf_container_ref, btf_minimal_type_list_cb>
+    (ctfc);
+
+  /* Insert the newly created forwards into the regular types list too.  */
+  FOR_EACH_VEC_ELT (*forwards, i, dtd)
+    ctfc->ctfc_types_list[dtd->dtd_type] = dtd;
+
+  max_translated_id = btf_minimal_types->elements ()
+    + vec_safe_length (forwards);
+}
+
 /* Late entry point for BTF generation, called from dwarf2out_finish ().
    Complete and emit BTF information.  */
 
@@ -1268,13 +1632,22 @@ btf_finish (void)
 
   datasecs.create (0);
 
-  tu_ctfc->ctfc_num_types = 0;
-  tu_ctfc->ctfc_num_vlen_bytes = 0;
-  tu_ctfc->ctfc_vars_list_count = 0;
-
   btf_late_add_vars (tu_ctfc);
-  btf_late_collect_translated_types (tu_ctfc);
+  if (flag_prune_btf)
+    {
+      /* Collect pruned set of BTF types and prepare for emission.
+	 This includes only types directly used in file-scope variables and
+	 function return/argument types.  */
+      btf_late_collect_pruned_types (tu_ctfc);
+    }
+  else
+    {
+      /* Collect all BTF types and prepare for emission.
+	 This includes all types translated from DWARF.  */
+      btf_late_collect_translated_types (tu_ctfc);
+    }
   btf_late_add_func_datasec_entries (tu_ctfc);
+
   btf_late_assign_var_ids (tu_ctfc);
   btf_late_assign_func_ids (tu_ctfc);
   btf_late_assign_datasec_ids (tu_ctfc);
@@ -1307,6 +1680,15 @@ btf_finalize (void)
   func_map->empty ();
   func_map = NULL;
 
+  if (flag_prune_btf)
+    {
+      btf_minimal_types->empty ();
+      btf_minimal_types = NULL;
+
+      fixups.release ();
+      forwards = NULL;
+    }
+
   ctf_container_ref tu_ctfc = ctf_get_tu_ctfc ();
   ctfc_delete_container (tu_ctfc);
   tu_ctfc = NULL;
diff --git a/gcc/common.opt b/gcc/common.opt
index ad348844775..c430cfd0428 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2578,6 +2578,10 @@ fpatchable-function-entry=
 Common Var(flag_patchable_function_entry) Joined Optimization
 Insert NOP instructions at each function entry.
 
+fprune-btf
+Common Var(flag_prune_btf) Init(0)
+Generate minimal BTF debug info.
+
 frandom-seed
 Common Var(common_deferred_options) Defer
 
diff --git a/gcc/ctfc.cc b/gcc/ctfc.cc
index d8f1037b4e0..7681af8542d 100644
--- a/gcc/ctfc.cc
+++ b/gcc/ctfc.cc
@@ -913,7 +913,7 @@ ctfc_get_dvd_srcloc (ctf_dvdef_ref dvd, ctf_srcloc_ref loc)
 /* Initialize the CTF string table.
    The first entry in the CTF string table (empty string) is added.  */
 
-static void
+void
 init_ctf_strtable (ctf_strtable_t * strtab)
 {
   strtab->ctstab_head = NULL;
diff --git a/gcc/ctfc.h b/gcc/ctfc.h
index 90421c72c09..bd3d98ffed9 100644
--- a/gcc/ctfc.h
+++ b/gcc/ctfc.h
@@ -168,6 +168,8 @@ struct GTY ((for_user)) ctf_dtdef
   BOOL_BITFIELD from_global_func : 1;
   /* Enum signedness.  */
   BOOL_BITFIELD dtd_enum_unsigned : 1;
+  /* Already visited children of this type, e.g. struct members.  */
+  BOOL_BITFIELD visited_children_p : 1;
   /* Lots of spare bits.  */
 
   union GTY ((desc ("ctf_dtu_d_union_selector (&%1)")))
@@ -369,6 +371,9 @@ extern unsigned int ctfc_get_num_ctf_vars (ctf_container_ref);
 
 extern ctf_strtable_t * ctfc_get_strtab (ctf_container_ref, int);
 
+extern void init_ctf_strtable (ctf_strtable_t *);
+extern void ctfc_delete_strtab (ctf_strtable_t *);
+
 /* Get the length of the specified string table in the CTF container.  */
 
 extern size_t ctfc_get_strtab_len (ctf_container_ref, int);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9456ced468a..2908afff7c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -533,6 +533,7 @@ Objective-C and Objective-C++ Dialects}.
 -gvms -gz@r{[}=@var{type}@r{]}
 -gsplit-dwarf  -gdescribe-dies  -gno-describe-dies
 -fdebug-prefix-map=@var{old}=@var{new}  -fdebug-types-section
+-fprune-btf -fno-prune-btf
 -fno-eliminate-unused-debug-types
 -femit-struct-debug-baseonly  -femit-struct-debug-reduced
 -femit-struct-debug-detailed@r{[}=@var{spec-list}@r{]}
@@ -12234,6 +12235,25 @@ compressed debug sections, the option is rejected.  Otherwise, if the
 assembler does not support them, @option{-gz} is silently ignored when
 producing object files.
 
+@opindex fprune-btf
+@opindex fno-prune-btf
+@item -fprune-btf
+@itemx -fno-prune-btf
+Prune BTF information before emission.  When pruning, only type
+information for types used by global variables and file-scope functions
+will be emitted.  If compiling for the BPF target with BPF CO-RE
+enabled, type information will also be emitted for types used in BPF
+CO-RE relocations.  In addition, struct and union types which are only
+referred to via pointers from members of other struct or union types
+shall be pruned and replaced with BTF_KIND_FWD, as though those types
+were only present in the input as forward declarations.
+
+This option substantially reduces the size of produced BTF information,
+but at significant loss in the amount of detailed type information.
+It is primarily useful when compiling for the BPF target, to minimize
+the size of the resulting object, and to eliminate BTF information
+which is not immediately relevant to the BPF program loading process.
+
 @opindex femit-struct-debug-baseonly
 @item -femit-struct-debug-baseonly
 Emit debug information for struct-like types
diff --git a/gcc/testsuite/gcc.dg/debug/btf/btf-prune-1.c b/gcc/testsuite/gcc.dg/debug/btf/btf-prune-1.c
new file mode 100644
index 00000000000..3c9b59a07ec
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/btf/btf-prune-1.c
@@ -0,0 +1,25 @@
+/* Simple test of -fprune-btf option operation.
+   Since 'struct foo' is not used, no BTF shall be emitted for it.  */
+
+/* { dg-do compile } */
+/* { dg-options "-gbtf -fprune-btf -dA" } */
+
+/* No BTF info for 'struct foo' nor types used only by it.  */
+/* { dg-final { scan-assembler-not "BTF_KIND_STRUCT 'foo'" } } */
+/* { dg-final { scan-assembler-not "BTF_KIND_INT 'char'" } } */
+
+/* We should get BTF info for 'struct bar' since it is used.  */
+/* { dg-final { scan-assembler "BTF_KIND_STRUCT 'bar'"} } */
+
+struct foo {
+  int a;
+  char c;
+};
+
+struct bar {
+  int x;
+  long z[4];
+};
+
+struct bar a_bar;
+
diff --git a/gcc/testsuite/gcc.dg/debug/btf/btf-prune-2.c b/gcc/testsuite/gcc.dg/debug/btf/btf-prune-2.c
new file mode 100644
index 00000000000..20183dffcc7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/btf/btf-prune-2.c
@@ -0,0 +1,33 @@
+/* Test that -fprune-btf does not chase pointer-to-struct members.  */
+
+/* { dg-do compile } */
+/* { dg-options "-gbtf -fprune-btf -dA" } */
+
+/* Only use of B is via a pointer member of C.
+   Full BTF for B is replaced with a forward.  */
+/* { dg-final { scan-assembler-not "BTF_KIND_STRUCT 'B'" } } */
+/* { dg-final { scan-assembler-times "TYPE \[0-9\]+ BTF_KIND_FWD 'B'" 1 } } */
+
+/* Detailed info for B is omitted, and A is otherwise unused.  */
+/* { dg-final { scan-assembler-not "BTF_KIND_\[A-Z\]+ 'A'" } } */
+
+/* { dg-final { scan-assembler "BTF_KIND_STRUCT 'C'" } } */
+
+struct A;
+
+struct B {
+  int x;
+  int (*do_A_thing) (int, int);
+  struct A *other;
+};
+
+struct C {
+  unsigned int x;
+  struct B * a;
+};
+
+int
+foo (struct C *c)
+{
+  return c->x;
+}
diff --git a/gcc/testsuite/gcc.dg/debug/btf/btf-prune-3.c b/gcc/testsuite/gcc.dg/debug/btf/btf-prune-3.c
new file mode 100644
index 00000000000..57a079cf0b4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/btf/btf-prune-3.c
@@ -0,0 +1,35 @@
+/* Test that -fprune-btf */
+
+/* { dg-do compile } */
+/* { dg-options "-gbtf -fprune-btf -dA" } */
+
+/* We expect full BTF information each struct.  */
+/* { dg-final { scan-assembler "TYPE \[0-9\]+ BTF_KIND_FWD 'file'" } } */
+/* { dg-final { scan-assembler "TYPE \[0-9\]+ BTF_KIND_STRUCT 'A'" } } */
+/* { dg-final { scan-assembler "TYPE \[0-9\]+ BTF_KIND_STRUCT 'B'" } } */
+/* { dg-final { scan-assembler "TYPE \[0-9\]+ BTF_KIND_STRUCT 'C'" } } */
+
+struct file;
+
+struct A {
+  void *private;
+  long (*read)(struct file *, char *, unsigned long);
+  long (*write)(struct file *, const char *, unsigned long);
+};
+
+struct B {
+  unsigned int x;
+  struct A **as;
+};
+
+struct C {
+  struct A *arr_a[4];
+  struct A *lone_a;
+  unsigned int z;
+};
+
+unsigned int
+foo (struct B *b, struct C *c)
+{
+  return b->x + c->z;
+}
