[lld] [llvm] [PseudoProbe] Include function hash in descriptor COMDAT key (PR #190296)
Haohai Wen via llvm-commits
llvm-commits at lists.llvm.org
Fri May 1 06:28:08 PDT 2026
https://github.com/HaohaiWen updated https://github.com/llvm/llvm-project/pull/190296
>From 1cf05a803be52ae467c2be835bf23488180694f9 Mon Sep 17 00:00:00 2001
From: "Wen, Haohai" <haohai.wen at intel.com>
Date: Wed, 1 Apr 2026 13:12:59 +0800
Subject: [PATCH 1/3] [PseudoProbe] Include function hash in descriptor COMDAT
key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The .pseudo_probe_desc section uses COMDAT to deduplicate descriptors
for the same function across translation units. On COFF, the COMDAT key
is uniquely determined by the function name. The COMDAT selection type
is EXACT_MATCH, which requires byte-identical content. This holds for
applications that strictly follow C/C++ ODR rules.
Unfortunately, we consistently observe .pseudo_probe_desc COMDAT
duplicate symbol errors on Windows (see also #177540). Most of them are
due to hash mismatches, meaning two non-internal functions with the same
name but different bodies — a violation of ODR rules. Some of these
functions are generated by the compiler (e.g., TU-local optimizations
that alter the CFG of a linkonce_odr function), and some are caused by
source code issues (e.g., different preprocessor settings or
optimization pragmas across TUs).
It is hard to fix all of them, but they seriously affect the user
experience of using pseudo probe on Windows due to the endless COFF
COMDAT symbol duplication errors. Users have to resort to
/force:multiple, which may hide other issues.
This patch includes the function hash in the COMDAT key for
.pseudo_probe_desc sections on both ELF and COFF. Descriptors with
different hashes now get separate COMDAT sections to avoiding linker
errors.
To help users identify functions affected by cross-TU inconsistencies,
MCPseudoProbe now detects duplicate GUIDs with mismatching hashes
during pseudo probe decoding. A summary count is printed by default;
users can also use llvm-profgen's --show-detailed-warning for
per-function details.
---
llvm/include/llvm/MC/MCObjectFileInfo.h | 3 +-
llvm/include/llvm/MC/MCPseudoProbe.h | 3 +-
llvm/lib/MC/MCObjectFileInfo.cpp | 24 +++--
llvm/lib/MC/MCPseudoProbe.cpp | 25 ++++-
llvm/lib/Target/TargetLoweringObjectFile.cpp | 3 +-
.../SampleProfile/pseudo-probe-emit-inline.ll | 24 ++---
.../SampleProfile/pseudo-probe-emit.ll | 28 ++---
.../pseudo-probe-desc-mismatch.test | 101 ++++++++++++++++++
llvm/tools/llvm-profgen/ProfiledBinary.cpp | 2 +-
9 files changed, 172 insertions(+), 41 deletions(-)
create mode 100644 llvm/test/tools/llvm-profgen/pseudo-probe-desc-mismatch.test
diff --git a/llvm/include/llvm/MC/MCObjectFileInfo.h b/llvm/include/llvm/MC/MCObjectFileInfo.h
index 51b7d73d46036..379c4e9094531 100644
--- a/llvm/include/llvm/MC/MCObjectFileInfo.h
+++ b/llvm/include/llvm/MC/MCObjectFileInfo.h
@@ -373,7 +373,8 @@ class LLVM_ABI MCObjectFileInfo {
MCSection *getPseudoProbeSection(const MCSection &TextSec) const;
- MCSection *getPseudoProbeDescSection(StringRef FuncName) const;
+ MCSection *getPseudoProbeDescSection(StringRef FuncName,
+ uint64_t FuncHash) const;
MCSection *getLLVMStatsSection() const;
diff --git a/llvm/include/llvm/MC/MCPseudoProbe.h b/llvm/include/llvm/MC/MCPseudoProbe.h
index 3799d2d84631d..a4b27e730399d 100644
--- a/llvm/include/llvm/MC/MCPseudoProbe.h
+++ b/llvm/include/llvm/MC/MCPseudoProbe.h
@@ -445,7 +445,8 @@ class MCPseudoProbeDecoder {
// If pseudo_probe_desc section is mapped to memory and \p IsMMapped is true,
// uses StringRefs pointing to the section.
LLVM_ABI bool buildGUID2FuncDescMap(const uint8_t *Start, std::size_t Size,
- bool IsMMapped = false);
+ bool IsMMapped = false,
+ bool VerboseWarnings = false);
// Decode pseudo_probe section to count the number of probes and inlined
// function records for each function record.
diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp
index 56a22241ae5d3..a20891cf17831 100644
--- a/llvm/lib/MC/MCObjectFileInfo.cpp
+++ b/llvm/lib/MC/MCObjectFileInfo.cpp
@@ -1339,7 +1339,8 @@ MCObjectFileInfo::getPseudoProbeSection(const MCSection &TextSec) const {
}
MCSection *
-MCObjectFileInfo::getPseudoProbeDescSection(StringRef FuncName) const {
+MCObjectFileInfo::getPseudoProbeDescSection(StringRef FuncName,
+ uint64_t FuncHash) const {
if (!Ctx->getTargetTriple().supportsCOMDAT() || FuncName.empty())
return PseudoProbeDescSection;
@@ -1347,23 +1348,28 @@ MCObjectFileInfo::getPseudoProbeDescSection(StringRef FuncName) const {
// for the linker to deduplicate. The duplication, must be from different
// tranlation unit, can come from:
// 1. Inline functions defined in header files;
- // 2. ThinLTO imported funcions;
+ // 2. ThinLTO imported functions;
// 3. Weak-linkage definitions.
- // Use a concatenation of the section name and the function name as the
- // group name so that descriptor-only groups won't be folded with groups of
- // code.
+ // Use a concatenation of the section name, function name, and function hash
+ // as the group name so that descriptors with different hashes (due to user
+ // code not following ODR or compiler codegen inconsistencies) get separate
+ // COMDAT sections instead of being silently dropped (ELF) or causing linker
+ // errors (COFF). Duplicate GUIDs with mismatching hashes are detected
+ // during descriptor decoding and reported by llvm-profgen.
+ std::string HashSuffix = "." + Twine::utohexstr(FuncHash).str();
auto ObjFileType = Ctx->getObjectFileType();
if (ObjFileType == MCContext::IsELF) {
auto *S = static_cast<MCSectionELF *>(PseudoProbeDescSection);
auto Flags = S->getFlags() | ELF::SHF_GROUP;
- return Ctx->getELFSection(S->getName(), S->getType(), Flags,
- S->getEntrySize(), S->getName() + "_" + FuncName,
- /*IsComdat=*/true);
+ return Ctx->getELFSection(
+ S->getName(), S->getType(), Flags, S->getEntrySize(),
+ S->getName() + "_" + FuncName + HashSuffix, /*IsComdat=*/true);
} else if (ObjFileType == MCContext::IsCOFF) {
auto *S = static_cast<MCSectionCOFF *>(PseudoProbeDescSection);
unsigned Characteristics =
S->getCharacteristics() | COFF::IMAGE_SCN_LNK_COMDAT;
- std::string COMDATSymName = (S->getName() + "_" + FuncName).str();
+ std::string COMDATSymName =
+ (S->getName() + "_" + FuncName + HashSuffix).str();
return Ctx->getCOFFSection(S->getName(), Characteristics, COMDATSymName,
COFF::IMAGE_COMDAT_SELECT_EXACT_MATCH);
}
diff --git a/llvm/lib/MC/MCPseudoProbe.cpp b/llvm/lib/MC/MCPseudoProbe.cpp
index 11e42118a29ef..4f637e678ce79 100644
--- a/llvm/lib/MC/MCPseudoProbe.cpp
+++ b/llvm/lib/MC/MCPseudoProbe.cpp
@@ -20,6 +20,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/MD5.h"
+#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
@@ -374,7 +375,8 @@ ErrorOr<StringRef> MCPseudoProbeDecoder::readString(uint32_t Size) {
bool MCPseudoProbeDecoder::buildGUID2FuncDescMap(const uint8_t *Start,
std::size_t Size,
- bool IsMMapped) {
+ bool IsMMapped,
+ bool VerboseWarnings) {
// The pseudo_probe_desc section has a format like:
// .section .pseudo_probe_desc,"", at progbits
// .quad -5182264717993193164 // GUID
@@ -427,9 +429,28 @@ bool MCPseudoProbeDecoder::buildGUID2FuncDescMap(const uint8_t *Start,
assert(Data == End && "Have unprocessed data in pseudo_probe_desc section");
assert(GUID2FuncDescMap.size() == FuncDescCount &&
"Mismatching function description count pre- and post-parsing");
- llvm::sort(GUID2FuncDescMap, [](const auto &LHS, const auto &RHS) {
+ llvm::stable_sort(GUID2FuncDescMap, [](const auto &LHS, const auto &RHS) {
return LHS.FuncGUID < RHS.FuncGUID;
});
+
+ // Detect duplicate GUIDs with different hashes across TUs.
+ uint32_t MismatchCount = 0;
+ for (size_t I = 1; I < GUID2FuncDescMap.size(); ++I) {
+ const auto &Prev = GUID2FuncDescMap[I - 1];
+ const auto &Curr = GUID2FuncDescMap[I];
+ if (Prev.FuncGUID == Curr.FuncGUID && Prev.FuncHash != Curr.FuncHash) {
+ ++MismatchCount;
+ if (VerboseWarnings)
+ WithColor::warning() << "pseudo probe descriptor for " << Prev.FuncName
+ << " has mismatching hash across TUs: "
+ << format_hex(Prev.FuncHash, 18) << " vs "
+ << format_hex(Curr.FuncHash, 18) << "\n";
+ }
+ }
+ if (MismatchCount > 0)
+ WithColor::warning() << MismatchCount
+ << " functions have mismatching pseudo probe "
+ "descriptors across translation units.\n";
return true;
}
diff --git a/llvm/lib/Target/TargetLoweringObjectFile.cpp b/llvm/lib/Target/TargetLoweringObjectFile.cpp
index 83acd956b7a7b..5f8704a6a3e1c 100644
--- a/llvm/lib/Target/TargetLoweringObjectFile.cpp
+++ b/llvm/lib/Target/TargetLoweringObjectFile.cpp
@@ -212,7 +212,8 @@ void TargetLoweringObjectFile::emitPseudoProbeDescMetadata(
auto *Hash = mdconst::extract<ConstantInt>(MD->getOperand(1));
auto *Name = cast<MDString>(MD->getOperand(2));
auto *S = C.getObjectFileInfo()->getPseudoProbeDescSection(
- TM->getFunctionSections() ? Name->getString() : StringRef());
+ TM->getFunctionSections() ? Name->getString() : StringRef(),
+ Hash->getZExtValue());
Streamer.switchSection(S);
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-emit-inline.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-emit-inline.ll
index 3575ad0b26e57..6403fe92bddc1 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-emit-inline.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-emit-inline.ll
@@ -64,38 +64,38 @@ define dso_local i32 @entry() !dbg !14 {
; Check the generation of .pseudo_probe_desc section
-; CHECK-ASM-ELF: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo2,comdat
+; CHECK-ASM-ELF: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo2.{{[0-9a-f]+}},comdat
; CHECK-ASM-ELF-NEXT: .quad [[#GUID1]]
; CHECK-ASM-ELF-NEXT: .quad [[#HASH1:]]
; CHECK-ASM-ELF-NEXT: .byte 4
; CHECK-ASM-ELF-NEXT: .ascii "foo2"
-; CHECK-ASM-ELF-NEXT: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo,comdat
+; CHECK-ASM-ELF-NEXT: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo.{{[0-9a-f]+}},comdat
; CHECK-ASM-ELF-NEXT: .quad [[#GUID2]]
; CHECK-ASM-ELF-NEXT: .quad [[#HASH2:]]
; CHECK-ASM-ELF-NEXT: .byte 3
; CHECK-ASM-ELF-NEXT: .ascii "foo"
-; CHECK-ASM-ELF-NEXT: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_entry,comdat
+; CHECK-ASM-ELF-NEXT: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_entry.{{[0-9a-f]+}},comdat
; CHECK-ASM-ELF-NEXT: .quad [[#GUID3]]
; CHECK-ASM-ELF-NEXT: .quad [[#HASH3:]]
; CHECK-ASM-ELF-NEXT: .byte 5
; CHECK-ASM-ELF-NEXT: .ascii "entry"
-; CHECK-ASM-COFF: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo2
-; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo2
-; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo2:
+; CHECK-ASM-COFF: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo2.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo2.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo2.{{[0-9a-f]+}}:
; CHECK-ASM-COFF-NEXT: .quad [[#GUID1]]
; CHECK-ASM-COFF-NEXT: .quad [[#HASH1:]]
; CHECK-ASM-COFF-NEXT: .byte 4
; CHECK-ASM-COFF-NEXT: .ascii "foo2"
-; CHECK-ASM-COFF-NEXT: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo
-; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo
-; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo:
+; CHECK-ASM-COFF-NEXT: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo.{{[0-9a-f]+}}:
; CHECK-ASM-COFF-NEXT: .quad [[#GUID2]]
; CHECK-ASM-COFF-NEXT: .quad [[#HASH2:]]
; CHECK-ASM-COFF-NEXT: .byte 3
; CHECK-ASM-COFF-NEXT: .ascii "foo"
-; CHECK-ASM-COFF-NEXT: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_entry
-; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_entry
-; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_entry:
+; CHECK-ASM-COFF-NEXT: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_entry.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_entry.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_entry.{{[0-9a-f]+}}:
; CHECK-ASM-COFF-NEXT: .quad [[#GUID3]]
; CHECK-ASM-COFF-NEXT: .quad [[#HASH3:]]
; CHECK-ASM-COFF-NEXT: .byte 5
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-emit.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-emit.ll
index 8306ddcc6a91d..a494ff14fbf29 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-emit.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-emit.ll
@@ -101,26 +101,26 @@ entry:
; CHECK-IL: ![[#SCOPE1]] = !DILexicalBlockFile(scope: ![[#]], file: ![[#]], discriminator: 455082015)
; Check the generation of .pseudo_probe_desc section
-; CHECK-ASM-ELF: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo,comdat
+; CHECK-ASM-ELF: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo.{{[0-9a-f]+}},comdat
; CHECK-ASM-ELF-NEXT: .quad [[#GUID]]
; CHECK-ASM-ELF-NEXT: .quad [[#HASH:]]
; CHECK-ASM-ELF-NEXT: .byte 3
; CHECK-ASM-ELF-NEXT: .ascii "foo"
-; CHECK-ASM-ELF-NEXT: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo2,comdat
+; CHECK-ASM-ELF-NEXT: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo2.{{[0-9a-f]+}},comdat
; CHECK-ASM-ELF-NEXT: .quad [[#GUID2]]
; CHECK-ASM-ELF-NEXT: .quad [[#HASH2:]]
; CHECK-ASM-ELF-NEXT: .byte 4
; CHECK-ASM-ELF-NEXT: .ascii "foo2"
-; CHECK-ASM-COFF: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo
-; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo
-; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo:
+; CHECK-ASM-COFF: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo.{{[0-9a-f]+}}:
; CHECK-ASM-COFF-NEXT: .quad [[#GUID]]
; CHECK-ASM-COFF-NEXT: .quad [[#HASH:]]
; CHECK-ASM-COFF-NEXT: .byte 3
; CHECK-ASM-COFF-NEXT: .ascii "foo"
-; CHECK-ASM-COFF-NEXT: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo2
-; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo2
-; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo2:
+; CHECK-ASM-COFF-NEXT: .section .pseudo_probe_desc,"drD",same_contents,.pseudo_probe_desc_foo2.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .globl .pseudo_probe_desc_foo2.{{[0-9a-f]+}}
+; CHECK-ASM-COFF-NEXT: .pseudo_probe_desc_foo2.{{[0-9a-f]+}}:
; CHECK-ASM-COFF-NEXT: .quad [[#GUID2]]
; CHECK-ASM-COFF-NEXT: .quad [[#HASH2:]]
; CHECK-ASM-COFF-NEXT: .byte 4
@@ -141,15 +141,15 @@ entry:
; CHECK-SEC-ELF-NEXT: [ 8] .text.foo3
; CHECK-SEC-ELF-NEXT: [ 21] .pseudo_probe
; CHECK-SEC-ELF-EMPTY:
-; CHECK-SEC-ELF-NEXT: COMDAT group section [ 10] `.group' [.pseudo_probe_desc_foo] contains 1 sections:
+; CHECK-SEC-ELF-NEXT: COMDAT group section [ 10] `.group' [.pseudo_probe_desc_foo.{{[0-9a-f]+}}] contains 1 sections:
; CHECK-SEC-ELF-NEXT: [Index] Name
; CHECK-SEC-ELF-NEXT: [ 11] .pseudo_probe_desc
; CHECK-SEC-ELF-EMPTY:
-; CHECK-SEC-ELF-NEXT: COMDAT group section [ 12] `.group' [.pseudo_probe_desc_foo2] contains 1 sections:
+; CHECK-SEC-ELF-NEXT: COMDAT group section [ 12] `.group' [.pseudo_probe_desc_foo2.{{[0-9a-f]+}}] contains 1 sections:
; CHECK-SEC-ELF-NEXT: [Index] Name
; CHECK-SEC-ELF-NEXT: [ 13] .pseudo_probe_desc
; CHECK-SEC-ELF-EMPTY:
-; CHECK-SEC-ELF-NEXT: COMDAT group section [ 14] `.group' [.pseudo_probe_desc_foo3] contains 1 sections:
+; CHECK-SEC-ELF-NEXT: COMDAT group section [ 14] `.group' [.pseudo_probe_desc_foo3.{{[0-9a-f]+}}] contains 1 sections:
; CHECK-SEC-ELF-NEXT: [Index] Name
; CHECK-SEC-ELF-NEXT: [ 15] .pseudo_probe_desc
@@ -223,15 +223,15 @@ entry:
; CHECK-SEC-COFF: Name: foo3
; CHECK-SEC-COFF: Section: .text (6)
; CHECK-SEC-COFF: }
-; CHECK-SEC-COFF: Name: .pseudo_probe_desc_foo
+; CHECK-SEC-COFF: Name: .pseudo_probe_desc_foo.{{[0-9a-f]+}}
; CHECK-SEC-COFF: Section: .pseudo_probe_desc (7)
; CHECK-SEC-COFF: StorageClass: Static
; CHECK-SEC-COFF: }
-; CHECK-SEC-COFF: Name: .pseudo_probe_desc_foo2
+; CHECK-SEC-COFF: Name: .pseudo_probe_desc_foo2.{{[0-9a-f]+}}
; CHECK-SEC-COFF: Section: .pseudo_probe_desc (8)
; CHECK-SEC-COFF: StorageClass: Static
; CHECK-SEC-COFF: }
-; CHECK-SEC-COFF: Name: .pseudo_probe_desc_foo3
+; CHECK-SEC-COFF: Name: .pseudo_probe_desc_foo3.{{[0-9a-f]+}}
; CHECK-SEC-COFF: Section: .pseudo_probe_desc (9)
; CHECK-SEC-COFF: StorageClass: Static
; CHECK-SEC-COFF: }
diff --git a/llvm/test/tools/llvm-profgen/pseudo-probe-desc-mismatch.test b/llvm/test/tools/llvm-profgen/pseudo-probe-desc-mismatch.test
new file mode 100644
index 0000000000000..49d2935e03111
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/pseudo-probe-desc-mismatch.test
@@ -0,0 +1,101 @@
+; Test that llvm-profgen detects and reports mismatching pseudo probe descriptor
+; hashes for the same function across translation units.
+
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+
+;; ELF
+; RUN: yaml2obj %t/elf.yaml -o %t/test.elf
+; RUN: llvm-profgen --show-disassembly-only --binary=%t/test.elf --output=%t/test.prof \
+; RUN: 2>&1 | FileCheck %s --check-prefix=SUMMARY
+; RUN: llvm-profgen --show-disassembly-only --show-detailed-warning \
+; RUN: --binary=%t/test.elf --output=%t/test.prof 2>&1 | FileCheck %s --check-prefix=DETAIL
+
+;; COFF
+; RUN: yaml2obj %t/coff.yaml -o %t/test.exe
+; RUN: llvm-profgen --show-disassembly-only --binary=%t/test.exe --output=%t/test-coff.prof \
+; RUN: 2>&1 | FileCheck %s --check-prefix=SUMMARY
+; RUN: llvm-profgen --show-disassembly-only --show-detailed-warning \
+; RUN: --binary=%t/test.exe --output=%t/test-coff.prof 2>&1 | FileCheck %s --check-prefix=DETAIL
+
+; SUMMARY: warning: 1 functions have mismatching pseudo probe descriptors across translation units.
+; SUMMARY-NOT: warning: pseudo probe descriptor for foo has mismatching hash
+
+; DETAIL: warning: pseudo probe descriptor for foo has mismatching hash across TUs: {{0x[0-9a-f]+}} vs {{0x[0-9a-f]+}}
+; DETAIL: warning: 1 functions have mismatching pseudo probe descriptors across translation units.
+
+;--- elf.yaml
+## Minimal ELF executable with .pseudo_probe_desc containing two entries for
+## the same function GUID (0x1234567890ABCDEF) with different hashes
+## (0xDEADBEEF vs 0xCAFEBABE).
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_X86_64
+ Entry: 0x400000
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [PF_X, PF_R]
+ VAddr: 0x400000
+ FirstSec: .text
+ LastSec: .pseudo_probe
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [SHF_ALLOC, SHF_EXECINSTR]
+ Address: 0x400000
+ Content: C3
+ - Name: .pseudo_probe_desc
+ Type: SHT_PROGBITS
+ Flags: [SHF_ALLOC]
+ ## Two descriptors: same GUID=0x1234567890ABCDEF, different hashes.
+ ## Entry 1: GUID=0x1234567890ABCDEF, Hash=0xDEADBEEF, Name="foo"
+ ## Entry 2: GUID=0x1234567890ABCDEF, Hash=0xCAFEBABE, Name="foo"
+ Content: EFCDAB9078563412EFBEADDE0000000003666F6FEFCDAB9078563412BEBAFECA0000000003666F6F
+ - Name: .pseudo_probe
+ Type: SHT_PROGBITS
+ Flags: [SHF_ALLOC]
+ Content: ''
+
+;--- coff.yaml
+## Minimal PE/COFF executable with the same .pseudo_probe_desc content.
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 0x1000
+ ImageBase: 0x140000000
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: []
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE]
+sections:
+ - Name: .text
+ Characteristics: [IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ]
+ VirtualAddress: 0x1000
+ VirtualSize: 1
+ SectionData: C3
+ - Name: .pseudo_probe_desc
+ Characteristics: [IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ]
+ VirtualAddress: 0x2000
+ VirtualSize: 40
+ SectionData: EFCDAB9078563412EFBEADDE0000000003666F6FEFCDAB9078563412BEBAFECA0000000003666F6F
+ - Name: .pseudo_probe
+ Characteristics: [IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ]
+ VirtualAddress: 0x3000
+ VirtualSize: 0
+ SectionData: ''
+symbols: []
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
index 915e991e4068c..2d2d787cf00d4 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.cpp
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
@@ -513,7 +513,7 @@ void ProfiledBinary::decodePseudoProbe(const ObjectFile *Obj) {
StringRef Contents = unwrapOrError(Section.getContents(), FileName);
if (!ProbeDecoder.buildGUID2FuncDescMap(
reinterpret_cast<const uint8_t *>(Contents.data()),
- Contents.size()))
+ Contents.size(), /*IsMMapped=*/false, ShowDetailedWarning))
exitWithError(
"Pseudo Probe decoder fail in .pseudo_probe_desc section");
} else if (SectionName == ".pseudo_probe") {
>From a53ad6367ce50a7a8b6b1aec445f8905f6113852 Mon Sep 17 00:00:00 2001
From: "Wen, Haohai" <haohai.wen at intel.com>
Date: Fri, 3 Apr 2026 10:19:30 +0800
Subject: [PATCH 2/3] Address review feedback: fix typo and count unique
mismatching functions
- Fix pre-existing typo "tranlation" -> "translation" in comment
- Track LastMismatchGUID to count unique functions with mismatching
descriptors instead of counting each adjacent pair
---
lld/test/ELF/lto/pseudo-probe-lto.ll | 2 +-
llvm/lib/MC/MCObjectFileInfo.cpp | 2 +-
llvm/lib/MC/MCPseudoProbe.cpp | 6 +++++-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/lld/test/ELF/lto/pseudo-probe-lto.ll b/lld/test/ELF/lto/pseudo-probe-lto.ll
index cabd69da33fe2..de8295a1df712 100644
--- a/lld/test/ELF/lto/pseudo-probe-lto.ll
+++ b/lld/test/ELF/lto/pseudo-probe-lto.ll
@@ -19,7 +19,7 @@ entry:
ret void
}
-; CHECK: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo,comdat
+; CHECK: .section .pseudo_probe_desc,"G", at progbits,.pseudo_probe_desc_foo.{{[0-9a-f]+}},comdat
; CHECK-NEXT: .quad [[#GUID]]
; CHECK-NEXT: .quad [[#HASH:]]
; CHECK-NEXT: .byte 3
diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp
index a20891cf17831..7016fd6c7c6f1 100644
--- a/llvm/lib/MC/MCObjectFileInfo.cpp
+++ b/llvm/lib/MC/MCObjectFileInfo.cpp
@@ -1346,7 +1346,7 @@ MCObjectFileInfo::getPseudoProbeDescSection(StringRef FuncName,
// Create a separate comdat group for each function's descriptor in order
// for the linker to deduplicate. The duplication, must be from different
- // tranlation unit, can come from:
+ // translation unit, can come from:
// 1. Inline functions defined in header files;
// 2. ThinLTO imported functions;
// 3. Weak-linkage definitions.
diff --git a/llvm/lib/MC/MCPseudoProbe.cpp b/llvm/lib/MC/MCPseudoProbe.cpp
index 4f637e678ce79..99cba8e3d8375 100644
--- a/llvm/lib/MC/MCPseudoProbe.cpp
+++ b/llvm/lib/MC/MCPseudoProbe.cpp
@@ -435,11 +435,15 @@ bool MCPseudoProbeDecoder::buildGUID2FuncDescMap(const uint8_t *Start,
// Detect duplicate GUIDs with different hashes across TUs.
uint32_t MismatchCount = 0;
+ uint64_t LastMismatchGUID = 0;
for (size_t I = 1; I < GUID2FuncDescMap.size(); ++I) {
const auto &Prev = GUID2FuncDescMap[I - 1];
const auto &Curr = GUID2FuncDescMap[I];
if (Prev.FuncGUID == Curr.FuncGUID && Prev.FuncHash != Curr.FuncHash) {
- ++MismatchCount;
+ if (LastMismatchGUID != Curr.FuncGUID) {
+ ++MismatchCount;
+ LastMismatchGUID = Curr.FuncGUID;
+ }
if (VerboseWarnings)
WithColor::warning() << "pseudo probe descriptor for " << Prev.FuncName
<< " has mismatching hash across TUs: "
>From 9b245f766940f67cea47001aafa7b636af4648f8 Mon Sep 17 00:00:00 2001
From: Haohai Wen <haohai.wen at intel.com>
Date: Fri, 1 May 2026 21:27:04 +0800
Subject: [PATCH 3/3] Address comments
---
llvm/lib/MC/MCObjectFileInfo.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp
index 74f30e8ff7b46..74cf7068251de 100644
--- a/llvm/lib/MC/MCObjectFileInfo.cpp
+++ b/llvm/lib/MC/MCObjectFileInfo.cpp
@@ -1375,20 +1375,21 @@ MCObjectFileInfo::getPseudoProbeDescSection(StringRef FuncName,
// COMDAT sections instead of being silently dropped (ELF) or causing linker
// errors (COFF). Duplicate GUIDs with mismatching hashes are detected
// during descriptor decoding and reported by llvm-profgen.
- std::string HashSuffix = "." + Twine::utohexstr(FuncHash).str();
auto ObjFileType = Ctx->getObjectFileType();
if (ObjFileType == MCContext::IsELF) {
auto *S = static_cast<MCSectionELF *>(PseudoProbeDescSection);
auto Flags = S->getFlags() | ELF::SHF_GROUP;
return Ctx->getELFSection(
S->getName(), S->getType(), Flags, S->getEntrySize(),
- S->getName() + "_" + FuncName + HashSuffix, /*IsComdat=*/true);
+ S->getName() + "_" + FuncName + "." + Twine::utohexstr(FuncHash),
+ /*IsComdat=*/true);
} else if (ObjFileType == MCContext::IsCOFF) {
auto *S = static_cast<MCSectionCOFF *>(PseudoProbeDescSection);
unsigned Characteristics =
S->getCharacteristics() | COFF::IMAGE_SCN_LNK_COMDAT;
std::string COMDATSymName =
- (S->getName() + "_" + FuncName + HashSuffix).str();
+ (S->getName() + "_" + FuncName + "." + Twine::utohexstr(FuncHash))
+ .str();
return Ctx->getCOFFSection(S->getName(), Characteristics, COMDATSymName,
COFF::IMAGE_COMDAT_SELECT_EXACT_MATCH);
}
More information about the llvm-commits
mailing list