Re: [PATCH 04/12] btf_encoder: Encode variant parts as union members in BTF

Yonghong Song <[email protected]>
Newsgroups org.kernel.vger.dwarves,org.kernel.vger.bpf
Message-ID <[email protected]>

On 7/31/26 12:30 PM, Arnaldo Carvalho de Melo wrote:
> 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

FYI, the below is for llvm bpf backend to handle DW_TAG_variant_part
to generate BTF.
   link: https://github.com/llvm/llvm-project/pull/155783
   
For a rust code like:

; Source:
;   #![no_std]
;   #![no_main]
;
;   pub enum MyEnum {
;       First { a: u32, b: i32 },
;       Second(u32),
;   }
;
;   #[unsafe(no_mangle)]
;   pub static X: MyEnum = MyEnum::First { a: 54, b: -23 };
;
;   #[cfg(not(test))]
;   #[panic_handler]
;   fn panic(_info: &core::panic::PanicInfo) -> ! {
;       loop {}
;   }

The BTF encoding:

; CHECK-BTF:      [1] STRUCT 'MyEnum' size=12 vlen=1
; CHECK-BTF-NEXT:         '(anon)' type_id=3 bits_offset=0
; CHECK-BTF-NEXT: [2] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
; CHECK-BTF-NEXT: [3] UNION '(anon)' size=12 vlen=3
; CHECK-BTF-NEXT:         '(anon)' type_id=2 bits_offset=0
; CHECK-BTF-NEXT:         'First' type_id=4 bits_offset=0
; CHECK-BTF-NEXT:         'Second' type_id=6 bits_offset=0
; CHECK-BTF-NEXT: [4] STRUCT 'First' size=12 vlen=2
; CHECK-BTF-NEXT:         'a' type_id=2 bits_offset=32
; CHECK-BTF-NEXT:         'b' type_id=5 bits_offset=64
; CHECK-BTF-NEXT: [5] INT 'i32' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
; CHECK-BTF-NEXT: [6] STRUCT 'Second' size=12 vlen=1
; CHECK-BTF-NEXT:         '__0' type_id=2 bits_offset=32
; CHECK-BTF-NEXT: [7] VAR 'X' type_id=1, linkage=global
; CHECK-BTF-NEXT: [8] DATASEC '.rodata' size=0 vlen=1
; CHECK-BTF-NEXT:         type_id=7 offset=0 size=12

>
> 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;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.