[PATCH 04/12] btf_encoder: Encode variant parts as union members in BTF
Arnaldo Carvalho de Melo <[email protected]> Fri, 31 Jul 2026 16:30:52 -0300
| Newsgroups | org.kernel.vger.dwarves,org.kernel.vger.bpf |
|---|---|
| Message-ID | <[email protected]> |
From: Arnaldo Carvalho de Melo <[email protected]> With the DWARF loader now populating DW_TAG_variant children (previous commit), wire them into BTF encoding so Rust discriminated unions (Option<T>, Result<T,E>, etc.) are no longer emitted as empty structs. Two changes: 1. Struct-to-union promotion: when a DW_TAG_structure_type has variant_parts but no regular data members, encode it as BTF_KIND_UNION instead of BTF_KIND_STRUCT, since the variants overlap at offset 0. 2. Variant member encoding: after encoding regular data members, iterate the variant_parts and emit each variant as a BTF union field with the variant's name and resolved type reference. Testing with the sashiko-cli Rust binary (a real-world async HTTP client using tokio, hyper, serde, etc.): Before: $ bpftool btf dump file sashiko-cli | grep -c UNION 2073 $ bpftool btf dump file sashiko-cli | grep 'STRUCT.*vlen=0' | grep -vc 'size=0' 24335 After: $ bpftool btf dump file sashiko-cli | grep -c UNION 25750 $ bpftool btf dump file sashiko-cli | grep 'STRUCT.*vlen=0' | grep -vc 'size=0' 2236 22,099 types that were previously encoded as empty structs are now properly represented as unions with their variant members: Before: $ bpftool btf dump file code_with_type.o | grep -A1 'Option<u32>' [11] STRUCT 'Option<u32>' size=8 vlen=0 After: $ bpftool btf dump file code_with_type.o | grep -A3 'Option<u32>' [11] UNION 'Option<u32>' size=8 vlen=2 'None' type_id=9 bits_offset=0 'Some' type_id=10 bits_offset=0 Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: Arnaldo Carvalho de Melo <[email protected]> --- btf_encoder.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/btf_encoder.c b/btf_encoder.c index 83ce21186ea7da36..acf0a5ade5e29014 100644 --- a/btf_encoder.c +++ b/btf_encoder.c @@ -1855,6 +1855,12 @@ static void dump_invalid_symbol(const char *msg, const char *sym, fprintf(stderr, "PAHOLE: Error: Use '--btf_encode_force' to ignore such symbols and force emit the btf.\n"); } +static bool type__has_variant_parts(const struct type *type) +{ + return !list_empty(&type->variant_parts); +} + + static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct tag *tag) { struct type *type = tag__type(tag); @@ -1863,8 +1869,18 @@ static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct int32_t type_id; uint8_t kind; - kind = (tag->tag == DW_TAG_union_type) ? - BTF_KIND_UNION : BTF_KIND_STRUCT; + /* + * Rust discriminated unions (enums) are represented in DWARF as + * DW_TAG_structure_type with DW_TAG_variant_part children. + * If the struct has only variant parts and no regular data members, + * encode it as a BTF union since the variants overlap at offset 0. + */ + if (tag->tag == DW_TAG_union_type) + kind = BTF_KIND_UNION; + else if (type__has_variant_parts(type) && type->nr_members == 0) + kind = BTF_KIND_UNION; + else + kind = BTF_KIND_STRUCT; type_id = btf_encoder__add_struct(encoder, kind, name, type->size); if (type_id < 0) @@ -1882,6 +1898,23 @@ static int32_t btf_encoder__add_struct_type(struct btf_encoder *encoder, struct return -1; } + if (type__has_variant_parts(type) && kind == BTF_KIND_UNION) { + struct variant_part *vpart; + + type__for_each_variant_part(type, vpart) { + struct variant *variant; + + variant_part__for_each_variant(vpart, variant) { + if (variant->tag.type == 0) + continue; + + uint32_t ref_type_id = btf_encoder__tag_type(encoder, variant->tag.type); + + if (btf_encoder__add_field(encoder, variant->name, ref_type_id, 0, 0)) + return -1; + } + } + } return type_id; -- 2.55.0