Message ID | 20240502171132.95601-6-david.faust@oracle.com |
---|---|
State | New |
Headers | show |
Series | btf: refactor and add pruning option | expand |
On 5/2/24 10:11, David Faust wrote: > This patch adds a new option, -fprune-btf, to control BTF debug info > generation. > > As the name implies, this option enables a kind of "pruning" of the BTF > information before it is emitted. When enabled, rather than emitting > all type information translated from DWARF, only information for types > directly used in the source program is emitted. > > The primary purpose of this pruning is to reduce the amount of > unnecessary BTF information emitted, especially for BPF programs. It is > very common for BPF programs to incldue Linux kernel internal headers in > order to have access to kernel data structures. However, doing so often > has the side effect of also adding type definitions for a large number > of types which are not actually used by nor relevant to the program. > In these cases, -fprune-btf commonly reduces the size of the resulting > BTF information by approximately 10x. This both slims down the size of > the resulting object and reduces the time required by the BPF loader to > verify the program and its BTF information. > The 10x reduction is substantial. Do you think its is worthwhile to mention alongside that this data is the average observed for the kernel self-tests (I assume it is) ? Just useful info when parsing the commit logs, especially when some data is specified... > Note that the pruning implemented in this patch follows the same rules > as the BTF pruning performed unconditionally by LLVM's BPF backend when > generating BTF. In particular, the main sources of pruning are: > > 1) Only generate BTF for types used by variables and functions at > the file scope. > I dont recollect anymore if BTF_KIND_VAR for unused static vars is also a correctness issue for BTF. (With PR debug/113566, we know having BTF_KIND_DATASEC entries for optimized away vars is an issue). It will be great to add some text here or elsewhere for posterity around this. > 2) Avoid emitting full BTF for struct and union types which are only > pointed-to by members of other struct/union types. In these cases, > the full BTF_KIND_STRUCT or BTF_KIND_UNION which would normally > be emitted is replaced with a BTF_KIND_FWD, as though the > underlying type was a forward-declared struct or union type. > > gcc/ > * btfout.cc (btf_minimal_types): New hash set. > (struct btf_fixup): New. > (fixups, forwards): New vecs. > (btf_output): Calculate num_types depending on flag_prune_btf. > (btf_early_finsih): New initialization for flag_prune_btf. > (btf_mark_full_type_used): Likewise. > (btf_minimal_add_type): New function. > (btf_minimal_type_list_cb): Likewise. > (btf_late_collect_pruned_types): Likewise. > (btf_late_add_vars): Handle special case for variables in ".maps" > section when generating BTF for BPF CO-RE target. > (btf_late_finish): Use btf_late_collect_pruned_types when > flag_prune_btf in effect. Move some initialization to btf_early_finish. > (btf_finalize): Additional deallocation for flag_prune_btf. > * common.opt (fprune-btf): New flag. > * ctfc.cc (init_ctf_strtable): Make non-static. > * ctfc.h (struct ctf_dtdef): Add visited_children_p boolean flag. > (init_ctf_strtable, ctfc_delete_strtab): Make extern. > * doc/invoke.texi (Debugging Options): Document -fprune-btf. > > gcc/testsuite/ > * gcc.dg/debug/btf/btf-prune-1.c: New test. > * gcc.dg/debug/btf/btf-prune-2.c: Likewise. > * gcc.dg/debug/btf/btf-prune-3.c: Likewise. > --- > gcc/btfout.cc | 394 ++++++++++++++++++- > gcc/common.opt | 4 + > gcc/ctfc.cc | 2 +- > gcc/ctfc.h | 5 + > gcc/doc/invoke.texi | 20 + > gcc/testsuite/gcc.dg/debug/btf/btf-prune-1.c | 25 ++ > gcc/testsuite/gcc.dg/debug/btf/btf-prune-2.c | 33 ++ > gcc/testsuite/gcc.dg/debug/btf/btf-prune-3.c | 35 ++ > 8 files changed, 511 insertions(+), 7 deletions(-) > create mode 100644 gcc/testsuite/gcc.dg/debug/btf/btf-prune-1.c > create mode 100644 gcc/testsuite/gcc.dg/debug/btf/btf-prune-2.c > create mode 100644 gcc/testsuite/gcc.dg/debug/btf/btf-prune-3.c > > 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. */ Nit: emitted when flag_prune_btf is set ? > +static GTY (()) hash_set<ctf_dtdef_ref> *btf_minimal_types; > + Curious to know if the choice of "btf_minimal_types" is deliberate over say "btf_used_types". Either is fine, although I see that in the comments, we say "used types". Its a nit really, so feel free to ignore. > +/* 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. */ > +}; > + Is it possible to guard the creation and usage of fixups behind a variable/argument ? The thinking is that there may be a future use case where a consumer wants only the unused types but no further "approximation" of converting sou to forwards. It may also be useful for debugging / testing for us. It will be easy to organize the code in that way now, rather than later adapting the code. While I have given the patch one quick look, I need more time to play around with this a bit. I am taking some time off next week, but I hope to come back to this after that. > +/* 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; > +}
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; +}