[lld] [llvm] [lld][DebugInfo/BTF] Add BTF section merging and deduplication (PR #183915)
Can H. Tartanoglu via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 2 14:46:23 PST 2026
https://github.com/caniko updated https://github.com/llvm/llvm-project/pull/183915
>From 2bf425e09ad9cf439474040dbd828e43d290465a Mon Sep 17 00:00:00 2001
From: "Can H. Tartanoglu" <gpg at rotas.mozmail.com>
Date: Sat, 28 Feb 2026 13:31:47 +0100
Subject: [PATCH] [lld][DebugInfo/BTF] Add BTF section merging and
deduplication
Add support for merging and deduplicating .BTF (BPF Type Format) sections
in lld's ELF linker, enabling the LTO + Rust + BTF pipeline in the Linux
kernel.
The implementation consists of:
1. BTFBuilder (llvm/lib/DebugInfo/BTF/BTFBuilder.cpp): A mutable BTF
container that can parse, merge, and serialize BTF sections. Supports
merging multiple .BTF sections with automatic type ID and string
offset remapping.
2. BTFDedup (llvm/lib/DebugInfo/BTF/BTFDedup.cpp): A 5-pass dedup
algorithm ported from libbpf's btf_dedup:
- String deduplication via hash-based interning
- Primitive type dedup (INT, FLOAT, ENUM, ENUM64, FWD)
- Composite type dedup (STRUCT, UNION) with DFS cycle handling
- Reference type dedup (PTR, TYPEDEF, CONST, VOLATILE, etc.)
- Type compaction and ID remapping
3. BtfSection (lld/ELF/SyntheticSections.cpp): A SyntheticSection that
collects .BTF input sections, merges and deduplicates them, and emits
a single merged .BTF output section. Enabled via --btf-merge flag.
---
lld/ELF/CMakeLists.txt | 1 +
lld/ELF/Config.h | 2 +
lld/ELF/Driver.cpp | 3 +
lld/ELF/Options.td | 4 +
lld/ELF/SyntheticSections.cpp | 49 +
lld/ELF/SyntheticSections.h | 15 +
lld/docs/ld.lld.1 | 7 +
lld/test/ELF/btf-merge-be.s | 63 +
lld/test/ELF/btf-merge.s | 197 ++++
llvm/include/llvm/DebugInfo/BTF/BTFBuilder.h | 96 ++
llvm/include/llvm/DebugInfo/BTF/BTFDedup.h | 49 +
llvm/lib/DebugInfo/BTF/BTFBuilder.cpp | 416 +++++++
llvm/lib/DebugInfo/BTF/BTFDedup.cpp | 777 ++++++++++++
llvm/lib/DebugInfo/BTF/CMakeLists.txt | 2 +
.../DebugInfo/BTF/BTFBuilderTest.cpp | 793 +++++++++++++
llvm/unittests/DebugInfo/BTF/BTFDedupTest.cpp | 1047 +++++++++++++++++
llvm/unittests/DebugInfo/BTF/CMakeLists.txt | 2 +
17 files changed, 3523 insertions(+)
create mode 100644 lld/test/ELF/btf-merge-be.s
create mode 100644 lld/test/ELF/btf-merge.s
create mode 100644 llvm/include/llvm/DebugInfo/BTF/BTFBuilder.h
create mode 100644 llvm/include/llvm/DebugInfo/BTF/BTFDedup.h
create mode 100644 llvm/lib/DebugInfo/BTF/BTFBuilder.cpp
create mode 100644 llvm/lib/DebugInfo/BTF/BTFDedup.cpp
create mode 100644 llvm/unittests/DebugInfo/BTF/BTFBuilderTest.cpp
create mode 100644 llvm/unittests/DebugInfo/BTF/BTFDedupTest.cpp
diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt
index e22897c2789d8..c2a9265f5281b 100644
--- a/lld/ELF/CMakeLists.txt
+++ b/lld/ELF/CMakeLists.txt
@@ -66,6 +66,7 @@ add_lld_library(lldELF
BinaryFormat
BitWriter
Core
+ DebugInfoBTF
DebugInfoDWARF
Demangle
DTLTO
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 237df52194210..afdca7432cbbc 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -332,6 +332,7 @@ struct Config {
bool fixCortexA8;
bool formatBinary = false;
bool fortranCommon;
+ bool btfMerge;
bool gcSections;
bool gdbIndex;
bool gnuHash = false;
@@ -569,6 +570,7 @@ struct InStruct {
std::unique_ptr<SyntheticSection> riscvAttributes;
std::unique_ptr<BssSection> bss;
std::unique_ptr<BssSection> bssRelRo;
+ std::unique_ptr<SyntheticSection> btfSection;
std::unique_ptr<SyntheticSection> gnuProperty;
std::unique_ptr<SyntheticSection> gnuStack;
std::unique_ptr<GotSection> got;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index d7bfa7357d4ed..2c7216dbddf12 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1436,6 +1436,9 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
args.hasArg(OPT_fix_cortex_a8) && !args.hasArg(OPT_relocatable);
ctx.arg.fortranCommon =
args.hasFlag(OPT_fortran_common, OPT_no_fortran_common, false);
+ ctx.arg.btfMerge =
+ args.hasFlag(OPT_btf_merge, OPT_no_btf_merge, false) &&
+ !args.hasArg(OPT_relocatable);
ctx.arg.gcSections = args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, false);
ctx.arg.gnuUnique = args.hasFlag(OPT_gnu_unique, OPT_no_gnu_unique, true);
ctx.arg.gdbIndex = args.hasFlag(OPT_gdb_index, OPT_no_gdb_index, false);
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index c2111e58c12b9..db4081c0c0635 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -63,6 +63,10 @@ defm branch_to_branch: BB<"branch-to-branch",
"Enable branch-to-branch optimization (default at -O2)",
"Disable branch-to-branch optimization (default at -O0 and -O1)">;
+defm btf_merge: BB<"btf-merge",
+ "Merge and deduplicate .BTF sections",
+ "Do not merge .BTF sections (default)">;
+
defm check_sections: B<"check-sections",
"Check section addresses for overlaps (default)",
"Do not check section addresses for overlaps">;
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 2cfc88d8389b0..88775935d4286 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -32,6 +32,8 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/DebugInfo/BTF/BTFBuilder.h"
+#include "llvm/DebugInfo/BTF/BTFDedup.h"
#include "llvm/DebugInfo/DWARF/DWARFAcceleratorTable.h"
#include "llvm/DebugInfo/DWARF/DWARFDebugPubTable.h"
#include "llvm/Support/DJB.h"
@@ -3676,6 +3678,48 @@ void GdbIndexSection::writeTo(uint8_t *buf) {
bool GdbIndexSection::isNeeded() const { return !chunks.empty(); }
+template <class ELFT>
+BtfSection<ELFT>::BtfSection(Ctx &ctx)
+ : SyntheticSection(ctx, ".BTF", SHT_PROGBITS, 0, 1) {
+ llvm::TimeTraceScope timeScope("Merge BTF");
+ const bool isLE = ELFT::Endianness == llvm::endianness::little;
+ llvm::BTFBuilder builder;
+
+ // Collect all .BTF input sections and merge them.
+ // Mark originals dead so they don't appear in the output.
+ for (InputSectionBase *s : ctx.inputSections) {
+ if (s->name != ".BTF" || !s->isLive())
+ continue;
+ s->markDead();
+ ArrayRef<uint8_t> data = s->content();
+ StringRef raw(reinterpret_cast<const char *>(data.data()), data.size());
+ Expected<uint32_t> idBase = builder.merge(raw, isLE);
+ if (!idBase) {
+ Warn(ctx) << s << ": failed to parse .BTF section: "
+ << toString(idBase.takeError());
+ continue;
+ }
+ }
+
+ if (builder.typesCount() == 0)
+ return;
+
+ // Deduplicate merged types.
+ if (Error e = llvm::BTF::dedup(builder)) {
+ Warn(ctx) << "BTF deduplication failed: " << toString(std::move(e));
+ // Fall through and emit un-deduped BTF.
+ builder.write(outputData, isLE);
+ return;
+ }
+
+ builder.write(outputData, isLE);
+}
+
+template <class ELFT>
+void BtfSection<ELFT>::writeTo(uint8_t *buf) {
+ memcpy(buf, outputData.data(), outputData.size());
+}
+
VersionDefinitionSection::VersionDefinitionSection(Ctx &ctx)
: SyntheticSection(ctx, ".gnu.version_d", SHT_GNU_verdef, SHF_ALLOC,
sizeof(uint32_t)) {}
@@ -4902,6 +4946,11 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
add(*ctx.in.gnuProperty);
}
+ if (ctx.arg.btfMerge) {
+ ctx.in.btfSection = std::make_unique<BtfSection<ELFT>>(ctx);
+ add(*ctx.in.btfSection);
+ }
+
if (ctx.arg.debugNames) {
ctx.in.debugNames = std::make_unique<DebugNamesSection<ELFT>>(ctx);
add(*ctx.in.debugNames);
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 1ae03dc24a2f2..314c13f0718b4 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -1013,6 +1013,21 @@ class GdbIndexSection final : public SyntheticSection {
size_t size;
};
+// Merges and deduplicates .BTF (BPF Type Format) sections from multiple input
+// object files into a single output .BTF section. Uses the BTFBuilder/BTFDedup
+// library to parse, merge, and deduplicate BTF type information.
+template <class ELFT>
+class BtfSection final : public SyntheticSection {
+public:
+ BtfSection(Ctx &);
+ void writeTo(uint8_t *buf) override;
+ size_t getSize() const override { return outputData.size(); }
+ bool isNeeded() const override { return !outputData.empty(); }
+
+private:
+ SmallVector<uint8_t, 0> outputData;
+};
+
// For more information about .gnu.version and .gnu.version_r see:
// https://www.akkadia.org/drepper/symbol-versioning
diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1
index cfdde0a6c2299..17d5022103109 100644
--- a/lld/docs/ld.lld.1
+++ b/lld/docs/ld.lld.1
@@ -98,6 +98,13 @@ Enable the branch-to-branch optimizations: a branch whose target is
another branch instruction is rewritten to point to the latter branch
target (AArch64 and X86_64 only). Enabled by default at
.Fl O2 Ns .
+.It Fl -btf-merge
+Merge and deduplicate
+.Li .BTF
+sections from input object files into a single output section.
+Disabled by default.
+Ignored for relocatable links
+.Fl ( r Ns ).
.It Fl -build-id Ns = Ns Ar value
Generate a build ID note.
.Ar value
diff --git a/lld/test/ELF/btf-merge-be.s b/lld/test/ELF/btf-merge-be.s
new file mode 100644
index 0000000000000..43a945f758cdd
--- /dev/null
+++ b/lld/test/ELF/btf-merge-be.s
@@ -0,0 +1,63 @@
+# REQUIRES: ppc
+## Test --btf-merge with a big-endian target.
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=powerpc64-unknown-linux a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=powerpc64-unknown-linux b.s -o b.o
+# RUN: ld.lld --btf-merge a.o b.o -o merged
+# RUN: llvm-readelf -x .BTF merged | FileCheck %s
+
+## Big-endian BTF magic is 0xeb9f stored as eb9f (not byte-swapped).
+# CHECK: Hex dump of section '.BTF':
+# CHECK: 0x{{[0-9a-f]+}} eb9f0100
+
+#--- a.s
+.text
+.globl _start
+_start:
+ blr
+
+.section .BTF,"", at progbits
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 5 # str_len
+## Type 1: INT "int" size=4
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 4 # size
+.long 0x00000020 # encoding: bits=32
+## String table: "\0int\0"
+.byte 0
+.ascii "int"
+.byte 0
+
+#--- b.s
+.text
+.globl bar
+.type bar, @function
+bar:
+ blr
+
+.section .BTF,"", at progbits
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 6 # str_len
+## Type 1: INT "long" size=8
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 8 # size
+.long 0x00000040 # encoding: bits=64
+## String table: "\0long\0"
+.byte 0
+.ascii "long"
+.byte 0
diff --git a/lld/test/ELF/btf-merge.s b/lld/test/ELF/btf-merge.s
new file mode 100644
index 0000000000000..8a7afb234a17d
--- /dev/null
+++ b/lld/test/ELF/btf-merge.s
@@ -0,0 +1,197 @@
+# REQUIRES: x86
+## Test --btf-merge: merging and deduplication of .BTF sections.
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 b.s -o b.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 empty.s -o empty.o
+
+## Without --btf-merge, input .BTF sections pass through as-is.
+# RUN: ld.lld a.o b.o -o no-merge
+# RUN: llvm-readelf -S no-merge | FileCheck %s --check-prefix=NO-MERGE
+
+## --btf-merge produces a single merged .BTF section.
+# RUN: ld.lld --btf-merge a.o b.o -o merged
+# RUN: llvm-readelf -S merged | FileCheck %s --check-prefix=MERGED
+# RUN: llvm-readelf -x .BTF merged | FileCheck %s --check-prefix=BTF-HEX
+
+## --no-btf-merge disables merging.
+# RUN: ld.lld --btf-merge --no-btf-merge a.o b.o -o disabled
+# RUN: llvm-readelf -S disabled | FileCheck %s --check-prefix=NO-MERGE
+
+## No .BTF input: no .BTF output.
+# RUN: ld.lld --btf-merge empty.o -o no-btf
+# RUN: llvm-readelf -S no-btf | FileCheck %s --check-prefix=NO-BTF
+
+## Single file: passthrough.
+# RUN: ld.lld --btf-merge a.o -o single
+# RUN: llvm-readelf -x .BTF single | FileCheck %s --check-prefix=BTF-HEX
+
+## Dedup: both files have INT "int"; after merge only one should remain.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 dup.s -o dup.o
+# RUN: ld.lld --btf-merge a.o dup.o -o dedup
+# RUN: llvm-readelf -x .BTF dedup | FileCheck %s --check-prefix=BTF-HEX
+
+## -r should not merge .BTF sections even with --btf-merge.
+## The .BTF content is the raw concatenation of both inputs (size 0x5b = 91),
+## not a parsed/deduped blob.
+# RUN: ld.lld -r --btf-merge a.o b.o -o reloc.o
+# RUN: llvm-readelf -S reloc.o | FileCheck %s --check-prefix=RELOC
+
+## --gc-sections: verify --btf-merge works together with --gc-sections.
+# RUN: ld.lld --btf-merge --gc-sections a.o b.o -o gc
+# RUN: llvm-readelf -x .BTF gc | FileCheck %s --check-prefix=BTF-HEX
+
+## isLive: a .BTF section in a discarded COMDAT group should be skipped.
+## Both comdat files define the same group "grp". The second is discarded,
+## so only the first file's .BTF (INT "int") is merged with a.o's.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 comdat1.s -o comdat1.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 comdat2.s -o comdat2.o
+# RUN: ld.lld --btf-merge a.o comdat1.o comdat2.o -o comdat
+# RUN: llvm-readelf -x .BTF comdat | FileCheck %s --check-prefix=BTF-HEX
+
+# NO-MERGE: .BTF
+# MERGED: .BTF PROGBITS
+# MERGED-NOT: .BTF PROGBITS
+# NO-BTF-NOT: .BTF
+# BTF-HEX: Hex dump of section '.BTF':
+# BTF-HEX: 0x{{[0-9a-f]+}} 9feb0100
+
+## -r: raw concatenation of both inputs (45 + 46 = 91 = 0x5b bytes).
+# RELOC: .BTF PROGBITS {{.*}} 00005b
+
+#--- a.s
+.text
+.globl _start
+_start:
+ ret
+
+.section .BTF,"", at progbits
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 5 # str_len
+## Type 1: INT "int" size=4
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 4 # size
+.long 0x00000020 # encoding: bits=32
+## String table: "\0int\0"
+.byte 0
+.ascii "int"
+.byte 0
+
+#--- b.s
+.text
+.globl bar
+.type bar, @function
+bar:
+ ret
+
+.section .BTF,"", at progbits
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 6 # str_len
+## Type 1: INT "long" size=8
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 8 # size
+.long 0x00000040 # encoding: bits=64
+## String table: "\0long\0"
+.byte 0
+.ascii "long"
+.byte 0
+
+#--- empty.s
+.text
+.globl _start
+_start:
+ ret
+
+#--- dup.s
+.text
+.globl dup_fn
+.type dup_fn, @function
+dup_fn:
+ ret
+
+.section .BTF,"", at progbits
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 5 # str_len
+## Type 1: INT "int" size=4 (identical to a.s)
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 4 # size
+.long 0x00000020 # encoding: bits=32
+## String table: "\0int\0"
+.byte 0
+.ascii "int"
+.byte 0
+
+#--- comdat1.s
+## COMDAT group "grp" with a .BTF section containing INT "int".
+.section .text.foo,"axG", at progbits,grp,comdat
+.globl foo
+foo:
+ ret
+
+.section .BTF,"G", at progbits,grp,comdat
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 5 # str_len
+## Type 1: INT "int" size=4
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 4 # size
+.long 0x00000020 # encoding: bits=32
+## String table: "\0int\0"
+.byte 0
+.ascii "int"
+.byte 0
+
+#--- comdat2.s
+## Same COMDAT group "grp"; this copy will be discarded during dedup.
+## Its .BTF section (INT "long") should NOT be merged.
+.section .text.foo,"axG", at progbits,grp,comdat
+.globl foo
+foo:
+ ret
+
+.section .BTF,"G", at progbits,grp,comdat
+.short 0xeb9f # magic
+.byte 1 # version
+.byte 0 # flags
+.long 24 # hdr_len
+.long 0 # type_off
+.long 16 # type_len
+.long 16 # str_off
+.long 6 # str_len
+## Type 1: INT "long" size=8 (should be discarded)
+.long 1 # name_off
+.long 0x01000000 # info: kind=INT(1), vlen=0
+.long 8 # size
+.long 0x00000040 # encoding: bits=64
+## String table: "\0long\0"
+.byte 0
+.ascii "long"
+.byte 0
diff --git a/llvm/include/llvm/DebugInfo/BTF/BTFBuilder.h b/llvm/include/llvm/DebugInfo/BTF/BTFBuilder.h
new file mode 100644
index 0000000000000..a401788ec88f0
--- /dev/null
+++ b/llvm/include/llvm/DebugInfo/BTF/BTFBuilder.h
@@ -0,0 +1,96 @@
+//===- BTFBuilder.h - BTF builder/writer -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Mutable in-memory representation of BTF type information.
+///
+/// BTFBuilder provides an interface for constructing and merging .BTF
+/// sections. Types and strings can be added individually or merged from
+/// raw .BTF section data parsed from ELF object files. The result can
+/// be serialized back to binary .BTF format.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_DEBUGINFO_BTF_BTFBUILDER_H
+#define LLVM_DEBUGINFO_BTF_BTFBUILDER_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/DebugInfo/BTF/BTF.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm {
+
+/// A mutable container for BTF type information that supports construction,
+/// merging, and serialization.
+///
+/// Types are stored as contiguous raw bytes in native byte order.
+/// Type IDs are 1-based (ID 0 = void, never stored).
+class BTFBuilder {
+ // String table: concatenated NUL-terminated strings.
+ // Offset 0 is always the empty string (single NUL byte).
+ SmallVector<char, 0> Strings;
+
+ // Raw type data in native byte order. Types are stored sequentially,
+ // each as CommonType header followed by kind-specific tail data.
+ SmallVector<uint8_t, 0> TypeData;
+
+ // TypeOffsets[i] is the byte offset in TypeData for type ID (i+1).
+ SmallVector<uint32_t, 0> TypeOffsets;
+
+public:
+ LLVM_ABI BTFBuilder();
+
+ /// Add a string, returning its offset in the string table.
+ LLVM_ABI uint32_t addString(StringRef S);
+
+ /// Add a type header, returning the new 1-based type ID.
+ /// Append kind-specific tail data with addTail() immediately after.
+ LLVM_ABI uint32_t addType(const BTF::CommonType &Header);
+
+ /// Append kind-specific tail data for the most recently added type.
+ template <typename T> void addTail(const T &Data) {
+ const auto *Ptr = reinterpret_cast<const uint8_t *>(&Data);
+ TypeData.append(Ptr, Ptr + sizeof(Data));
+ }
+
+ /// Merge all types and strings from a raw .BTF section, remapping
+ /// type IDs and string offsets. Returns the first new type ID.
+ LLVM_ABI Expected<uint32_t> merge(StringRef RawBTFSection,
+ bool IsLittleEndian);
+
+ /// Look up a type by 1-based ID. Returns nullptr for invalid IDs.
+ LLVM_ABI const BTF::CommonType *findType(uint32_t Id) const;
+
+ /// Get raw bytes for a type entry (CommonType + tail data).
+ LLVM_ABI ArrayRef<uint8_t> getTypeBytes(uint32_t Id) const;
+
+ /// Get mutable raw bytes for a type entry.
+ LLVM_ABI MutableArrayRef<uint8_t> getMutableTypeBytes(uint32_t Id);
+
+ /// Look up a string by offset in the string table.
+ LLVM_ABI StringRef findString(uint32_t Offset) const;
+
+ /// Number of types, excluding void (type 0).
+ uint32_t typesCount() const { return TypeOffsets.size(); }
+
+ /// Compute the byte size of a type entry from its CommonType header.
+ LLVM_ABI static size_t typeByteSize(const BTF::CommonType *T);
+
+ /// Returns true if CommonType.Type is a type reference for this kind.
+ LLVM_ABI static bool hasTypeRef(uint32_t Kind);
+
+ /// Serialize to binary .BTF format, appending to Out.
+ LLVM_ABI void write(SmallVectorImpl<uint8_t> &Out,
+ bool IsLittleEndian) const;
+};
+
+} // namespace llvm
+
+#endif // LLVM_DEBUGINFO_BTF_BTFBUILDER_H
diff --git a/llvm/include/llvm/DebugInfo/BTF/BTFDedup.h b/llvm/include/llvm/DebugInfo/BTF/BTFDedup.h
new file mode 100644
index 0000000000000..0422e516ca0ba
--- /dev/null
+++ b/llvm/include/llvm/DebugInfo/BTF/BTFDedup.h
@@ -0,0 +1,49 @@
+//===- BTFDedup.h - BTF type deduplication ---------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// BTF type deduplication algorithm.
+///
+/// Implements the 5-pass deduplication algorithm from libbpf:
+/// 1. String deduplication
+/// 2. Primitive/struct/enum type dedup with DFS equivalence checking
+/// 3. Reference type dedup (PTR, TYPEDEF, etc.)
+/// 4. Type compaction (remove duplicates, assign new IDs)
+/// 5. Type ID remapping
+///
+/// The algorithm achieves ~137x type reduction on a full Linux kernel
+/// (3.6M types -> 26K types).
+///
+/// Reference: https://nakryiko.com/posts/btf-dedup/
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_DEBUGINFO_BTF_BTFDEDUP_H
+#define LLVM_DEBUGINFO_BTF_BTFDEDUP_H
+
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm {
+
+class BTFBuilder;
+
+namespace BTF {
+
+/// Deduplicate types in a BTFBuilder in-place.
+///
+/// After deduplication, structurally equivalent types are merged and
+/// all type IDs are remapped to point to canonical representatives.
+/// The resulting BTFBuilder can be serialized to produce a compact
+/// .BTF section.
+LLVM_ABI Error dedup(BTFBuilder &Builder);
+
+} // namespace BTF
+} // namespace llvm
+
+#endif // LLVM_DEBUGINFO_BTF_BTFDEDUP_H
diff --git a/llvm/lib/DebugInfo/BTF/BTFBuilder.cpp b/llvm/lib/DebugInfo/BTF/BTFBuilder.cpp
new file mode 100644
index 0000000000000..d9875133b4dfb
--- /dev/null
+++ b/llvm/lib/DebugInfo/BTF/BTFBuilder.cpp
@@ -0,0 +1,416 @@
+//===- BTFBuilder.cpp - BTF builder/writer implementation -----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFBuilder.h"
+#include "llvm/Support/Endian.h"
+#include "llvm/Support/SwapByteOrder.h"
+
+using namespace llvm;
+
+BTFBuilder::BTFBuilder() {
+ // String table starts with the empty string at offset 0.
+ Strings.push_back('\0');
+}
+
+uint32_t BTFBuilder::addString(StringRef S) {
+ uint32_t Offset = Strings.size();
+ Strings.append(S.begin(), S.end());
+ Strings.push_back('\0');
+ return Offset;
+}
+
+uint32_t BTFBuilder::addType(const BTF::CommonType &Header) {
+ TypeOffsets.push_back(TypeData.size());
+ const auto *Ptr = reinterpret_cast<const uint8_t *>(&Header);
+ TypeData.append(Ptr, Ptr + sizeof(Header));
+ return TypeOffsets.size(); // 1-based ID
+}
+
+const BTF::CommonType *BTFBuilder::findType(uint32_t Id) const {
+ if (Id == 0 || Id > TypeOffsets.size())
+ return nullptr;
+ return reinterpret_cast<const BTF::CommonType *>(
+ &TypeData[TypeOffsets[Id - 1]]);
+}
+
+// Returns {Start, Size} for a type's byte range, or {0, 0} for invalid IDs.
+static std::pair<uint32_t, uint32_t>
+typeBounds(uint32_t Id, const SmallVectorImpl<uint32_t> &TypeOffsets,
+ size_t TypeDataSize) {
+ if (Id == 0 || Id > TypeOffsets.size())
+ return {0, 0};
+ uint32_t Start = TypeOffsets[Id - 1];
+ uint32_t End =
+ (Id < TypeOffsets.size()) ? TypeOffsets[Id] : TypeDataSize;
+ return {Start, End - Start};
+}
+
+ArrayRef<uint8_t> BTFBuilder::getTypeBytes(uint32_t Id) const {
+ auto [Start, Size] = typeBounds(Id, TypeOffsets, TypeData.size());
+ if (Size == 0)
+ return {};
+ return ArrayRef<uint8_t>(&TypeData[Start], Size);
+}
+
+MutableArrayRef<uint8_t> BTFBuilder::getMutableTypeBytes(uint32_t Id) {
+ auto [Start, Size] = typeBounds(Id, TypeOffsets, TypeData.size());
+ if (Size == 0)
+ return {};
+ return MutableArrayRef<uint8_t>(&TypeData[Start], Size);
+}
+
+StringRef BTFBuilder::findString(uint32_t Offset) const {
+ if (Offset >= Strings.size())
+ return StringRef();
+ return StringRef(&Strings[Offset]);
+}
+
+size_t BTFBuilder::typeByteSize(const BTF::CommonType *T) {
+ size_t Size = sizeof(BTF::CommonType);
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_INT:
+ case BTF::BTF_KIND_VAR:
+ case BTF::BTF_KIND_DECL_TAG:
+ Size += sizeof(uint32_t);
+ break;
+ case BTF::BTF_KIND_ARRAY:
+ Size += sizeof(BTF::BTFArray);
+ break;
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION:
+ Size += sizeof(BTF::BTFMember) * T->getVlen();
+ break;
+ case BTF::BTF_KIND_ENUM:
+ Size += sizeof(BTF::BTFEnum) * T->getVlen();
+ break;
+ case BTF::BTF_KIND_ENUM64:
+ Size += sizeof(BTF::BTFEnum64) * T->getVlen();
+ break;
+ case BTF::BTF_KIND_FUNC_PROTO:
+ Size += sizeof(BTF::BTFParam) * T->getVlen();
+ break;
+ case BTF::BTF_KIND_DATASEC:
+ Size += sizeof(BTF::BTFDataSec) * T->getVlen();
+ break;
+ default:
+ break;
+ }
+ return Size;
+}
+
+bool BTFBuilder::hasTypeRef(uint32_t Kind) {
+ switch (Kind) {
+ case BTF::BTF_KIND_PTR:
+ case BTF::BTF_KIND_TYPEDEF:
+ case BTF::BTF_KIND_VOLATILE:
+ case BTF::BTF_KIND_CONST:
+ case BTF::BTF_KIND_RESTRICT:
+ case BTF::BTF_KIND_FUNC:
+ case BTF::BTF_KIND_FUNC_PROTO:
+ case BTF::BTF_KIND_VAR:
+ case BTF::BTF_KIND_DECL_TAG:
+ case BTF::BTF_KIND_TYPE_TAG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Byte-swap CommonType header fields in place.
+static void swapCommonType(BTF::CommonType *T) {
+ using llvm::sys::swapByteOrder;
+ swapByteOrder(T->NameOff);
+ swapByteOrder(T->Info);
+ swapByteOrder(T->Size); // Size and Type are a union, same bytes.
+}
+
+// Byte-swap kind-specific tail data in place.
+// CommonType must already be in native byte order.
+static void swapTailData(uint8_t *TailPtr, const BTF::CommonType *T) {
+ using llvm::sys::swapByteOrder;
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_INT:
+ case BTF::BTF_KIND_VAR:
+ case BTF::BTF_KIND_DECL_TAG: {
+ auto *V = reinterpret_cast<uint32_t *>(TailPtr);
+ swapByteOrder(*V);
+ break;
+ }
+ case BTF::BTF_KIND_ARRAY: {
+ auto *A = reinterpret_cast<BTF::BTFArray *>(TailPtr);
+ swapByteOrder(A->ElemType);
+ swapByteOrder(A->IndexType);
+ swapByteOrder(A->Nelems);
+ break;
+ }
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION: {
+ auto *M = reinterpret_cast<BTF::BTFMember *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I) {
+ swapByteOrder(M[I].NameOff);
+ swapByteOrder(M[I].Type);
+ swapByteOrder(M[I].Offset);
+ }
+ break;
+ }
+ case BTF::BTF_KIND_ENUM: {
+ auto *E = reinterpret_cast<BTF::BTFEnum *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I) {
+ swapByteOrder(E[I].NameOff);
+ swapByteOrder(E[I].Val);
+ }
+ break;
+ }
+ case BTF::BTF_KIND_ENUM64: {
+ auto *E = reinterpret_cast<BTF::BTFEnum64 *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I) {
+ swapByteOrder(E[I].NameOff);
+ swapByteOrder(E[I].Val_Lo32);
+ swapByteOrder(E[I].Val_Hi32);
+ }
+ break;
+ }
+ case BTF::BTF_KIND_FUNC_PROTO: {
+ auto *P = reinterpret_cast<BTF::BTFParam *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I) {
+ swapByteOrder(P[I].NameOff);
+ swapByteOrder(P[I].Type);
+ }
+ break;
+ }
+ case BTF::BTF_KIND_DATASEC: {
+ auto *D = reinterpret_cast<BTF::BTFDataSec *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I) {
+ swapByteOrder(D[I].Type);
+ swapByteOrder(D[I].Offset);
+ swapByteOrder(D[I].Size);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+// Remap type IDs in a type entry. Non-zero IDs are adjusted by IdDelta.
+static void remapTypeIds(uint8_t *Data, uint32_t IdDelta) {
+ if (IdDelta == 0)
+ return;
+
+ auto *T = reinterpret_cast<BTF::CommonType *>(Data);
+ uint8_t *TailPtr = Data + sizeof(BTF::CommonType);
+
+ if (BTFBuilder::hasTypeRef(T->getKind()) && T->Type != 0)
+ T->Type += IdDelta;
+
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_ARRAY: {
+ auto *A = reinterpret_cast<BTF::BTFArray *>(TailPtr);
+ if (A->ElemType != 0)
+ A->ElemType += IdDelta;
+ if (A->IndexType != 0)
+ A->IndexType += IdDelta;
+ break;
+ }
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION: {
+ auto *M = reinterpret_cast<BTF::BTFMember *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ if (M[I].Type != 0)
+ M[I].Type += IdDelta;
+ break;
+ }
+ case BTF::BTF_KIND_FUNC_PROTO: {
+ auto *P = reinterpret_cast<BTF::BTFParam *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ if (P[I].Type != 0)
+ P[I].Type += IdDelta;
+ break;
+ }
+ case BTF::BTF_KIND_DATASEC: {
+ auto *D = reinterpret_cast<BTF::BTFDataSec *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ if (D[I].Type != 0)
+ D[I].Type += IdDelta;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+// Remap string offsets in a type entry.
+static void remapStringOffsets(uint8_t *Data, uint32_t StrDelta) {
+ if (StrDelta == 0)
+ return;
+
+ auto *T = reinterpret_cast<BTF::CommonType *>(Data);
+ T->NameOff += StrDelta;
+
+ uint8_t *TailPtr = Data + sizeof(BTF::CommonType);
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION: {
+ auto *M = reinterpret_cast<BTF::BTFMember *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ M[I].NameOff += StrDelta;
+ break;
+ }
+ case BTF::BTF_KIND_ENUM: {
+ auto *E = reinterpret_cast<BTF::BTFEnum *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ E[I].NameOff += StrDelta;
+ break;
+ }
+ case BTF::BTF_KIND_ENUM64: {
+ auto *E = reinterpret_cast<BTF::BTFEnum64 *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ E[I].NameOff += StrDelta;
+ break;
+ }
+ case BTF::BTF_KIND_FUNC_PROTO: {
+ auto *P = reinterpret_cast<BTF::BTFParam *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ P[I].NameOff += StrDelta;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+Expected<uint32_t> BTFBuilder::merge(StringRef RawBTFSection,
+ bool IsLittleEndian) {
+ bool NeedSwap = (IsLittleEndian != sys::IsLittleEndianHost);
+
+ if (RawBTFSection.size() < sizeof(BTF::Header))
+ return createStringError("BTF section too small for header");
+
+ BTF::Header Hdr;
+ memcpy(&Hdr, RawBTFSection.data(), sizeof(Hdr));
+ if (NeedSwap) {
+ sys::swapByteOrder(Hdr.Magic);
+ sys::swapByteOrder(Hdr.HdrLen);
+ sys::swapByteOrder(Hdr.TypeOff);
+ sys::swapByteOrder(Hdr.TypeLen);
+ sys::swapByteOrder(Hdr.StrOff);
+ sys::swapByteOrder(Hdr.StrLen);
+ }
+
+ if (Hdr.Magic != BTF::MAGIC)
+ return createStringError("invalid BTF magic: " +
+ Twine::utohexstr(Hdr.Magic));
+ if (Hdr.Version != BTF::VERSION)
+ return createStringError("unsupported BTF version: " +
+ Twine(Hdr.Version));
+
+ uint64_t DataStart = Hdr.HdrLen;
+ if (DataStart + Hdr.StrOff + Hdr.StrLen > RawBTFSection.size())
+ return createStringError("BTF string section exceeds section bounds");
+ if (DataStart + Hdr.TypeOff + Hdr.TypeLen > RawBTFSection.size())
+ return createStringError("BTF type section exceeds section bounds");
+
+ StringRef InputStrings =
+ RawBTFSection.substr(DataStart + Hdr.StrOff, Hdr.StrLen);
+ StringRef InputTypes =
+ RawBTFSection.substr(DataStart + Hdr.TypeOff, Hdr.TypeLen);
+
+ uint32_t StrDelta = Strings.size();
+ uint32_t IdDelta = TypeOffsets.size();
+ uint32_t FirstNewId = IdDelta + 1;
+
+ Strings.append(InputStrings.begin(), InputStrings.end());
+
+ uint32_t TypeDataBase = TypeData.size();
+ TypeData.append(reinterpret_cast<const uint8_t *>(InputTypes.data()),
+ reinterpret_cast<const uint8_t *>(InputTypes.data()) +
+ InputTypes.size());
+
+ uint64_t Offset = 0;
+ while (Offset + sizeof(BTF::CommonType) <= InputTypes.size()) {
+ uint32_t AbsOffset = TypeDataBase + Offset;
+ auto *CT = reinterpret_cast<BTF::CommonType *>(&TypeData[AbsOffset]);
+
+ if (NeedSwap)
+ swapCommonType(CT);
+
+ TypeOffsets.push_back(AbsOffset);
+ size_t FullSize = typeByteSize(CT);
+
+ if (Offset + FullSize > InputTypes.size()) {
+ TypeData.resize(TypeDataBase);
+ TypeOffsets.resize(IdDelta);
+ Strings.resize(StrDelta);
+ return createStringError("incomplete type in BTF type section");
+ }
+
+ if (NeedSwap)
+ swapTailData(&TypeData[AbsOffset + sizeof(BTF::CommonType)], CT);
+
+ remapStringOffsets(&TypeData[AbsOffset], StrDelta);
+ remapTypeIds(&TypeData[AbsOffset], IdDelta);
+
+ Offset += FullSize;
+ }
+
+ if (Offset != InputTypes.size()) {
+ TypeData.resize(TypeDataBase);
+ TypeOffsets.resize(IdDelta);
+ Strings.resize(StrDelta);
+ return createStringError("trailing bytes in BTF type section");
+ }
+
+ return FirstNewId;
+}
+
+void BTFBuilder::write(SmallVectorImpl<uint8_t> &Out,
+ bool IsLittleEndian) const {
+ bool NeedSwap = (IsLittleEndian != sys::IsLittleEndianHost);
+
+ BTF::Header Hdr;
+ Hdr.Magic = BTF::MAGIC;
+ Hdr.Version = BTF::VERSION;
+ Hdr.Flags = 0;
+ Hdr.HdrLen = sizeof(BTF::Header);
+ Hdr.TypeOff = 0;
+ Hdr.TypeLen = TypeData.size();
+ Hdr.StrOff = TypeData.size();
+ Hdr.StrLen = Strings.size();
+
+ size_t TotalSize = sizeof(Hdr) + TypeData.size() + Strings.size();
+ size_t OutStart = Out.size();
+ Out.resize(OutStart + TotalSize);
+ uint8_t *Buf = &Out[OutStart];
+
+ BTF::Header OutHdr = Hdr;
+ if (NeedSwap) {
+ sys::swapByteOrder(OutHdr.Magic);
+ sys::swapByteOrder(OutHdr.HdrLen);
+ sys::swapByteOrder(OutHdr.TypeOff);
+ sys::swapByteOrder(OutHdr.TypeLen);
+ sys::swapByteOrder(OutHdr.StrOff);
+ sys::swapByteOrder(OutHdr.StrLen);
+ }
+ memcpy(Buf, &OutHdr, sizeof(OutHdr));
+ Buf += sizeof(OutHdr);
+
+ memcpy(Buf, TypeData.data(), TypeData.size());
+ if (NeedSwap) {
+ uint64_t Offset = 0;
+ while (Offset + sizeof(BTF::CommonType) <= TypeData.size()) {
+ auto *CT = reinterpret_cast<BTF::CommonType *>(Buf + Offset);
+ size_t FullSize = typeByteSize(CT);
+ swapTailData(Buf + Offset + sizeof(BTF::CommonType), CT);
+ swapCommonType(CT);
+ Offset += FullSize;
+ }
+ }
+ Buf += TypeData.size();
+
+ memcpy(Buf, Strings.data(), Strings.size());
+}
diff --git a/llvm/lib/DebugInfo/BTF/BTFDedup.cpp b/llvm/lib/DebugInfo/BTF/BTFDedup.cpp
new file mode 100644
index 0000000000000..e2d73243fc717
--- /dev/null
+++ b/llvm/lib/DebugInfo/BTF/BTFDedup.cpp
@@ -0,0 +1,777 @@
+//===- BTFDedup.cpp - BTF type deduplication implementation ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implements the BTF type deduplication algorithm, a port of the algorithm
+// from libbpf (btf_dedup.c, BSD-licensed).
+//
+// The algorithm runs in 5 passes:
+// 1. String deduplication
+// 2. Primitive and composite type dedup (INT, ENUM, STRUCT, UNION, FWD)
+// 3. Reference type dedup (PTR, TYPEDEF, VOLATILE, etc.)
+// 4. Type compaction (remove dups, assign sequential IDs)
+// 5. Type ID remapping (fix all references to use new IDs)
+//
+// For struct/union types, a DFS-based type graph equivalence check is used
+// with a "hypothetical map" to handle recursive/cyclic types.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFDedup.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/DebugInfo/BTF/BTF.h"
+#include "llvm/DebugInfo/BTF/BTFBuilder.h"
+
+#define DEBUG_TYPE "btf-dedup"
+
+using namespace llvm;
+
+namespace {
+
+// Sentinel value: type has not been processed yet.
+constexpr uint32_t BTF_UNPROCESSED = UINT32_MAX;
+
+/// State for the BTF deduplication algorithm.
+class BTFDedupState {
+ BTFBuilder &Builder;
+
+ // Equivalence map: Map[i] = canonical type ID for type i.
+ // Initially Map[i] = i (each type is its own canonical).
+ // After dedup, Map[i] = j means type i is equivalent to canonical type j.
+ std::vector<uint32_t> Map;
+
+ // Hypothetical map for recursive type comparison.
+ // HypotMap[i] = j means "we hypothesize type i is equivalent to type j".
+ // Reset between each top-level comparison.
+ std::vector<uint32_t> HypotMap;
+
+ // List of types currently in the hypothetical map (for fast reset).
+ SmallVector<uint32_t, 0> HypotList;
+
+ // String dedup: maps string content to canonical offset.
+ StringMap<uint32_t> StringDedup;
+
+ // New string offsets: NewStrOff[old_offset] = new_offset after dedup.
+ DenseMap<uint32_t, uint32_t> NewStrOff;
+
+ // Hash for each type, used for bucketing candidates.
+ std::vector<uint64_t> TypeHash;
+
+ // Buckets: hash -> list of canonical type IDs with that hash.
+ DenseMap<uint64_t, SmallVector<uint32_t, 4>> HashBuckets;
+
+ // After compaction: OldToNew[old_id] = new_id.
+ std::vector<uint32_t> OldToNew;
+
+ static bool isPrimitiveKind(uint32_t Kind) {
+ switch (Kind) {
+ case BTF::BTF_KIND_INT:
+ case BTF::BTF_KIND_FLOAT:
+ case BTF::BTF_KIND_ENUM:
+ case BTF::BTF_KIND_ENUM64:
+ case BTF::BTF_KIND_FWD:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // Returns true if this is a composite kind that needs DFS comparison.
+ static bool isCompositeKind(uint32_t Kind) {
+ return Kind == BTF::BTF_KIND_STRUCT || Kind == BTF::BTF_KIND_UNION;
+ }
+
+ // Get the canonical representative for a type ID.
+ uint32_t resolve(uint32_t Id) const {
+ while (Id < Map.size() && Map[Id] != Id)
+ Id = Map[Id];
+ return Id;
+ }
+
+ // Hash a type for bucketing. Only considers local structure, not
+ // referenced type IDs (those are checked in equivalence comparison).
+ uint64_t hashType(uint32_t Id);
+
+ // Hash helpers for specific kinds.
+ uint64_t hashCommon(const BTF::CommonType *T);
+ uint64_t hashStruct(const BTF::CommonType *T);
+ uint64_t hashEnum(const BTF::CommonType *T);
+ uint64_t hashEnum64(const BTF::CommonType *T);
+ uint64_t hashFuncProto(const BTF::CommonType *T);
+ uint64_t hashArray(const BTF::CommonType *T);
+
+ // Check if two types are structurally equivalent.
+ // Uses the hypothetical map for cycle handling.
+ bool isEquiv(uint32_t CandId, uint32_t CanonId);
+
+ // Deep comparison helpers.
+ bool isEquivCommon(const BTF::CommonType *Cand, const BTF::CommonType *Canon);
+ bool isEquivStruct(uint32_t CandId, uint32_t CanonId);
+ bool isEquivEnum(const BTF::CommonType *Cand, const BTF::CommonType *Canon);
+ bool isEquivEnum64(const BTF::CommonType *Cand, const BTF::CommonType *Canon);
+ bool isEquivFuncProto(uint32_t CandId, uint32_t CanonId);
+ bool isEquivArray(uint32_t CandId, uint32_t CanonId);
+
+ // Reset the hypothetical map.
+ void clearHypot() {
+ for (uint32_t Id : HypotList)
+ HypotMap[Id] = BTF_UNPROCESSED;
+ HypotList.clear();
+ }
+
+ uint32_t dedupStrOff(uint32_t Offset) const {
+ auto It = NewStrOff.find(Offset);
+ return It != NewStrOff.end() ? It->second : Offset;
+ }
+
+ // The five passes.
+ Error dedupStrings();
+ Error dedupPrimitives();
+ Error dedupComposites();
+ Error dedupRefs();
+ Error compact();
+
+public:
+ BTFDedupState(BTFBuilder &B) : Builder(B) {}
+ Error run();
+};
+
+//===----------------------------------------------------------------------===//
+// Hashing
+//===----------------------------------------------------------------------===//
+
+uint64_t BTFDedupState::hashCommon(const BTF::CommonType *T) {
+ return hash_combine(T->getKind(), dedupStrOff(T->NameOff), T->Size);
+}
+
+uint64_t BTFDedupState::hashStruct(const BTF::CommonType *T) {
+ // Hash name + size + member names (NOT member types — those are checked
+ // during equivalence comparison).
+ uint64_t H = hash_combine(T->getKind(), dedupStrOff(T->NameOff), T->Size,
+ T->getVlen());
+ auto *Members = reinterpret_cast<const BTF::BTFMember *>(
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType));
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ H = hash_combine(H, dedupStrOff(Members[I].NameOff), Members[I].Offset);
+ return H;
+}
+
+uint64_t BTFDedupState::hashEnum(const BTF::CommonType *T) {
+ uint64_t H = hashCommon(T);
+ auto *Values = reinterpret_cast<const BTF::BTFEnum *>(
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType));
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ H = hash_combine(H, dedupStrOff(Values[I].NameOff), Values[I].Val);
+ return H;
+}
+
+uint64_t BTFDedupState::hashEnum64(const BTF::CommonType *T) {
+ uint64_t H = hashCommon(T);
+ auto *Values = reinterpret_cast<const BTF::BTFEnum64 *>(
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType));
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ H = hash_combine(H, dedupStrOff(Values[I].NameOff), Values[I].Val_Lo32,
+ Values[I].Val_Hi32);
+ return H;
+}
+
+uint64_t BTFDedupState::hashFuncProto(const BTF::CommonType *T) {
+ // Hash return type + param names (NOT param types).
+ uint64_t H = hash_combine(T->getKind(), T->getVlen());
+ auto *Params = reinterpret_cast<const BTF::BTFParam *>(
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType));
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ H = hash_combine(H, dedupStrOff(Params[I].NameOff));
+ return H;
+}
+
+uint64_t BTFDedupState::hashArray(const BTF::CommonType *T) {
+ auto *Arr = reinterpret_cast<const BTF::BTFArray *>(
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType));
+ return hash_combine(T->getKind(), Arr->Nelems);
+}
+
+uint64_t BTFDedupState::hashType(uint32_t Id) {
+ const BTF::CommonType *T = Builder.findType(Id);
+ if (!T)
+ return 0;
+
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_INT:
+ case BTF::BTF_KIND_FLOAT:
+ case BTF::BTF_KIND_FWD:
+ return hashCommon(T);
+ case BTF::BTF_KIND_ENUM:
+ return hashEnum(T);
+ case BTF::BTF_KIND_ENUM64:
+ return hashEnum64(T);
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION:
+ return hashStruct(T);
+ case BTF::BTF_KIND_FUNC_PROTO:
+ return hashFuncProto(T);
+ case BTF::BTF_KIND_ARRAY:
+ return hashArray(T);
+ default:
+ // Reference types: hash by kind only (real comparison uses resolved refs).
+ return hash_combine(T->getKind());
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// Equivalence checking
+//===----------------------------------------------------------------------===//
+
+bool BTFDedupState::isEquivCommon(const BTF::CommonType *Cand,
+ const BTF::CommonType *Canon) {
+ return Cand->getKind() == Canon->getKind() &&
+ dedupStrOff(Cand->NameOff) == dedupStrOff(Canon->NameOff) &&
+ Cand->Size == Canon->Size && Cand->getVlen() == Canon->getVlen();
+}
+
+bool BTFDedupState::isEquivEnum(const BTF::CommonType *Cand,
+ const BTF::CommonType *Canon) {
+ if (!isEquivCommon(Cand, Canon))
+ return false;
+
+ auto *CandVals = reinterpret_cast<const BTF::BTFEnum *>(
+ reinterpret_cast<const uint8_t *>(Cand) + sizeof(BTF::CommonType));
+ auto *CanonVals = reinterpret_cast<const BTF::BTFEnum *>(
+ reinterpret_cast<const uint8_t *>(Canon) + sizeof(BTF::CommonType));
+
+ for (unsigned I = 0, N = Cand->getVlen(); I < N; ++I) {
+ if (dedupStrOff(CandVals[I].NameOff) !=
+ dedupStrOff(CanonVals[I].NameOff) ||
+ CandVals[I].Val != CanonVals[I].Val)
+ return false;
+ }
+ return true;
+}
+
+bool BTFDedupState::isEquivEnum64(const BTF::CommonType *Cand,
+ const BTF::CommonType *Canon) {
+ if (!isEquivCommon(Cand, Canon))
+ return false;
+
+ auto *CandVals = reinterpret_cast<const BTF::BTFEnum64 *>(
+ reinterpret_cast<const uint8_t *>(Cand) + sizeof(BTF::CommonType));
+ auto *CanonVals = reinterpret_cast<const BTF::BTFEnum64 *>(
+ reinterpret_cast<const uint8_t *>(Canon) + sizeof(BTF::CommonType));
+
+ for (unsigned I = 0, N = Cand->getVlen(); I < N; ++I) {
+ if (dedupStrOff(CandVals[I].NameOff) !=
+ dedupStrOff(CanonVals[I].NameOff) ||
+ CandVals[I].Val_Lo32 != CanonVals[I].Val_Lo32 ||
+ CandVals[I].Val_Hi32 != CanonVals[I].Val_Hi32)
+ return false;
+ }
+ return true;
+}
+
+bool BTFDedupState::isEquivStruct(uint32_t CandId, uint32_t CanonId) {
+ const BTF::CommonType *Cand = Builder.findType(CandId);
+ const BTF::CommonType *Canon = Builder.findType(CanonId);
+ if (!Cand || !Canon || !isEquivCommon(Cand, Canon))
+ return false;
+
+ auto *CandMembers = reinterpret_cast<const BTF::BTFMember *>(
+ reinterpret_cast<const uint8_t *>(Cand) + sizeof(BTF::CommonType));
+ auto *CanonMembers = reinterpret_cast<const BTF::BTFMember *>(
+ reinterpret_cast<const uint8_t *>(Canon) + sizeof(BTF::CommonType));
+
+ for (unsigned I = 0, N = Cand->getVlen(); I < N; ++I) {
+ if (dedupStrOff(CandMembers[I].NameOff) !=
+ dedupStrOff(CanonMembers[I].NameOff) ||
+ CandMembers[I].Offset != CanonMembers[I].Offset)
+ return false;
+ if (!isEquiv(CandMembers[I].Type, CanonMembers[I].Type))
+ return false;
+ }
+ return true;
+}
+
+bool BTFDedupState::isEquivFuncProto(uint32_t CandId, uint32_t CanonId) {
+ const BTF::CommonType *Cand = Builder.findType(CandId);
+ const BTF::CommonType *Canon = Builder.findType(CanonId);
+ if (!Cand || !Canon)
+ return false;
+ if (Cand->getKind() != Canon->getKind() ||
+ Cand->getVlen() != Canon->getVlen())
+ return false;
+
+ if (!isEquiv(Cand->Type, Canon->Type))
+ return false;
+
+ auto *CandParams = reinterpret_cast<const BTF::BTFParam *>(
+ reinterpret_cast<const uint8_t *>(Cand) + sizeof(BTF::CommonType));
+ auto *CanonParams = reinterpret_cast<const BTF::BTFParam *>(
+ reinterpret_cast<const uint8_t *>(Canon) + sizeof(BTF::CommonType));
+
+ for (unsigned I = 0, N = Cand->getVlen(); I < N; ++I) {
+ if (dedupStrOff(CandParams[I].NameOff) !=
+ dedupStrOff(CanonParams[I].NameOff))
+ return false;
+ if (!isEquiv(CandParams[I].Type, CanonParams[I].Type))
+ return false;
+ }
+ return true;
+}
+
+bool BTFDedupState::isEquivArray(uint32_t CandId, uint32_t CanonId) {
+ const BTF::CommonType *Cand = Builder.findType(CandId);
+ const BTF::CommonType *Canon = Builder.findType(CanonId);
+ if (!Cand || !Canon || Cand->getKind() != Canon->getKind())
+ return false;
+
+ auto *CandArr = reinterpret_cast<const BTF::BTFArray *>(
+ reinterpret_cast<const uint8_t *>(Cand) + sizeof(BTF::CommonType));
+ auto *CanonArr = reinterpret_cast<const BTF::BTFArray *>(
+ reinterpret_cast<const uint8_t *>(Canon) + sizeof(BTF::CommonType));
+
+ if (CandArr->Nelems != CanonArr->Nelems)
+ return false;
+ return isEquiv(CandArr->ElemType, CanonArr->ElemType) &&
+ isEquiv(CandArr->IndexType, CanonArr->IndexType);
+}
+
+bool BTFDedupState::isEquiv(uint32_t CandId, uint32_t CanonId) {
+ CandId = resolve(CandId);
+ CanonId = resolve(CanonId);
+
+ if (CandId == 0 && CanonId == 0)
+ return true;
+ if (CandId == 0 || CanonId == 0)
+ return false;
+ if (CandId == CanonId)
+ return true;
+
+ // Cycle detection: check if we already have a hypothesis for CandId.
+ if (HypotMap[CandId] != BTF_UNPROCESSED)
+ return HypotMap[CandId] == CanonId;
+
+ HypotMap[CandId] = CanonId;
+ HypotList.push_back(CandId);
+
+ const BTF::CommonType *Cand = Builder.findType(CandId);
+ const BTF::CommonType *Canon = Builder.findType(CanonId);
+ if (!Cand || !Canon)
+ return false;
+
+ if (Cand->getKind() != Canon->getKind())
+ return false;
+
+ switch (Cand->getKind()) {
+ case BTF::BTF_KIND_INT:
+ case BTF::BTF_KIND_FLOAT:
+ case BTF::BTF_KIND_FWD:
+ return isEquivCommon(Cand, Canon);
+
+ case BTF::BTF_KIND_ENUM:
+ return isEquivEnum(Cand, Canon);
+
+ case BTF::BTF_KIND_ENUM64:
+ return isEquivEnum64(Cand, Canon);
+
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION:
+ return isEquivStruct(CandId, CanonId);
+
+ case BTF::BTF_KIND_FUNC_PROTO:
+ return isEquivFuncProto(CandId, CanonId);
+
+ case BTF::BTF_KIND_ARRAY:
+ return isEquivArray(CandId, CanonId);
+
+ case BTF::BTF_KIND_PTR:
+ case BTF::BTF_KIND_TYPEDEF:
+ case BTF::BTF_KIND_VOLATILE:
+ case BTF::BTF_KIND_CONST:
+ case BTF::BTF_KIND_RESTRICT:
+ case BTF::BTF_KIND_TYPE_TAG:
+ if (dedupStrOff(Cand->NameOff) != dedupStrOff(Canon->NameOff))
+ return false;
+ return isEquiv(Cand->Type, Canon->Type);
+
+ case BTF::BTF_KIND_FUNC:
+ if (dedupStrOff(Cand->NameOff) != dedupStrOff(Canon->NameOff))
+ return false;
+ return isEquiv(Cand->Type, Canon->Type);
+
+ case BTF::BTF_KIND_VAR:
+ if (dedupStrOff(Cand->NameOff) != dedupStrOff(Canon->NameOff))
+ return false;
+ if (Cand->Type != 0 && Canon->Type != 0)
+ return isEquiv(Cand->Type, Canon->Type);
+ return Cand->Type == Canon->Type;
+
+ case BTF::BTF_KIND_DATASEC:
+ return isEquivCommon(Cand, Canon);
+
+ case BTF::BTF_KIND_DECL_TAG:
+ if (dedupStrOff(Cand->NameOff) != dedupStrOff(Canon->NameOff))
+ return false;
+ {
+ auto CandBytes = Builder.getTypeBytes(CandId);
+ auto CanonBytes = Builder.getTypeBytes(CanonId);
+ if (CandBytes.size() < sizeof(BTF::CommonType) + 4 ||
+ CanonBytes.size() < sizeof(BTF::CommonType) + 4)
+ return false;
+ uint32_t CandIdx, CanonIdx;
+ memcpy(&CandIdx, CandBytes.data() + sizeof(BTF::CommonType), 4);
+ memcpy(&CanonIdx, CanonBytes.data() + sizeof(BTF::CommonType), 4);
+ if (CandIdx != CanonIdx)
+ return false;
+ }
+ return isEquiv(Cand->Type, Canon->Type);
+
+ default:
+ return false;
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// Pass 1: String deduplication
+//===----------------------------------------------------------------------===//
+
+Error BTFDedupState::dedupStrings() {
+ StringDedup[""] = 0;
+
+ for (uint32_t Id = 1; Id <= Builder.typesCount(); ++Id) {
+ const BTF::CommonType *T = Builder.findType(Id);
+ if (!T)
+ continue;
+
+ auto DedupStr = [&](uint32_t Off) {
+ if (NewStrOff.count(Off))
+ return;
+ StringRef S = Builder.findString(Off);
+ auto It = StringDedup.find(S);
+ if (It != StringDedup.end()) {
+ NewStrOff[Off] = It->second;
+ } else {
+ StringDedup[S] = Off;
+ NewStrOff[Off] = Off;
+ }
+ };
+
+ DedupStr(T->NameOff);
+
+ const uint8_t *TailPtr =
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType);
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION: {
+ auto *M = reinterpret_cast<const BTF::BTFMember *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ DedupStr(M[I].NameOff);
+ break;
+ }
+ case BTF::BTF_KIND_ENUM: {
+ auto *E = reinterpret_cast<const BTF::BTFEnum *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ DedupStr(E[I].NameOff);
+ break;
+ }
+ case BTF::BTF_KIND_ENUM64: {
+ auto *E = reinterpret_cast<const BTF::BTFEnum64 *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ DedupStr(E[I].NameOff);
+ break;
+ }
+ case BTF::BTF_KIND_FUNC_PROTO: {
+ auto *P = reinterpret_cast<const BTF::BTFParam *>(TailPtr);
+ for (unsigned I = 0, N = T->getVlen(); I < N; ++I)
+ DedupStr(P[I].NameOff);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return Error::success();
+}
+
+//===----------------------------------------------------------------------===//
+// Pass 2: Primitive and composite type dedup
+//===----------------------------------------------------------------------===//
+
+Error BTFDedupState::dedupPrimitives() {
+ uint32_t N = Builder.typesCount();
+ for (uint32_t Id = 1; Id <= N; ++Id) {
+ const BTF::CommonType *T = Builder.findType(Id);
+ if (!T || !isPrimitiveKind(T->getKind()))
+ continue;
+
+ uint64_t H = TypeHash[Id];
+ auto &Bucket = HashBuckets[H];
+
+ bool Found = false;
+ for (uint32_t CanonId : Bucket) {
+ clearHypot();
+ if (isEquiv(Id, CanonId)) {
+ Map[Id] = CanonId;
+ Found = true;
+ break;
+ }
+ }
+ if (!Found)
+ Bucket.push_back(Id);
+ }
+
+ return Error::success();
+}
+
+Error BTFDedupState::dedupComposites() {
+ uint32_t N = Builder.typesCount();
+ for (uint32_t Id = 1; Id <= N; ++Id) {
+ const BTF::CommonType *T = Builder.findType(Id);
+ if (!T || !isCompositeKind(T->getKind()))
+ continue;
+
+ uint64_t H = TypeHash[Id];
+ auto &Bucket = HashBuckets[H];
+
+ bool Found = false;
+ for (uint32_t CanonId : Bucket) {
+ clearHypot();
+ if (isEquiv(Id, CanonId)) {
+ // Commit hypothetical mappings.
+ for (uint32_t HId : HypotList)
+ Map[HId] = HypotMap[HId];
+ Found = true;
+ break;
+ }
+ }
+
+ clearHypot();
+ if (!Found)
+ Bucket.push_back(Id);
+ }
+
+ return Error::success();
+}
+
+//===----------------------------------------------------------------------===//
+// Pass 3: Reference type dedup
+//===----------------------------------------------------------------------===//
+
+Error BTFDedupState::dedupRefs() {
+ uint32_t N = Builder.typesCount();
+
+ for (uint32_t Id = 1; Id <= N; ++Id) {
+ if (Map[Id] != Id)
+ continue;
+
+ const BTF::CommonType *T = Builder.findType(Id);
+ if (!T)
+ continue;
+
+ uint32_t Kind = T->getKind();
+ if (isPrimitiveKind(Kind) || isCompositeKind(Kind))
+ continue;
+
+ uint64_t H = hashType(Id);
+ auto &Bucket = HashBuckets[H];
+
+ bool Found = false;
+ for (uint32_t CanonId : Bucket) {
+ clearHypot();
+ if (isEquiv(Id, CanonId)) {
+ Map[Id] = CanonId;
+ Found = true;
+ break;
+ }
+ }
+
+ clearHypot();
+ if (!Found)
+ Bucket.push_back(Id);
+ }
+
+ return Error::success();
+}
+
+//===----------------------------------------------------------------------===//
+// Pass 4-5: Compaction and remapping
+//===----------------------------------------------------------------------===//
+
+Error BTFDedupState::compact() {
+ uint32_t N = Builder.typesCount();
+ BTFBuilder NewBuilder;
+ DenseMap<uint32_t, uint32_t> StrMap;
+ auto MapStr = [&](uint32_t OldOff) -> uint32_t {
+ uint32_t DedupOff = dedupStrOff(OldOff);
+ auto It = StrMap.find(DedupOff);
+ if (It != StrMap.end())
+ return It->second;
+ StringRef S = Builder.findString(DedupOff);
+ uint32_t NewOff = NewBuilder.addString(S);
+ StrMap[DedupOff] = NewOff;
+ return NewOff;
+ };
+
+ StrMap[0] = 0;
+ OldToNew.assign(N + 1, 0);
+ for (uint32_t Id = 1; Id <= N; ++Id) {
+ if (Map[Id] != Id)
+ continue;
+
+ const BTF::CommonType *T = Builder.findType(Id);
+ if (!T)
+ continue;
+
+ BTF::CommonType NewHeader = *T;
+ NewHeader.NameOff = MapStr(T->NameOff);
+ uint32_t NewId = NewBuilder.addType(NewHeader);
+ OldToNew[Id] = NewId;
+
+ ArrayRef<uint8_t> TypeBytes = Builder.getTypeBytes(Id);
+ if (TypeBytes.size() > sizeof(BTF::CommonType)) {
+ ArrayRef<uint8_t> Tail =
+ TypeBytes.slice(sizeof(BTF::CommonType));
+ SmallVector<uint8_t, 64> TailCopy(Tail.begin(), Tail.end());
+ uint8_t *TailPtr = TailCopy.data();
+
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION: {
+ auto *M = reinterpret_cast<BTF::BTFMember *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ M[I].NameOff = MapStr(M[I].NameOff);
+ break;
+ }
+ case BTF::BTF_KIND_ENUM: {
+ auto *E = reinterpret_cast<BTF::BTFEnum *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ E[I].NameOff = MapStr(E[I].NameOff);
+ break;
+ }
+ case BTF::BTF_KIND_ENUM64: {
+ auto *E = reinterpret_cast<BTF::BTFEnum64 *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ E[I].NameOff = MapStr(E[I].NameOff);
+ break;
+ }
+ case BTF::BTF_KIND_FUNC_PROTO: {
+ auto *P = reinterpret_cast<BTF::BTFParam *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ P[I].NameOff = MapStr(P[I].NameOff);
+ break;
+ }
+ default:
+ break;
+ }
+
+ for (uint8_t B : TailCopy)
+ NewBuilder.addTail(B);
+ }
+ }
+
+ for (uint32_t Id = 1; Id <= N; ++Id) {
+ if (OldToNew[Id] != 0)
+ continue;
+ uint32_t CanonId = resolve(Id);
+ OldToNew[Id] = OldToNew[CanonId];
+ }
+
+ for (uint32_t NewId = 1; NewId <= NewBuilder.typesCount(); ++NewId) {
+ MutableArrayRef<uint8_t> Bytes = NewBuilder.getMutableTypeBytes(NewId);
+ if (Bytes.empty())
+ continue;
+
+ auto *T = reinterpret_cast<BTF::CommonType *>(Bytes.data());
+ uint8_t *TailPtr = Bytes.data() + sizeof(BTF::CommonType);
+
+ if (BTFBuilder::hasTypeRef(T->getKind()) && T->Type != 0) {
+ if (T->Type < OldToNew.size())
+ T->Type = OldToNew[T->Type];
+ }
+
+ switch (T->getKind()) {
+ case BTF::BTF_KIND_ARRAY: {
+ auto *A = reinterpret_cast<BTF::BTFArray *>(TailPtr);
+ if (A->ElemType != 0 && A->ElemType < OldToNew.size())
+ A->ElemType = OldToNew[A->ElemType];
+ if (A->IndexType != 0 && A->IndexType < OldToNew.size())
+ A->IndexType = OldToNew[A->IndexType];
+ break;
+ }
+ case BTF::BTF_KIND_STRUCT:
+ case BTF::BTF_KIND_UNION: {
+ auto *M = reinterpret_cast<BTF::BTFMember *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ if (M[I].Type != 0 && M[I].Type < OldToNew.size())
+ M[I].Type = OldToNew[M[I].Type];
+ break;
+ }
+ case BTF::BTF_KIND_FUNC_PROTO: {
+ auto *P = reinterpret_cast<BTF::BTFParam *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ if (P[I].Type != 0 && P[I].Type < OldToNew.size())
+ P[I].Type = OldToNew[P[I].Type];
+ break;
+ }
+ case BTF::BTF_KIND_DATASEC: {
+ auto *D = reinterpret_cast<BTF::BTFDataSec *>(TailPtr);
+ for (unsigned I = 0, VN = T->getVlen(); I < VN; ++I)
+ if (D[I].Type != 0 && D[I].Type < OldToNew.size())
+ D[I].Type = OldToNew[D[I].Type];
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ Builder = std::move(NewBuilder);
+ return Error::success();
+}
+
+//===----------------------------------------------------------------------===//
+// Main entry point
+//===----------------------------------------------------------------------===//
+
+Error BTFDedupState::run() {
+ uint32_t N = Builder.typesCount();
+ if (N == 0)
+ return Error::success();
+
+ Map.resize(N + 1);
+ for (uint32_t I = 0; I <= N; ++I)
+ Map[I] = I;
+ HypotMap.assign(N + 1, BTF_UNPROCESSED);
+
+ if (Error E = dedupStrings())
+ return E;
+
+ TypeHash.resize(N + 1, 0);
+ for (uint32_t Id = 1; Id <= N; ++Id)
+ TypeHash[Id] = hashType(Id);
+
+ if (Error E = dedupPrimitives())
+ return E;
+ if (Error E = dedupComposites())
+ return E;
+ if (Error E = dedupRefs())
+ return E;
+ if (Error E = compact())
+ return E;
+
+ return Error::success();
+}
+
+} // anonymous namespace
+
+Error llvm::BTF::dedup(BTFBuilder &Builder) {
+ BTFDedupState State(Builder);
+ return State.run();
+}
diff --git a/llvm/lib/DebugInfo/BTF/CMakeLists.txt b/llvm/lib/DebugInfo/BTF/CMakeLists.txt
index 689470c8f23e8..20805df16e0b3 100644
--- a/llvm/lib/DebugInfo/BTF/CMakeLists.txt
+++ b/llvm/lib/DebugInfo/BTF/CMakeLists.txt
@@ -1,4 +1,6 @@
add_llvm_component_library(LLVMDebugInfoBTF
+ BTFBuilder.cpp
+ BTFDedup.cpp
BTFParser.cpp
BTFContext.cpp
ADDITIONAL_HEADER_DIRS
diff --git a/llvm/unittests/DebugInfo/BTF/BTFBuilderTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFBuilderTest.cpp
new file mode 100644
index 0000000000000..315c4ac4ed3a5
--- /dev/null
+++ b/llvm/unittests/DebugInfo/BTF/BTFBuilderTest.cpp
@@ -0,0 +1,793 @@
+//===-- BTFBuilderTest.cpp - BTFBuilder unit tests ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFBuilder.h"
+#include "llvm/DebugInfo/BTF/BTFParser.h"
+#include "llvm/ObjectYAML/YAML.h"
+#include "llvm/ObjectYAML/yaml2obj.h"
+#include "llvm/Support/SwapByteOrder.h"
+#include "llvm/Testing/Support/Error.h"
+
+using namespace llvm;
+using namespace llvm::object;
+
+#define ASSERT_SUCCEEDED(E) ASSERT_THAT_ERROR((E), Succeeded())
+
+static uint32_t mkInfo(uint32_t Kind) { return Kind << 24; }
+
+static raw_ostream &operator<<(raw_ostream &OS, const yaml::BinaryRef &Ref) {
+ Ref.writeAsHex(OS);
+ return OS;
+}
+
+static yaml::BinaryRef makeBinRef(const void *Ptr, size_t Size) {
+ return yaml::BinaryRef(
+ ArrayRef<uint8_t>(static_cast<const uint8_t *>(Ptr), Size));
+}
+
+// Wrap raw BTF bytes in an ELF ObjectFile for BTFParser verification.
+// Includes a minimal empty .BTF.ext section (required by BTFParser).
+static std::unique_ptr<ObjectFile>
+makeELFWithBTF(const SmallVectorImpl<uint8_t> &BTFData,
+ SmallString<0> &Storage) {
+ // Build a minimal .BTF.ext section (just the header, no subsections).
+ BTF::ExtHeader ExtHdr = {};
+ ExtHdr.Magic = BTF::MAGIC;
+ ExtHdr.Version = 1;
+ ExtHdr.HdrLen = sizeof(BTF::ExtHeader);
+
+ std::string YamlBuffer;
+ raw_string_ostream Yaml(YamlBuffer);
+ Yaml << R"(
+!ELF
+FileHeader:
+ Class: ELFCLASS64)";
+ if (sys::IsBigEndianHost)
+ Yaml << "\n Data: ELFDATA2MSB";
+ else
+ Yaml << "\n Data: ELFDATA2LSB";
+ Yaml << R"(
+ Type: ET_REL
+ Machine: EM_BPF
+Sections:
+ - Name: .BTF
+ Type: SHT_PROGBITS
+ Content: )"
+ << makeBinRef(BTFData.data(), BTFData.size());
+ Yaml << R"(
+ - Name: .BTF.ext
+ Type: SHT_PROGBITS
+ Content: )"
+ << makeBinRef(&ExtHdr, sizeof(ExtHdr));
+
+ return yaml::yaml2ObjectFile(Storage, YamlBuffer,
+ [](const Twine &Err) { errs() << Err; });
+}
+
+namespace {
+
+TEST(BTFBuilderTest, emptyBuilder) {
+ BTFBuilder B;
+ EXPECT_EQ(B.typesCount(), 0u);
+ EXPECT_EQ(B.findType(0), nullptr);
+ EXPECT_EQ(B.findType(1), nullptr);
+ EXPECT_EQ(B.findString(0), "");
+}
+
+TEST(BTFBuilderTest, addStringAndType) {
+ BTFBuilder B;
+
+ uint32_t FooOff = B.addString("foo");
+ uint32_t BarOff = B.addString("bar");
+ EXPECT_EQ(B.findString(FooOff), "foo");
+ EXPECT_EQ(B.findString(BarOff), "bar");
+ EXPECT_EQ(B.findString(0), "");
+
+ // Add INT type: int foo, 4 bytes.
+ uint32_t Id = B.addType({FooOff, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0); // INT encoding
+ EXPECT_EQ(Id, 1u);
+ EXPECT_EQ(B.typesCount(), 1u);
+
+ const BTF::CommonType *T = B.findType(1);
+ ASSERT_TRUE(T);
+ EXPECT_EQ(T->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(T->Size, 4u);
+ EXPECT_EQ(B.findString(T->NameOff), "foo");
+
+ // Add PTR type pointing to type 1.
+ uint32_t Id2 = B.addType({BarOff, mkInfo(BTF::BTF_KIND_PTR), {1}});
+ EXPECT_EQ(Id2, 2u);
+ EXPECT_EQ(B.typesCount(), 2u);
+
+ const BTF::CommonType *T2 = B.findType(2);
+ ASSERT_TRUE(T2);
+ EXPECT_EQ(T2->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(T2->Type, 1u);
+}
+
+TEST(BTFBuilderTest, typeByteSize) {
+ BTFBuilder B;
+ uint32_t S = B.addString("s");
+
+ // INT: CommonType + uint32_t = 12 + 4 = 16
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ EXPECT_EQ(B.getTypeBytes(1).size(), 16u);
+
+ // PTR: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_PTR), {1}});
+ EXPECT_EQ(B.getTypeBytes(2).size(), 12u);
+
+ // STRUCT with 2 members: 12 + 2*12 = 36
+ B.addType({S, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+ B.addTail(BTF::BTFMember({S, 1, 0}));
+ B.addTail(BTF::BTFMember({S, 1, 32}));
+ EXPECT_EQ(B.getTypeBytes(3).size(), 36u);
+
+ // ARRAY: 12 + 12 = 24
+ B.addType({S, mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+ B.addTail(BTF::BTFArray({1, 1, 10}));
+ EXPECT_EQ(B.getTypeBytes(4).size(), 24u);
+}
+
+TEST(BTFBuilderTest, writeAndParseRoundtrip) {
+ BTFBuilder B;
+
+ // Build a small BTF with various types.
+ uint32_t IntName = B.addString("int");
+ uint32_t FooName = B.addString("foo");
+ uint32_t AName = B.addString("a");
+ uint32_t BName = B.addString("b");
+
+ // Type 1: int, 4 bytes
+ B.addType({IntName, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 2: struct foo { int a; int b; }
+ B.addType({FooName, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+ B.addTail(BTF::BTFMember({AName, 1, 0}));
+ B.addTail(BTF::BTFMember({BName, 1, 32}));
+
+ // Type 3: pointer to struct foo
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {2}});
+
+ // Write to binary.
+ SmallVector<uint8_t, 0> Output;
+ B.write(Output, !sys::IsBigEndianHost);
+
+ // Parse with BTFParser to verify.
+ SmallString<0> Storage;
+ auto Obj = makeELFWithBTF(Output, Storage);
+ ASSERT_TRUE(Obj);
+
+ BTFParser Parser;
+ BTFParser::ParseOptions Opts;
+ Opts.LoadTypes = true;
+ ASSERT_SUCCEEDED(Parser.parse(*Obj, Opts));
+
+ ASSERT_EQ(Parser.typesCount(), 4u); // 3 types + void
+
+ // Verify INT.
+ const BTF::CommonType *IntType = Parser.findType(1);
+ ASSERT_TRUE(IntType);
+ EXPECT_EQ(IntType->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(IntType->Size, 4u);
+ EXPECT_EQ(Parser.findString(IntType->NameOff), "int");
+
+ // Verify STRUCT.
+ const BTF::CommonType *StructType = Parser.findType(2);
+ ASSERT_TRUE(StructType);
+ EXPECT_EQ(StructType->getKind(), BTF::BTF_KIND_STRUCT);
+ EXPECT_EQ(StructType->getVlen(), 2u);
+ EXPECT_EQ(StructType->Size, 8u);
+ EXPECT_EQ(Parser.findString(StructType->NameOff), "foo");
+
+ // Verify struct members via cast.
+ auto *ST = dyn_cast<BTF::StructType>(StructType);
+ ASSERT_TRUE(ST);
+ EXPECT_EQ(Parser.findString(ST->members()[0].NameOff), "a");
+ EXPECT_EQ(ST->members()[0].Type, 1u);
+ EXPECT_EQ(Parser.findString(ST->members()[1].NameOff), "b");
+ EXPECT_EQ(ST->members()[1].Type, 1u);
+
+ // Verify PTR.
+ const BTF::CommonType *PtrType = Parser.findType(3);
+ ASSERT_TRUE(PtrType);
+ EXPECT_EQ(PtrType->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(PtrType->Type, 2u);
+}
+
+TEST(BTFBuilderTest, mergeTwo) {
+ // Build first BTF section.
+ BTFBuilder B1;
+ uint32_t IntName1 = B1.addString("int");
+ B1.addType({IntName1, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B1.addTail((uint32_t)0);
+
+ SmallVector<uint8_t, 0> Blob1;
+ B1.write(Blob1, !sys::IsBigEndianHost);
+
+ // Build second BTF section.
+ BTFBuilder B2;
+ uint32_t LongName2 = B2.addString("long");
+ // Type 1 in B2: long, 8 bytes.
+ B2.addType({LongName2, mkInfo(BTF::BTF_KIND_INT), {8}});
+ B2.addTail((uint32_t)0);
+ // Type 2 in B2: ptr to long (type 1).
+ B2.addType({0, mkInfo(BTF::BTF_KIND_PTR), {1}});
+
+ SmallVector<uint8_t, 0> Blob2;
+ B2.write(Blob2, !sys::IsBigEndianHost);
+
+ // Merge both into a new builder.
+ BTFBuilder Merged;
+ auto FirstId1 = Merged.merge(
+ StringRef(reinterpret_cast<const char *>(Blob1.data()), Blob1.size()),
+ !sys::IsBigEndianHost);
+ ASSERT_SUCCEEDED(FirstId1.takeError());
+ EXPECT_EQ(*FirstId1, 1u);
+ EXPECT_EQ(Merged.typesCount(), 1u);
+
+ auto FirstId2 = Merged.merge(
+ StringRef(reinterpret_cast<const char *>(Blob2.data()), Blob2.size()),
+ !sys::IsBigEndianHost);
+ ASSERT_SUCCEEDED(FirstId2.takeError());
+ EXPECT_EQ(*FirstId2, 2u);
+ EXPECT_EQ(Merged.typesCount(), 3u);
+
+ // Type 1: int from first blob.
+ const BTF::CommonType *T1 = Merged.findType(1);
+ ASSERT_TRUE(T1);
+ EXPECT_EQ(T1->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(T1->Size, 4u);
+ EXPECT_EQ(Merged.findString(T1->NameOff), "int");
+
+ // Type 2: long from second blob.
+ const BTF::CommonType *T2 = Merged.findType(2);
+ ASSERT_TRUE(T2);
+ EXPECT_EQ(T2->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(T2->Size, 8u);
+ EXPECT_EQ(Merged.findString(T2->NameOff), "long");
+
+ // Type 3: ptr to type 2 (remapped from type 1 in second blob).
+ const BTF::CommonType *T3 = Merged.findType(3);
+ ASSERT_TRUE(T3);
+ EXPECT_EQ(T3->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(T3->Type, 2u); // Remapped: was 1, now 1+1=2
+
+ // Verify roundtrip through BTFParser.
+ SmallVector<uint8_t, 0> MergedOutput;
+ Merged.write(MergedOutput, !sys::IsBigEndianHost);
+
+ SmallString<0> Storage;
+ auto Obj = makeELFWithBTF(MergedOutput, Storage);
+ ASSERT_TRUE(Obj);
+
+ BTFParser Parser;
+ BTFParser::ParseOptions Opts;
+ Opts.LoadTypes = true;
+ ASSERT_SUCCEEDED(Parser.parse(*Obj, Opts));
+ EXPECT_EQ(Parser.typesCount(), 4u); // 3 types + void
+}
+
+TEST(BTFBuilderTest, mergeStructWithMembers) {
+ // Build BTF with struct that references other types.
+ BTFBuilder B1;
+ uint32_t IntName = B1.addString("int");
+ uint32_t FooName = B1.addString("foo");
+ uint32_t XName = B1.addString("x");
+ uint32_t YName = B1.addString("y");
+
+ // Type 1: int
+ B1.addType({IntName, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B1.addTail((uint32_t)0);
+ // Type 2: struct foo { int x; int y; }
+ B1.addType({FooName, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+ B1.addTail(BTF::BTFMember({XName, 1, 0}));
+ B1.addTail(BTF::BTFMember({YName, 1, 32}));
+ // Type 3: ptr to void
+ B1.addType({0, mkInfo(BTF::BTF_KIND_PTR), {0}});
+
+ SmallVector<uint8_t, 0> Blob1;
+ B1.write(Blob1, !sys::IsBigEndianHost);
+
+ // Merge into a builder that already has one type.
+ BTFBuilder Merged;
+ uint32_t PreName = Merged.addString("pre");
+ Merged.addType({PreName, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+ EXPECT_EQ(Merged.typesCount(), 1u);
+
+ auto FirstId = Merged.merge(
+ StringRef(reinterpret_cast<const char *>(Blob1.data()), Blob1.size()),
+ !sys::IsBigEndianHost);
+ ASSERT_SUCCEEDED(FirstId.takeError());
+ EXPECT_EQ(*FirstId, 2u); // First new type from merge
+ EXPECT_EQ(Merged.typesCount(), 4u);
+
+ // Type 1: pre-existing FLOAT
+ EXPECT_EQ(Merged.findType(1)->getKind(), BTF::BTF_KIND_FLOAT);
+
+ // Type 2: int (remapped from id 1)
+ const BTF::CommonType *IntT = Merged.findType(2);
+ ASSERT_TRUE(IntT);
+ EXPECT_EQ(IntT->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(Merged.findString(IntT->NameOff), "int");
+
+ // Type 3: struct foo with member types remapped to 2
+ const BTF::CommonType *StructT = Merged.findType(3);
+ ASSERT_TRUE(StructT);
+ EXPECT_EQ(StructT->getKind(), BTF::BTF_KIND_STRUCT);
+ EXPECT_EQ(Merged.findString(StructT->NameOff), "foo");
+ auto *ST = dyn_cast<BTF::StructType>(StructT);
+ ASSERT_TRUE(ST);
+ EXPECT_EQ(Merged.findString(ST->members()[0].NameOff), "x");
+ EXPECT_EQ(ST->members()[0].Type, 2u); // Was 1, remapped to 2
+ EXPECT_EQ(Merged.findString(ST->members()[1].NameOff), "y");
+ EXPECT_EQ(ST->members()[1].Type, 2u); // Was 1, remapped to 2
+
+ // Type 4: ptr to void (type 0 stays 0, not remapped)
+ const BTF::CommonType *PtrT = Merged.findType(4);
+ ASSERT_TRUE(PtrT);
+ EXPECT_EQ(PtrT->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(PtrT->Type, 0u); // void stays 0
+}
+
+TEST(BTFBuilderTest, mergeAllKinds) {
+ BTFBuilder B;
+
+ // Build a BTF with all type kinds.
+ uint32_t S = B.addString("t");
+ uint32_t M = B.addString("m");
+
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}}); // 1
+ B.addTail((uint32_t)0);
+ B.addType({S, mkInfo(BTF::BTF_KIND_PTR), {1}}); // 2
+ B.addType({S, mkInfo(BTF::BTF_KIND_ARRAY), {0}}); // 3
+ B.addTail(BTF::BTFArray({1, 1, 10}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // 4
+ B.addTail(BTF::BTFMember({M, 1, 0}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_UNION) | 1, {4}}); // 5
+ B.addTail(BTF::BTFMember({M, 1, 0}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM) | 1, {4}}); // 6
+ B.addTail(BTF::BTFEnum({M, 42}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_FWD), {0}}); // 7
+ B.addType({S, mkInfo(BTF::BTF_KIND_TYPEDEF), {1}}); // 8
+ B.addType({S, mkInfo(BTF::BTF_KIND_VOLATILE), {1}}); // 9
+ B.addType({S, mkInfo(BTF::BTF_KIND_CONST), {1}}); // 10
+ B.addType({S, mkInfo(BTF::BTF_KIND_RESTRICT), {1}}); // 11
+ B.addType({S, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}}); // 12
+ B.addTail(BTF::BTFParam({M, 1}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_FUNC), {12}}); // 13
+ B.addType({S, mkInfo(BTF::BTF_KIND_VAR), {1}}); // 14
+ B.addTail((uint32_t)0); // linkage
+ B.addType({S, mkInfo(BTF::BTF_KIND_DATASEC) | 1, {0}}); // 15
+ B.addTail(BTF::BTFDataSec({14, 0, 4}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_FLOAT), {4}}); // 16
+ B.addType({S, mkInfo(BTF::BTF_KIND_DECL_TAG), {1}}); // 17
+ B.addTail((uint32_t)-1); // component_idx
+ B.addType({S, mkInfo(BTF::BTF_KIND_TYPE_TAG), {1}}); // 18
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}}); // 19
+ B.addTail(BTF::BTFEnum64({M, 1, 0}));
+
+ EXPECT_EQ(B.typesCount(), 19u);
+
+ // Write and parse back.
+ SmallVector<uint8_t, 0> Output;
+ B.write(Output, !sys::IsBigEndianHost);
+
+ SmallString<0> Storage;
+ auto Obj = makeELFWithBTF(Output, Storage);
+ ASSERT_TRUE(Obj);
+
+ BTFParser Parser;
+ BTFParser::ParseOptions Opts;
+ Opts.LoadTypes = true;
+ ASSERT_SUCCEEDED(Parser.parse(*Obj, Opts));
+ EXPECT_EQ(Parser.typesCount(), 20u); // 19 + void
+
+ // Verify all types were parsed correctly.
+ for (uint32_t Id = 1; Id <= 19; ++Id) {
+ ASSERT_TRUE(Parser.findType(Id))
+ << "Type " << Id << " not found after roundtrip";
+ }
+
+ // Spot-check a few.
+ EXPECT_EQ(Parser.findType(1)->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(Parser.findType(2)->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(Parser.findType(3)->getKind(), BTF::BTF_KIND_ARRAY);
+ EXPECT_EQ(Parser.findType(6)->getKind(), BTF::BTF_KIND_ENUM);
+ EXPECT_EQ(Parser.findType(12)->getKind(), BTF::BTF_KIND_FUNC_PROTO);
+ EXPECT_EQ(Parser.findType(19)->getKind(), BTF::BTF_KIND_ENUM64);
+}
+
+TEST(BTFBuilderTest, mergeInvalidBTF) {
+ BTFBuilder B;
+
+ // Too small.
+ EXPECT_THAT_ERROR(B.merge("", !sys::IsBigEndianHost).takeError(),
+ FailedWithMessage(testing::HasSubstr("too small")));
+
+ // Bad magic.
+ SmallVector<uint8_t, 0> BadMagic(sizeof(BTF::Header), 0);
+ EXPECT_THAT_ERROR(
+ B.merge(StringRef(reinterpret_cast<const char *>(BadMagic.data()),
+ BadMagic.size()),
+ !sys::IsBigEndianHost)
+ .takeError(),
+ FailedWithMessage(testing::HasSubstr("invalid BTF magic")));
+}
+
+// Helper to build a raw BTF blob from a BTFBuilder.
+static StringRef blobRef(const SmallVectorImpl<uint8_t> &V) {
+ return StringRef(reinterpret_cast<const char *>(V.data()), V.size());
+}
+
+TEST(BTFBuilderTest, invalidTypeIdLookups) {
+ BTFBuilder B;
+ uint32_t S = B.addString("x");
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // ID 0 (void) returns nullptr / empty.
+ EXPECT_EQ(B.findType(0), nullptr);
+ EXPECT_TRUE(B.getTypeBytes(0).empty());
+ EXPECT_TRUE(B.getMutableTypeBytes(0).empty());
+
+ // ID beyond range returns nullptr / empty.
+ EXPECT_EQ(B.findType(2), nullptr);
+ EXPECT_TRUE(B.getTypeBytes(2).empty());
+ EXPECT_TRUE(B.getMutableTypeBytes(2).empty());
+ EXPECT_EQ(B.findType(UINT32_MAX), nullptr);
+}
+
+TEST(BTFBuilderTest, getMutableTypeBytesIsWritable) {
+ BTFBuilder B;
+ uint32_t S = B.addString("int");
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0x00000020); // bits=32
+
+ // Read the INT encoding via mutable bytes and change it.
+ MutableArrayRef<uint8_t> Bytes = B.getMutableTypeBytes(1);
+ ASSERT_EQ(Bytes.size(), 16u);
+ auto *T = reinterpret_cast<BTF::CommonType *>(Bytes.data());
+ EXPECT_EQ(T->Size, 4u);
+
+ // Mutate: change size to 8.
+ T->Size = 8;
+
+ // Verify mutation is visible through findType.
+ const BTF::CommonType *T2 = B.findType(1);
+ EXPECT_EQ(T2->Size, 8u);
+}
+
+TEST(BTFBuilderTest, findStringOutOfBounds) {
+ BTFBuilder B;
+ B.addString("hello");
+ // Offset 0 is always the empty string.
+ EXPECT_EQ(B.findString(0), "");
+ // Far past the end.
+ EXPECT_TRUE(B.findString(99999).empty());
+}
+
+TEST(BTFBuilderTest, hasTypeRefAllKinds) {
+ // Types that use Type (not Size) in the CommonType union.
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_PTR));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_TYPEDEF));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_VOLATILE));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_CONST));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_RESTRICT));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_FUNC));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_FUNC_PROTO));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_VAR));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_DECL_TAG));
+ EXPECT_TRUE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_TYPE_TAG));
+
+ // Types that use Size (not a type reference).
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_INT));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_ARRAY));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_STRUCT));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_UNION));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_ENUM));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_ENUM64));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_FWD));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_FLOAT));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_DATASEC));
+ EXPECT_FALSE(BTFBuilder::hasTypeRef(BTF::BTF_KIND_UNKN));
+}
+
+TEST(BTFBuilderTest, typeByteSizeAllKinds) {
+ BTFBuilder B;
+ uint32_t S = B.addString("s");
+ uint32_t M = B.addString("m");
+
+ // ENUM with 2 values: 12 + 2*8 = 28
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}}); // 1
+ B.addTail(BTF::BTFEnum({M, 0}));
+ B.addTail(BTF::BTFEnum({M, 1}));
+ EXPECT_EQ(B.getTypeBytes(1).size(), 28u);
+
+ // ENUM64 with 1 value: 12 + 1*12 = 24
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}}); // 2
+ B.addTail(BTF::BTFEnum64({M, 0, 0}));
+ EXPECT_EQ(B.getTypeBytes(2).size(), 24u);
+
+ // FUNC_PROTO with 2 params: 12 + 2*8 = 28
+ B.addType({0, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 2, {0}}); // 3
+ B.addTail(BTF::BTFParam({M, 1}));
+ B.addTail(BTF::BTFParam({M, 1}));
+ EXPECT_EQ(B.getTypeBytes(3).size(), 28u);
+
+ // DATASEC with 1 entry: 12 + 1*12 = 24
+ B.addType({S, mkInfo(BTF::BTF_KIND_DATASEC) | 1, {0}}); // 4
+ B.addTail(BTF::BTFDataSec({1, 0, 4}));
+ EXPECT_EQ(B.getTypeBytes(4).size(), 24u);
+
+ // VAR: 12 + 4 = 16
+ B.addType({S, mkInfo(BTF::BTF_KIND_VAR), {1}}); // 5
+ B.addTail((uint32_t)0);
+ EXPECT_EQ(B.getTypeBytes(5).size(), 16u);
+
+ // DECL_TAG: 12 + 4 = 16
+ B.addType({S, mkInfo(BTF::BTF_KIND_DECL_TAG), {1}}); // 6
+ B.addTail((uint32_t)-1);
+ EXPECT_EQ(B.getTypeBytes(6).size(), 16u);
+
+ // FLOAT: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_FLOAT), {4}}); // 7
+ EXPECT_EQ(B.getTypeBytes(7).size(), 12u);
+
+ // FWD: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_FWD), {0}}); // 8
+ EXPECT_EQ(B.getTypeBytes(8).size(), 12u);
+
+ // TYPEDEF: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_TYPEDEF), {1}}); // 9
+ EXPECT_EQ(B.getTypeBytes(9).size(), 12u);
+
+ // VOLATILE: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_VOLATILE), {1}}); // 10
+ EXPECT_EQ(B.getTypeBytes(10).size(), 12u);
+
+ // CONST: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_CONST), {1}}); // 11
+ EXPECT_EQ(B.getTypeBytes(11).size(), 12u);
+
+ // RESTRICT: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_RESTRICT), {1}}); // 12
+ EXPECT_EQ(B.getTypeBytes(12).size(), 12u);
+
+ // FUNC: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_FUNC), {3}}); // 13
+ EXPECT_EQ(B.getTypeBytes(13).size(), 12u);
+
+ // TYPE_TAG: CommonType only = 12
+ B.addType({S, mkInfo(BTF::BTF_KIND_TYPE_TAG), {1}}); // 14
+ EXPECT_EQ(B.getTypeBytes(14).size(), 12u);
+
+ // UNION with 1 member: 12 + 1*12 = 24
+ B.addType({S, mkInfo(BTF::BTF_KIND_UNION) | 1, {4}}); // 15
+ B.addTail(BTF::BTFMember({M, 1, 0}));
+ EXPECT_EQ(B.getTypeBytes(15).size(), 24u);
+}
+
+TEST(BTFBuilderTest, mergeBadVersion) {
+ // Build a valid BTF section but with version=2.
+ BTFBuilder Tmp;
+ uint32_t S = Tmp.addString("x");
+ Tmp.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ Tmp.addTail((uint32_t)0);
+ SmallVector<uint8_t, 0> Blob;
+ Tmp.write(Blob, !sys::IsBigEndianHost);
+
+ // Corrupt the version byte (offset 2 in the header).
+ Blob[2] = 99;
+
+ BTFBuilder B;
+ EXPECT_THAT_ERROR(B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError(),
+ FailedWithMessage(testing::HasSubstr("unsupported BTF version")));
+ EXPECT_EQ(B.typesCount(), 0u);
+}
+
+TEST(BTFBuilderTest, mergeStringBoundsExceeded) {
+ // Build a valid BTF, then corrupt str_off+str_len to exceed section size.
+ BTFBuilder Tmp;
+ uint32_t S = Tmp.addString("x");
+ Tmp.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ Tmp.addTail((uint32_t)0);
+ SmallVector<uint8_t, 0> Blob;
+ Tmp.write(Blob, !sys::IsBigEndianHost);
+
+ // Corrupt StrLen: at offset 20 (uint32_t), set it to a huge value.
+ uint32_t HugeLen = 0xFFFFFF;
+ memcpy(&Blob[20], &HugeLen, sizeof(HugeLen));
+
+ BTFBuilder B;
+ EXPECT_THAT_ERROR(B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError(),
+ FailedWithMessage(testing::HasSubstr("exceeds section bounds")));
+ EXPECT_EQ(B.typesCount(), 0u);
+}
+
+TEST(BTFBuilderTest, mergeTypeBoundsExceeded) {
+ // Corrupt type_len to exceed section size.
+ BTFBuilder Tmp;
+ uint32_t S = Tmp.addString("x");
+ Tmp.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ Tmp.addTail((uint32_t)0);
+ SmallVector<uint8_t, 0> Blob;
+ Tmp.write(Blob, !sys::IsBigEndianHost);
+
+ // Corrupt TypeLen: at offset 12 in header.
+ uint32_t HugeLen = 0xFFFFFF;
+ memcpy(&Blob[12], &HugeLen, sizeof(HugeLen));
+
+ BTFBuilder B;
+ EXPECT_THAT_ERROR(B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError(),
+ FailedWithMessage(testing::HasSubstr("exceeds section bounds")));
+ EXPECT_EQ(B.typesCount(), 0u);
+}
+
+TEST(BTFBuilderTest, mergeIncompleteTypeRollback) {
+ // Manually build a raw BTF blob where the type section contains a STRUCT
+ // header claiming vlen=2 (needs 36 bytes), but only 28 bytes of type data.
+ BTF::Header Hdr;
+ Hdr.Magic = BTF::MAGIC;
+ Hdr.Version = BTF::VERSION;
+ Hdr.Flags = 0;
+ Hdr.HdrLen = sizeof(BTF::Header);
+ Hdr.TypeOff = 0;
+ Hdr.TypeLen = 28; // Struct with 2 members needs 36, only 28 given
+ Hdr.StrOff = 28;
+ Hdr.StrLen = 3; // "\0x\0"
+
+ SmallVector<uint8_t, 0> Blob(sizeof(Hdr) + 28 + 3, 0);
+ memcpy(Blob.data(), &Hdr, sizeof(Hdr));
+
+ // STRUCT header at start of type section.
+ uint8_t *TypePtr = Blob.data() + sizeof(Hdr);
+ BTF::CommonType CT;
+ CT.NameOff = 1; // "x"
+ CT.Info = mkInfo(BTF::BTF_KIND_STRUCT) | 2; // vlen=2
+ CT.Size = 8;
+ memcpy(TypePtr, &CT, sizeof(CT));
+ // Write 1 member (12 bytes) — struct claims 2 but only space for ~1.
+ BTF::BTFMember Mem = {1, 0, 0};
+ memcpy(TypePtr + sizeof(CT), &Mem, sizeof(Mem));
+
+ // String table: "\0x\0"
+ uint8_t *StrPtr = Blob.data() + sizeof(Hdr) + 28;
+ StrPtr[0] = 0;
+ StrPtr[1] = 'x';
+ StrPtr[2] = 0;
+
+ // Pre-populate the builder, then attempt merge.
+ BTFBuilder B;
+ uint32_t Pre = B.addString("pre");
+ B.addType({Pre, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+ EXPECT_EQ(B.typesCount(), 1u);
+
+ EXPECT_THAT_ERROR(
+ B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError(),
+ FailedWithMessage(testing::HasSubstr("incomplete type")));
+ // Builder state should be rolled back — still just 1 type.
+ EXPECT_EQ(B.typesCount(), 1u);
+}
+
+TEST(BTFBuilderTest, mergeArrayRemapsElemAndIndexType) {
+ BTFBuilder Src;
+ uint32_t IntS = Src.addString("int");
+ // Type 1: int
+ Src.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ Src.addTail((uint32_t)0);
+ // Type 2: array of 10 ints, indexed by int
+ Src.addType({0, mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+ Src.addTail(BTF::BTFArray({1, 1, 10}));
+
+ SmallVector<uint8_t, 0> Blob;
+ Src.write(Blob, !sys::IsBigEndianHost);
+
+ // Merge into a builder that already has 1 type.
+ BTFBuilder B;
+ uint32_t Pre = B.addString("pre");
+ B.addType({Pre, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+
+ ASSERT_SUCCEEDED(B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError());
+ EXPECT_EQ(B.typesCount(), 3u);
+
+ // Array is type 3, int is type 2. Array's ElemType and IndexType should
+ // be remapped from 1 to 2.
+ const BTF::CommonType *ArrT = B.findType(3);
+ ASSERT_TRUE(ArrT);
+ EXPECT_EQ(ArrT->getKind(), BTF::BTF_KIND_ARRAY);
+ auto *Arr = reinterpret_cast<const BTF::BTFArray *>(
+ reinterpret_cast<const uint8_t *>(ArrT) + sizeof(BTF::CommonType));
+ EXPECT_EQ(Arr->ElemType, 2u);
+ EXPECT_EQ(Arr->IndexType, 2u);
+ EXPECT_EQ(Arr->Nelems, 10u);
+}
+
+TEST(BTFBuilderTest, mergeFuncProtoRemapsParamTypes) {
+ BTFBuilder Src;
+ uint32_t IntS = Src.addString("int");
+ uint32_t XS = Src.addString("x");
+ // Type 1: int
+ Src.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ Src.addTail((uint32_t)0);
+ // Type 2: func_proto(int x) -> int
+ Src.addType({0, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}});
+ Src.addTail(BTF::BTFParam({XS, 1}));
+
+ SmallVector<uint8_t, 0> Blob;
+ Src.write(Blob, !sys::IsBigEndianHost);
+
+ BTFBuilder B;
+ uint32_t Pre = B.addString("pre");
+ B.addType({Pre, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+
+ ASSERT_SUCCEEDED(B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError());
+ EXPECT_EQ(B.typesCount(), 3u);
+
+ // func_proto is type 3, returns type 2 (remapped int).
+ const BTF::CommonType *FP = B.findType(3);
+ ASSERT_TRUE(FP);
+ EXPECT_EQ(FP->getKind(), BTF::BTF_KIND_FUNC_PROTO);
+ EXPECT_EQ(FP->Type, 2u); // Return type remapped
+ auto *Params = reinterpret_cast<const BTF::BTFParam *>(
+ reinterpret_cast<const uint8_t *>(FP) + sizeof(BTF::CommonType));
+ EXPECT_EQ(Params[0].Type, 2u); // Param type remapped
+}
+
+TEST(BTFBuilderTest, mergeDataSecRemapsVarTypes) {
+ BTFBuilder Src;
+ uint32_t IntS = Src.addString("int");
+ uint32_t DS = Src.addString(".data");
+ // Type 1: int
+ Src.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ Src.addTail((uint32_t)0);
+ // Type 2: VAR referencing int
+ Src.addType({IntS, mkInfo(BTF::BTF_KIND_VAR), {1}});
+ Src.addTail((uint32_t)1); // global linkage
+ // Type 3: DATASEC with 1 var
+ Src.addType({DS, mkInfo(BTF::BTF_KIND_DATASEC) | 1, {4}});
+ Src.addTail(BTF::BTFDataSec({2, 0, 4}));
+
+ SmallVector<uint8_t, 0> Blob;
+ Src.write(Blob, !sys::IsBigEndianHost);
+
+ BTFBuilder B;
+ uint32_t Pre = B.addString("pre");
+ B.addType({Pre, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+
+ ASSERT_SUCCEEDED(B.merge(blobRef(Blob), !sys::IsBigEndianHost).takeError());
+ EXPECT_EQ(B.typesCount(), 4u);
+
+ // DATASEC is type 4, its var entry should point to type 3 (remapped VAR).
+ const BTF::CommonType *DSec = B.findType(4);
+ ASSERT_TRUE(DSec);
+ EXPECT_EQ(DSec->getKind(), BTF::BTF_KIND_DATASEC);
+ auto *Vars = reinterpret_cast<const BTF::BTFDataSec *>(
+ reinterpret_cast<const uint8_t *>(DSec) + sizeof(BTF::CommonType));
+ EXPECT_EQ(Vars[0].Type, 3u); // Remapped from 2 to 3
+}
+
+TEST(BTFBuilderTest, writeEmptyBuilder) {
+ BTFBuilder B;
+ SmallVector<uint8_t, 0> Output;
+ B.write(Output, !sys::IsBigEndianHost);
+
+ // Should produce a valid header with empty type and string sections.
+ EXPECT_GE(Output.size(), sizeof(BTF::Header));
+ BTF::Header Hdr;
+ memcpy(&Hdr, Output.data(), sizeof(Hdr));
+ EXPECT_EQ(Hdr.Magic, BTF::MAGIC);
+ EXPECT_EQ(Hdr.TypeLen, 0u);
+ // String table always has at least the empty string (\0).
+ EXPECT_EQ(Hdr.StrLen, 1u);
+}
+
+} // namespace
diff --git a/llvm/unittests/DebugInfo/BTF/BTFDedupTest.cpp b/llvm/unittests/DebugInfo/BTF/BTFDedupTest.cpp
new file mode 100644
index 0000000000000..a761e697d9555
--- /dev/null
+++ b/llvm/unittests/DebugInfo/BTF/BTFDedupTest.cpp
@@ -0,0 +1,1047 @@
+//===-- BTFDedupTest.cpp - BTFDedup unit tests ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/DebugInfo/BTF/BTFDedup.h"
+#include "llvm/DebugInfo/BTF/BTFBuilder.h"
+#include "llvm/DebugInfo/BTF/BTFParser.h"
+#include "llvm/ObjectYAML/YAML.h"
+#include "llvm/ObjectYAML/yaml2obj.h"
+#include "llvm/Testing/Support/Error.h"
+
+using namespace llvm;
+using namespace llvm::object;
+
+#define ASSERT_SUCCEEDED(E) ASSERT_THAT_ERROR((E), Succeeded())
+
+static uint32_t mkInfo(uint32_t Kind) { return Kind << 24; }
+
+static raw_ostream &operator<<(raw_ostream &OS, const yaml::BinaryRef &Ref) {
+ Ref.writeAsHex(OS);
+ return OS;
+}
+
+static yaml::BinaryRef makeBinRef(const void *Ptr, size_t Size) {
+ return yaml::BinaryRef(
+ ArrayRef<uint8_t>(static_cast<const uint8_t *>(Ptr), Size));
+}
+
+static std::unique_ptr<ObjectFile>
+makeELFWithBTF(const SmallVectorImpl<uint8_t> &BTFData,
+ SmallString<0> &Storage) {
+ BTF::ExtHeader ExtHdr = {};
+ ExtHdr.Magic = BTF::MAGIC;
+ ExtHdr.Version = 1;
+ ExtHdr.HdrLen = sizeof(BTF::ExtHeader);
+
+ std::string YamlBuffer;
+ raw_string_ostream Yaml(YamlBuffer);
+ Yaml << R"(
+!ELF
+FileHeader:
+ Class: ELFCLASS64)";
+ if (sys::IsBigEndianHost)
+ Yaml << "\n Data: ELFDATA2MSB";
+ else
+ Yaml << "\n Data: ELFDATA2LSB";
+ Yaml << R"(
+ Type: ET_REL
+ Machine: EM_BPF
+Sections:
+ - Name: .BTF
+ Type: SHT_PROGBITS
+ Content: )"
+ << makeBinRef(BTFData.data(), BTFData.size());
+ Yaml << R"(
+ - Name: .BTF.ext
+ Type: SHT_PROGBITS
+ Content: )"
+ << makeBinRef(&ExtHdr, sizeof(ExtHdr));
+
+ return yaml::yaml2ObjectFile(Storage, YamlBuffer,
+ [](const Twine &Err) { errs() << Err; });
+}
+
+namespace {
+
+TEST(BTFDedupTest, emptyDedup) {
+ BTFBuilder B;
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 0u);
+}
+
+TEST(BTFDedupTest, singleType) {
+ BTFBuilder B;
+ uint32_t S = B.addString("int");
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+ EXPECT_EQ(B.findType(1)->getKind(), BTF::BTF_KIND_INT);
+}
+
+TEST(BTFDedupTest, duplicateInts) {
+ BTFBuilder B;
+ uint32_t S1 = B.addString("int");
+ uint32_t S2 = B.addString("int"); // Same string, different offset
+
+ // Two identical INT types.
+ B.addType({S1, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ B.addType({S2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ EXPECT_EQ(B.typesCount(), 2u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+
+ const BTF::CommonType *T = B.findType(1);
+ ASSERT_TRUE(T);
+ EXPECT_EQ(T->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(T->Size, 4u);
+ EXPECT_EQ(B.findString(T->NameOff), "int");
+}
+
+TEST(BTFDedupTest, differentInts) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t LongS = B.addString("long");
+
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ B.addType({LongS, mkInfo(BTF::BTF_KIND_INT), {8}});
+ B.addTail((uint32_t)0);
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, duplicateEnums) {
+ BTFBuilder B;
+ uint32_t EnumS = B.addString("color");
+ uint32_t RedS = B.addString("RED");
+ uint32_t BlueS = B.addString("BLUE");
+
+ // First enum
+ B.addType({EnumS, mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}});
+ B.addTail(BTF::BTFEnum({RedS, 0}));
+ B.addTail(BTF::BTFEnum({BlueS, 1}));
+
+ // Duplicate enum (same content, different string offsets)
+ uint32_t EnumS2 = B.addString("color");
+ uint32_t RedS2 = B.addString("RED");
+ uint32_t BlueS2 = B.addString("BLUE");
+ B.addType({EnumS2, mkInfo(BTF::BTF_KIND_ENUM) | 2, {4}});
+ B.addTail(BTF::BTFEnum({RedS2, 0}));
+ B.addTail(BTF::BTFEnum({BlueS2, 1}));
+
+ EXPECT_EQ(B.typesCount(), 2u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+}
+
+TEST(BTFDedupTest, duplicatePtrTypes) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: duplicate int
+ uint32_t IntS2 = B.addString("int");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 3: ptr to type 1
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {1}});
+ // Type 4: ptr to type 2
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {2}});
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // Should dedup to: int + ptr
+ EXPECT_EQ(B.typesCount(), 2u);
+
+ EXPECT_EQ(B.findType(1)->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(B.findType(2)->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(B.findType(2)->Type, 1u);
+}
+
+TEST(BTFDedupTest, duplicateStructs) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t FooS = B.addString("foo");
+ uint32_t AS = B.addString("a");
+ uint32_t BS = B.addString("b");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 2: struct foo { int a; int b; }
+ B.addType({FooS, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+ B.addTail(BTF::BTFMember({AS, 1, 0}));
+ B.addTail(BTF::BTFMember({BS, 1, 32}));
+
+ // Duplicate types (from another compilation unit):
+ // Type 3: int (duplicate)
+ uint32_t IntS2 = B.addString("int");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 4: struct foo { int a; int b; } (duplicate, refs type 3)
+ uint32_t FooS2 = B.addString("foo");
+ uint32_t AS2 = B.addString("a");
+ uint32_t BS2 = B.addString("b");
+ B.addType({FooS2, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+ B.addTail(BTF::BTFMember({AS2, 3, 0})); // refs type 3 (dup int)
+ B.addTail(BTF::BTFMember({BS2, 3, 32})); // refs type 3 (dup int)
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // Should dedup to: int + struct foo
+ EXPECT_EQ(B.typesCount(), 2u);
+
+ // Verify struct members reference the deduped int.
+ const BTF::CommonType *ST = B.findType(2);
+ ASSERT_TRUE(ST);
+ EXPECT_EQ(ST->getKind(), BTF::BTF_KIND_STRUCT);
+ auto *Members = reinterpret_cast<const BTF::BTFMember *>(
+ reinterpret_cast<const uint8_t *>(ST) + sizeof(BTF::CommonType));
+ EXPECT_EQ(Members[0].Type, 1u); // Remapped to new int ID
+ EXPECT_EQ(Members[1].Type, 1u);
+}
+
+TEST(BTFDedupTest, selfReferentialStruct) {
+ BTFBuilder B;
+ uint32_t NodeS = B.addString("node");
+ uint32_t DataS = B.addString("data");
+ uint32_t NextS = B.addString("next");
+ uint32_t IntS = B.addString("int");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: ptr to type 3 (struct node)
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {3}});
+ // Type 3: struct node { int data; struct node *next; }
+ B.addType({NodeS, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {16}});
+ B.addTail(BTF::BTFMember({DataS, 1, 0}));
+ B.addTail(BTF::BTFMember({NextS, 2, 64}));
+
+ // Duplicate self-referential struct:
+ // Type 4: int (dup)
+ uint32_t IntS2 = B.addString("int");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 5: ptr to type 6
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {6}});
+ // Type 6: struct node (dup, refs types 4 and 5)
+ uint32_t NodeS2 = B.addString("node");
+ uint32_t DataS2 = B.addString("data");
+ uint32_t NextS2 = B.addString("next");
+ B.addType({NodeS2, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {16}});
+ B.addTail(BTF::BTFMember({DataS2, 4, 0}));
+ B.addTail(BTF::BTFMember({NextS2, 5, 64}));
+
+ EXPECT_EQ(B.typesCount(), 6u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // Should dedup to: int + ptr + struct node = 3 types
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, fwdDeclResolution) {
+ BTFBuilder B;
+ uint32_t FooS = B.addString("foo");
+
+ // Type 1: forward declaration of struct foo
+ B.addType({FooS, mkInfo(BTF::BTF_KIND_FWD), {0}});
+
+ // Type 2: full struct foo {}
+ uint32_t FooS2 = B.addString("foo");
+ B.addType({FooS2, mkInfo(BTF::BTF_KIND_STRUCT), {0}});
+
+ // FWD and STRUCT are different kinds, so they shouldn't be deduped
+ // by the basic algorithm (FWD resolution is a separate concern).
+ // Both should survive dedup.
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, funcProtoDedup) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t XS = B.addString("x");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 2: func_proto(int) -> int
+ B.addType({0, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}});
+ B.addTail(BTF::BTFParam({XS, 1}));
+
+ // Duplicate func_proto:
+ // Type 3: int (dup)
+ uint32_t IntS2 = B.addString("int");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 4: func_proto(int) -> int (dup, refs type 3)
+ uint32_t XS2 = B.addString("x");
+ B.addType({0, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {3}});
+ B.addTail(BTF::BTFParam({XS2, 3}));
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // Should dedup to: int + func_proto
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, roundtripAfterDedup) {
+ BTFBuilder B;
+
+ // Build types with duplicates.
+ uint32_t IntS = B.addString("int");
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ uint32_t IntS2 = B.addString("int");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {1}});
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {2}});
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+
+ // Write to binary and verify with BTFParser.
+ SmallVector<uint8_t, 0> Output;
+ B.write(Output, !sys::IsBigEndianHost);
+
+ SmallString<0> Storage;
+ auto Obj = makeELFWithBTF(Output, Storage);
+ ASSERT_TRUE(Obj);
+
+ BTFParser Parser;
+ BTFParser::ParseOptions Opts;
+ Opts.LoadTypes = true;
+ ASSERT_SUCCEEDED(Parser.parse(*Obj, Opts));
+ EXPECT_EQ(Parser.typesCount(), 3u); // 2 types + void
+
+ EXPECT_EQ(Parser.findType(1)->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(Parser.findType(2)->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(Parser.findType(2)->Type, 1u);
+}
+
+TEST(BTFDedupTest, mergedBlobDedup) {
+ // Simulate what lld would do: merge two .BTF sections, then dedup.
+
+ // First "object file" BTF.
+ BTFBuilder B1;
+ uint32_t IntS1 = B1.addString("int");
+ B1.addType({IntS1, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B1.addTail((uint32_t)0);
+ uint32_t FooS1 = B1.addString("foo");
+ uint32_t XS1 = B1.addString("x");
+ B1.addType({FooS1, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B1.addTail(BTF::BTFMember({XS1, 1, 0}));
+
+ SmallVector<uint8_t, 0> Blob1;
+ B1.write(Blob1, !sys::IsBigEndianHost);
+
+ // Second "object file" BTF (same types, different IDs).
+ BTFBuilder B2;
+ uint32_t IntS2 = B2.addString("int");
+ B2.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B2.addTail((uint32_t)0);
+ uint32_t FooS2 = B2.addString("foo");
+ uint32_t XS2 = B2.addString("x");
+ B2.addType({FooS2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B2.addTail(BTF::BTFMember({XS2, 1, 0}));
+
+ SmallVector<uint8_t, 0> Blob2;
+ B2.write(Blob2, !sys::IsBigEndianHost);
+
+ // Merge.
+ BTFBuilder Merged;
+ ASSERT_SUCCEEDED(
+ Merged.merge(StringRef(reinterpret_cast<const char *>(Blob1.data()),
+ Blob1.size()),
+ !sys::IsBigEndianHost)
+ .takeError());
+ ASSERT_SUCCEEDED(
+ Merged.merge(StringRef(reinterpret_cast<const char *>(Blob2.data()),
+ Blob2.size()),
+ !sys::IsBigEndianHost)
+ .takeError());
+ EXPECT_EQ(Merged.typesCount(), 4u); // 2 from each blob
+
+ // Dedup.
+ ASSERT_SUCCEEDED(BTF::dedup(Merged));
+ EXPECT_EQ(Merged.typesCount(), 2u); // int + struct foo
+
+ // Verify correctness.
+ EXPECT_EQ(Merged.findType(1)->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(Merged.findType(2)->getKind(), BTF::BTF_KIND_STRUCT);
+
+ auto *ST = Merged.findType(2);
+ auto *Members = reinterpret_cast<const BTF::BTFMember *>(
+ reinterpret_cast<const uint8_t *>(ST) + sizeof(BTF::CommonType));
+ EXPECT_EQ(Members[0].Type, 1u); // Points to deduped int
+}
+
+TEST(BTFDedupTest, duplicateFloats) {
+ BTFBuilder B;
+ uint32_t S1 = B.addString("float");
+ uint32_t S2 = B.addString("float");
+
+ B.addType({S1, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+ B.addType({S2, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+
+ EXPECT_EQ(B.typesCount(), 2u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+ EXPECT_EQ(B.findType(1)->getKind(), BTF::BTF_KIND_FLOAT);
+ EXPECT_EQ(B.findType(1)->Size, 4u);
+}
+
+TEST(BTFDedupTest, differentFloats) {
+ BTFBuilder B;
+ uint32_t S1 = B.addString("float");
+ uint32_t S2 = B.addString("double");
+
+ B.addType({S1, mkInfo(BTF::BTF_KIND_FLOAT), {4}});
+ B.addType({S2, mkInfo(BTF::BTF_KIND_FLOAT), {8}});
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, duplicateEnum64) {
+ BTFBuilder B;
+ uint32_t ES = B.addString("big_enum");
+ uint32_t VS = B.addString("VAL");
+
+ B.addType({ES, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}});
+ B.addTail(BTF::BTFEnum64({VS, 0xDEADBEEF, 0x12345678}));
+
+ uint32_t ES2 = B.addString("big_enum");
+ uint32_t VS2 = B.addString("VAL");
+ B.addType({ES2, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}});
+ B.addTail(BTF::BTFEnum64({VS2, 0xDEADBEEF, 0x12345678}));
+
+ EXPECT_EQ(B.typesCount(), 2u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+}
+
+TEST(BTFDedupTest, differentEnum64Values) {
+ BTFBuilder B;
+ uint32_t ES = B.addString("big_enum");
+ uint32_t VS = B.addString("VAL");
+
+ B.addType({ES, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}});
+ B.addTail(BTF::BTFEnum64({VS, 1, 0}));
+
+ uint32_t ES2 = B.addString("big_enum");
+ uint32_t VS2 = B.addString("VAL");
+ B.addType({ES2, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}});
+ B.addTail(BTF::BTFEnum64({VS2, 2, 0})); // Different value
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, duplicateTypedefChain) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t MyIntS = B.addString("myint");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: typedef myint -> int
+ B.addType({MyIntS, mkInfo(BTF::BTF_KIND_TYPEDEF), {1}});
+
+ // Duplicate set:
+ uint32_t IntS2 = B.addString("int");
+ uint32_t MyIntS2 = B.addString("myint");
+ // Type 3: int (dup)
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 4: typedef myint -> int (dup, refs type 3)
+ B.addType({MyIntS2, mkInfo(BTF::BTF_KIND_TYPEDEF), {3}});
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+
+ EXPECT_EQ(B.findType(1)->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(B.findType(2)->getKind(), BTF::BTF_KIND_TYPEDEF);
+ EXPECT_EQ(B.findType(2)->Type, 1u);
+}
+
+TEST(BTFDedupTest, duplicateVolatileConstRestrict) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: volatile int
+ B.addType({0, mkInfo(BTF::BTF_KIND_VOLATILE), {1}});
+ // Type 3: const int
+ B.addType({0, mkInfo(BTF::BTF_KIND_CONST), {1}});
+ // Type 4: restrict int
+ B.addType({0, mkInfo(BTF::BTF_KIND_RESTRICT), {1}});
+
+ // Duplicate set:
+ uint32_t IntS2 = B.addString("int");
+ // Type 5: int (dup)
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 6: volatile int (dup, refs 5)
+ B.addType({0, mkInfo(BTF::BTF_KIND_VOLATILE), {5}});
+ // Type 7: const int (dup, refs 5)
+ B.addType({0, mkInfo(BTF::BTF_KIND_CONST), {5}});
+ // Type 8: restrict int (dup, refs 5)
+ B.addType({0, mkInfo(BTF::BTF_KIND_RESTRICT), {5}});
+
+ EXPECT_EQ(B.typesCount(), 8u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // int + volatile + const + restrict = 4 types
+ EXPECT_EQ(B.typesCount(), 4u);
+}
+
+TEST(BTFDedupTest, duplicateArrays) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: int[10]
+ B.addType({0, mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+ B.addTail(BTF::BTFArray({1, 1, 10}));
+
+ // Duplicate set:
+ uint32_t IntS2 = B.addString("int");
+ // Type 3: int (dup)
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 4: int[10] (dup, refs type 3)
+ B.addType({0, mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+ B.addTail(BTF::BTFArray({3, 3, 10}));
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+
+ // Verify array references are remapped.
+ const BTF::CommonType *ArrT = B.findType(2);
+ ASSERT_TRUE(ArrT);
+ EXPECT_EQ(ArrT->getKind(), BTF::BTF_KIND_ARRAY);
+ auto *Arr = reinterpret_cast<const BTF::BTFArray *>(
+ reinterpret_cast<const uint8_t *>(ArrT) + sizeof(BTF::CommonType));
+ EXPECT_EQ(Arr->ElemType, 1u);
+ EXPECT_EQ(Arr->IndexType, 1u);
+ EXPECT_EQ(Arr->Nelems, 10u);
+}
+
+TEST(BTFDedupTest, differentArrayNelems) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // int[10]
+ B.addType({0, mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+ B.addTail(BTF::BTFArray({1, 1, 10}));
+ // int[20] — different nelems, should NOT dedup.
+ B.addType({0, mkInfo(BTF::BTF_KIND_ARRAY), {0}});
+ B.addTail(BTF::BTFArray({1, 1, 20}));
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, duplicateFuncAndFuncProto) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t XS = B.addString("x");
+ uint32_t FnS = B.addString("myfunc");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: func_proto(int x) -> int
+ B.addType({0, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {1}});
+ B.addTail(BTF::BTFParam({XS, 1}));
+ // Type 3: func "myfunc" -> func_proto
+ B.addType({FnS, mkInfo(BTF::BTF_KIND_FUNC), {2}});
+
+ // Duplicate:
+ uint32_t IntS2 = B.addString("int");
+ uint32_t XS2 = B.addString("x");
+ uint32_t FnS2 = B.addString("myfunc");
+ // Type 4: int (dup)
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 5: func_proto (dup, refs type 4)
+ B.addType({0, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {4}});
+ B.addTail(BTF::BTFParam({XS2, 4}));
+ // Type 6: func (dup, refs type 5)
+ B.addType({FnS2, mkInfo(BTF::BTF_KIND_FUNC), {5}});
+
+ EXPECT_EQ(B.typesCount(), 6u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, duplicateVar) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t VS = B.addString("myvar");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: VAR myvar -> int
+ B.addType({VS, mkInfo(BTF::BTF_KIND_VAR), {1}});
+ B.addTail((uint32_t)1); // global linkage
+
+ // Duplicate:
+ uint32_t IntS2 = B.addString("int");
+ uint32_t VS2 = B.addString("myvar");
+ // Type 3: int (dup)
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 4: VAR myvar (dup, refs type 3)
+ B.addType({VS2, mkInfo(BTF::BTF_KIND_VAR), {3}});
+ B.addTail((uint32_t)1);
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, duplicateTypeTag) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t TagS = B.addString("user");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: TYPE_TAG "user" -> int
+ B.addType({TagS, mkInfo(BTF::BTF_KIND_TYPE_TAG), {1}});
+
+ // Duplicate:
+ uint32_t IntS2 = B.addString("int");
+ uint32_t TagS2 = B.addString("user");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ B.addType({TagS2, mkInfo(BTF::BTF_KIND_TYPE_TAG), {3}});
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, duplicateDeclTag) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t TagS = B.addString("mytag");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: DECL_TAG "mytag" on type 1, component_idx=-1
+ B.addType({TagS, mkInfo(BTF::BTF_KIND_DECL_TAG), {1}});
+ B.addTail((uint32_t)-1);
+
+ // Duplicate:
+ uint32_t IntS2 = B.addString("int");
+ uint32_t TagS2 = B.addString("mytag");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ B.addType({TagS2, mkInfo(BTF::BTF_KIND_DECL_TAG), {3}});
+ B.addTail((uint32_t)-1);
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, differentDeclTagComponentIdx) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t TagS = B.addString("mytag");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: DECL_TAG on type 1, component_idx=0
+ B.addType({TagS, mkInfo(BTF::BTF_KIND_DECL_TAG), {1}});
+ B.addTail((uint32_t)0);
+ // Type 3: DECL_TAG on type 1, component_idx=1 — different, should NOT dedup
+ uint32_t TagS2 = B.addString("mytag");
+ B.addType({TagS2, mkInfo(BTF::BTF_KIND_DECL_TAG), {1}});
+ B.addTail((uint32_t)1);
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, structDifferentMemberOffsets) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t FooS = B.addString("foo");
+ uint32_t AS = B.addString("a");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 2: struct foo { int a; } at offset 0
+ B.addType({FooS, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B.addTail(BTF::BTFMember({AS, 1, 0}));
+
+ // Type 3: struct foo { int a; } at offset 32 — different offset
+ uint32_t FooS2 = B.addString("foo");
+ uint32_t AS2 = B.addString("a");
+ B.addType({FooS2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B.addTail(BTF::BTFMember({AS2, 1, 32})); // Different offset
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 3u); // int + 2 different structs
+}
+
+TEST(BTFDedupTest, structDifferentMemberNames) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t FooS = B.addString("foo");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 2: struct foo { int a; }
+ uint32_t AS = B.addString("a");
+ B.addType({FooS, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B.addTail(BTF::BTFMember({AS, 1, 0}));
+
+ // Type 3: struct foo { int b; } — different member name
+ uint32_t FooS2 = B.addString("foo");
+ uint32_t BAS = B.addString("b");
+ B.addType({FooS2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B.addTail(BTF::BTFMember({BAS, 1, 0}));
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, structDifferentMemberCount) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t FooS = B.addString("foo");
+ uint32_t AS = B.addString("a");
+ uint32_t BS = B.addString("b");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+
+ // Type 2: struct foo { int a; } — 1 member
+ B.addType({FooS, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}});
+ B.addTail(BTF::BTFMember({AS, 1, 0}));
+
+ // Type 3: struct foo { int a; int b; } — 2 members
+ uint32_t FooS2 = B.addString("foo");
+ uint32_t AS2 = B.addString("a");
+ uint32_t BS2 = B.addString("b");
+ B.addType({FooS2, mkInfo(BTF::BTF_KIND_STRUCT) | 2, {8}});
+ B.addTail(BTF::BTFMember({AS2, 1, 0}));
+ B.addTail(BTF::BTFMember({BS2, 1, 32}));
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, mutualRecursion) {
+ // struct A { struct B *b; };
+ // struct B { struct A *a; };
+ BTFBuilder B;
+ uint32_t AS = B.addString("A");
+ uint32_t BS = B.addString("B");
+ uint32_t MemA = B.addString("a");
+ uint32_t MemB = B.addString("b");
+
+ // Type 1: ptr to struct B (type 4)
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {4}});
+ // Type 2: ptr to struct A (type 3)
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {3}});
+ // Type 3: struct A { struct B *b; }
+ B.addType({AS, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {8}});
+ B.addTail(BTF::BTFMember({MemB, 1, 0}));
+ // Type 4: struct B { struct A *a; }
+ B.addType({BS, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {8}});
+ B.addTail(BTF::BTFMember({MemA, 2, 0}));
+
+ // Duplicate set:
+ uint32_t AS2 = B.addString("A");
+ uint32_t BS2 = B.addString("B");
+ uint32_t MemA2 = B.addString("a");
+ uint32_t MemB2 = B.addString("b");
+
+ // Type 5: ptr to struct B (type 8)
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {8}});
+ // Type 6: ptr to struct A (type 7)
+ B.addType({0, mkInfo(BTF::BTF_KIND_PTR), {7}});
+ // Type 7: struct A (dup)
+ B.addType({AS2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {8}});
+ B.addTail(BTF::BTFMember({MemB2, 5, 0}));
+ // Type 8: struct B (dup)
+ B.addType({BS2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {8}});
+ B.addTail(BTF::BTFMember({MemA2, 6, 0}));
+
+ EXPECT_EQ(B.typesCount(), 8u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // Should dedup to: ptr(B), ptr(A), struct A, struct B = 4 types
+ EXPECT_EQ(B.typesCount(), 4u);
+}
+
+TEST(BTFDedupTest, diamondDependency) {
+ // Two structs that share a base INT type.
+ // struct S1 { int x; }; struct S2 { int y; };
+ // Both reference the same int. After dedup from two CUs, we should get
+ // 1 int + 2 structs (they're different) = 3 types.
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t S1S = B.addString("S1");
+ uint32_t S2S = B.addString("S2");
+ uint32_t XS = B.addString("x");
+ uint32_t YS = B.addString("y");
+
+ // CU 1:
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}}); // 1
+ B.addTail((uint32_t)0);
+ B.addType({S1S, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // 2
+ B.addTail(BTF::BTFMember({XS, 1, 0}));
+ B.addType({S2S, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // 3
+ B.addTail(BTF::BTFMember({YS, 1, 0}));
+
+ // CU 2 (same types, different IDs):
+ uint32_t IntS2 = B.addString("int");
+ uint32_t S1S2 = B.addString("S1");
+ uint32_t S2S2 = B.addString("S2");
+ uint32_t XS2 = B.addString("x");
+ uint32_t YS2 = B.addString("y");
+
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}}); // 4
+ B.addTail((uint32_t)0);
+ B.addType({S1S2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // 5
+ B.addTail(BTF::BTFMember({XS2, 4, 0}));
+ B.addType({S2S2, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // 6
+ B.addTail(BTF::BTFMember({YS2, 4, 0}));
+
+ EXPECT_EQ(B.typesCount(), 6u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ // 1 int + 2 different structs = 3
+ EXPECT_EQ(B.typesCount(), 3u);
+}
+
+TEST(BTFDedupTest, manyDuplicateInts) {
+ BTFBuilder B;
+ const unsigned N = 100;
+ for (unsigned I = 0; I < N; ++I) {
+ uint32_t S = B.addString("int");
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ }
+
+ EXPECT_EQ(B.typesCount(), N);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+}
+
+TEST(BTFDedupTest, enumMembersPreservedAfterDedup) {
+ BTFBuilder B;
+ uint32_t ES = B.addString("color");
+ uint32_t RS = B.addString("RED");
+ uint32_t GS = B.addString("GREEN");
+ uint32_t BS2 = B.addString("BLUE");
+
+ B.addType({ES, mkInfo(BTF::BTF_KIND_ENUM) | 3, {4}});
+ B.addTail(BTF::BTFEnum({RS, 0}));
+ B.addTail(BTF::BTFEnum({GS, 1}));
+ B.addTail(BTF::BTFEnum({BS2, 2}));
+
+ // Duplicate:
+ uint32_t ES2 = B.addString("color");
+ uint32_t RS2 = B.addString("RED");
+ uint32_t GS2 = B.addString("GREEN");
+ uint32_t BS3 = B.addString("BLUE");
+ B.addType({ES2, mkInfo(BTF::BTF_KIND_ENUM) | 3, {4}});
+ B.addTail(BTF::BTFEnum({RS2, 0}));
+ B.addTail(BTF::BTFEnum({GS2, 1}));
+ B.addTail(BTF::BTFEnum({BS3, 2}));
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 1u);
+
+ // Verify enum members are intact.
+ const BTF::CommonType *T = B.findType(1);
+ ASSERT_TRUE(T);
+ EXPECT_EQ(T->getKind(), BTF::BTF_KIND_ENUM);
+ EXPECT_EQ(T->getVlen(), 3u);
+ auto *Vals = reinterpret_cast<const BTF::BTFEnum *>(
+ reinterpret_cast<const uint8_t *>(T) + sizeof(BTF::CommonType));
+ EXPECT_EQ(B.findString(Vals[0].NameOff), "RED");
+ EXPECT_EQ(Vals[0].Val, 0);
+ EXPECT_EQ(B.findString(Vals[1].NameOff), "GREEN");
+ EXPECT_EQ(Vals[1].Val, 1);
+ EXPECT_EQ(B.findString(Vals[2].NameOff), "BLUE");
+ EXPECT_EQ(Vals[2].Val, 2);
+}
+
+TEST(BTFDedupTest, allKindsDedup) {
+ // Build a BTF with one of each type kind, then duplicate all of them.
+ // After dedup, should end up with exactly 19 types.
+ auto BuildAllKinds = [](BTFBuilder &B, uint32_t BaseId) {
+ uint32_t S = B.addString("t");
+ uint32_t M = B.addString("m");
+ uint32_t IntId = BaseId + 1;
+ uint32_t FuncProtoId = BaseId + 12;
+ uint32_t VarId = BaseId + 14;
+
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}}); // +1
+ B.addTail((uint32_t)0);
+ B.addType({S, mkInfo(BTF::BTF_KIND_PTR), {IntId}}); // +2
+ B.addType({S, mkInfo(BTF::BTF_KIND_ARRAY), {0}}); // +3
+ B.addTail(BTF::BTFArray({IntId, IntId, 10}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // +4
+ B.addTail(BTF::BTFMember({M, IntId, 0}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_UNION) | 1, {4}}); // +5
+ B.addTail(BTF::BTFMember({M, IntId, 0}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM) | 1, {4}}); // +6
+ B.addTail(BTF::BTFEnum({M, 42}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_FWD), {0}}); // +7
+ B.addType({S, mkInfo(BTF::BTF_KIND_TYPEDEF), {IntId}}); // +8
+ B.addType({S, mkInfo(BTF::BTF_KIND_VOLATILE), {IntId}}); // +9
+ B.addType({S, mkInfo(BTF::BTF_KIND_CONST), {IntId}}); // +10
+ B.addType({S, mkInfo(BTF::BTF_KIND_RESTRICT), {IntId}}); // +11
+ B.addType({S, mkInfo(BTF::BTF_KIND_FUNC_PROTO) | 1, {IntId}}); // +12
+ B.addTail(BTF::BTFParam({M, IntId}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_FUNC), {FuncProtoId}}); // +13
+ B.addType({S, mkInfo(BTF::BTF_KIND_VAR), {IntId}}); // +14
+ B.addTail((uint32_t)0);
+ B.addType({S, mkInfo(BTF::BTF_KIND_DATASEC) | 1, {0}}); // +15
+ B.addTail(BTF::BTFDataSec({VarId, 0, 4}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_FLOAT), {4}}); // +16
+ B.addType({S, mkInfo(BTF::BTF_KIND_DECL_TAG), {IntId}}); // +17
+ B.addTail((uint32_t)-1);
+ B.addType({S, mkInfo(BTF::BTF_KIND_TYPE_TAG), {IntId}}); // +18
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM64) | 1, {8}}); // +19
+ B.addTail(BTF::BTFEnum64({M, 1, 0}));
+ };
+
+ BTFBuilder B;
+ BuildAllKinds(B, 0); // Types 1-19
+ BuildAllKinds(B, 19); // Types 20-38 (duplicates)
+
+ EXPECT_EQ(B.typesCount(), 38u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 19u);
+
+ // Verify all 19 type kinds survive.
+ for (uint32_t Id = 1; Id <= 19; ++Id) {
+ ASSERT_TRUE(B.findType(Id))
+ << "Type " << Id << " missing after dedup";
+ }
+}
+
+TEST(BTFDedupTest, unionDedup) {
+ BTFBuilder B;
+ uint32_t IntS = B.addString("int");
+ uint32_t US = B.addString("myunion");
+ uint32_t XS = B.addString("x");
+ uint32_t YS = B.addString("y");
+
+ // Type 1: int
+ B.addType({IntS, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ // Type 2: union myunion { int x; int y; }
+ B.addType({US, mkInfo(BTF::BTF_KIND_UNION) | 2, {4}});
+ B.addTail(BTF::BTFMember({XS, 1, 0}));
+ B.addTail(BTF::BTFMember({YS, 1, 0}));
+
+ // Duplicate:
+ uint32_t IntS2 = B.addString("int");
+ uint32_t US2 = B.addString("myunion");
+ uint32_t XS2 = B.addString("x");
+ uint32_t YS2 = B.addString("y");
+ B.addType({IntS2, mkInfo(BTF::BTF_KIND_INT), {4}});
+ B.addTail((uint32_t)0);
+ B.addType({US2, mkInfo(BTF::BTF_KIND_UNION) | 2, {4}});
+ B.addTail(BTF::BTFMember({XS2, 3, 0}));
+ B.addTail(BTF::BTFMember({YS2, 3, 0}));
+
+ EXPECT_EQ(B.typesCount(), 4u);
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 2u);
+}
+
+TEST(BTFDedupTest, dedupRoundtripAllKinds) {
+ // Build, dedup, write, parse — verify the output is valid BTF.
+ BTFBuilder B;
+ uint32_t S = B.addString("t");
+ uint32_t M = B.addString("m");
+
+ B.addType({S, mkInfo(BTF::BTF_KIND_INT), {4}}); // 1
+ B.addTail((uint32_t)0);
+ B.addType({S, mkInfo(BTF::BTF_KIND_PTR), {1}}); // 2
+ B.addType({S, mkInfo(BTF::BTF_KIND_STRUCT) | 1, {4}}); // 3
+ B.addTail(BTF::BTFMember({M, 1, 0}));
+ B.addType({S, mkInfo(BTF::BTF_KIND_ENUM) | 1, {4}}); // 4
+ B.addTail(BTF::BTFEnum({M, 99}));
+ B.addType({0, mkInfo(BTF::BTF_KIND_CONST), {1}}); // 5
+
+ // Duplicate int + ptr:
+ uint32_t S2 = B.addString("t");
+ B.addType({S2, mkInfo(BTF::BTF_KIND_INT), {4}}); // 6
+ B.addTail((uint32_t)0);
+ B.addType({S2, mkInfo(BTF::BTF_KIND_PTR), {6}}); // 7
+
+ ASSERT_SUCCEEDED(BTF::dedup(B));
+ EXPECT_EQ(B.typesCount(), 5u);
+
+ SmallVector<uint8_t, 0> Output;
+ B.write(Output, !sys::IsBigEndianHost);
+
+ SmallString<0> Storage;
+ auto Obj = makeELFWithBTF(Output, Storage);
+ ASSERT_TRUE(Obj);
+
+ BTFParser Parser;
+ BTFParser::ParseOptions Opts;
+ Opts.LoadTypes = true;
+ ASSERT_SUCCEEDED(Parser.parse(*Obj, Opts));
+ EXPECT_EQ(Parser.typesCount(), 6u); // 5 types + void
+
+ EXPECT_EQ(Parser.findType(1)->getKind(), BTF::BTF_KIND_INT);
+ EXPECT_EQ(Parser.findType(2)->getKind(), BTF::BTF_KIND_PTR);
+ EXPECT_EQ(Parser.findType(2)->Type, 1u);
+ EXPECT_EQ(Parser.findType(3)->getKind(), BTF::BTF_KIND_STRUCT);
+ EXPECT_EQ(Parser.findType(4)->getKind(), BTF::BTF_KIND_ENUM);
+ EXPECT_EQ(Parser.findType(5)->getKind(), BTF::BTF_KIND_CONST);
+ EXPECT_EQ(Parser.findType(5)->Type, 1u);
+}
+
+} // namespace
diff --git a/llvm/unittests/DebugInfo/BTF/CMakeLists.txt b/llvm/unittests/DebugInfo/BTF/CMakeLists.txt
index 6f7f684c58bed..6f745f87166d5 100644
--- a/llvm/unittests/DebugInfo/BTF/CMakeLists.txt
+++ b/llvm/unittests/DebugInfo/BTF/CMakeLists.txt
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
)
add_llvm_unittest(DebugInfoBTFTests
+ BTFBuilderTest.cpp
+ BTFDedupTest.cpp
BTFParserTest.cpp
)
More information about the llvm-commits
mailing list