[llvm] [CSSPGO] [Pseudo-Probe] Avoid infinite loop on cyclic invoke destinations (PR #221630)
chandan singh via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 15 02:07:04 PDT 2026
https://github.com/chandankds updated https://github.com/llvm/llvm-project/pull/221630
>From b9ab072e3df296425824b52431cc244bc996a476 Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Mon, 7 Sep 2026 04:32:13 +0000
Subject: [PATCH 1/6] [CSSPGO] [Pseudo-Probe] Avoid infinite loop on cyclic
invoke destinations
SampleProfileProber::getOriginalTerminator() walks invoke normal destinations (and ignored single-successor blocks) so the CFG hash stays stable across call-to-invoke conversion. That walk assumes the chain is acyclic.
Valid IR can have a self-looping invoke (normal dest == the invoke block). The walk then never returns, and opt -passes=pseudo-probe hangs.
Example Testcase:
void f1();
void test() {
try {
for (;;)
f1();
} catch (...) {}
}
This patch walk iteratively and record visited blocks. If the next block was already seen, stop at the terminator that closes the cycle. Acyclic invoke continuation chains are unchanged.
---
.../lib/Transforms/IPO/SampleProfileProbe.cpp | 32 ++++++++++++++-----
.../pseudo-probe-cyclic-normal-dest.ll | 32 +++++++++++++++++++
2 files changed, 56 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
diff --git a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
index a62d76010f678..7e69fa0b30764 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/IPO/SampleProfileProbe.h"
+#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Analysis/BlockFrequencyInfo.h"
@@ -249,15 +250,30 @@ void SampleProfileProber::findInvokeNormalDests(
// the tail block's successors are the original block's successors.
const Instruction *SampleProfileProber::getOriginalTerminator(
const BasicBlock *Head, const DenseSet<BasicBlock *> &BlocksToIgnore) {
- auto *TI = Head->getTerminator();
- if (auto *II = dyn_cast<InvokeInst>(TI)) {
- return getOriginalTerminator(II->getNormalDest(), BlocksToIgnore);
- } else if (succ_size(Head) == 1 &&
- BlocksToIgnore.contains(*succ_begin(Head))) {
- // Go to the unconditional branch dest.
- return getOriginalTerminator(*succ_begin(Head), BlocksToIgnore);
+ // This walk follows invoke normal destinations and ignored single-successor
+ // blocks, which is a finite chain after call-to-invoke conversion. Valid IR
+ // can still contain a self-looping invoke (normal dest == this block). Track
+ // visited blocks so that case terminates instead of looping forever.
+ SmallPtrSet<const BasicBlock *, 8> Visited;
+ const BasicBlock *BB = Head;
+ Visited.insert(BB);
+ while (true) {
+ auto *TI = BB->getTerminator();
+ const BasicBlock *Next = nullptr;
+ if (const auto *II = dyn_cast<InvokeInst>(TI))
+ Next = II->getNormalDest();
+ else if (succ_size(BB) == 1 && BlocksToIgnore.contains(*succ_begin(BB)))
+ Next = *succ_begin(BB);
+ else
+ return TI;
+
+ // A cycle has no tail block whose terminator represents the original
+ // block. Stop at the terminator that closes the cycle.
+ if (!Visited.insert(Next).second)
+ return TI;
+
+ BB = Next;
}
- return TI;
}
// Compute Hash value for the CFG: the lower 32 bits are CRC32 of the index
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
new file mode 100644
index 0000000000000..bba7a921360c4
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
@@ -0,0 +1,32 @@
+; RUN: opt < %s -passes=pseudo-probe -S | FileCheck %s
+
+; getOriginalTerminator walks invoke normal destinations. If that chain is a
+; cycle (self-looping invoke), the walk must stop. This CFG hangs without
+; cycle detection.
+;
+; Matches clang++ -O2 -fexceptions on: try { for (;;) f1(); } catch (...) {}
+define void @self_looping_invoke() personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: define void @self_looping_invoke(
+entry:
+; CHECK: entry:
+; CHECK: call void @llvm.pseudoprobe
+; CHECK-NEXT: br label %loop
+ br label %loop
+
+loop:
+; CHECK: loop:
+; CHECK-NEXT: invoke void @may_throw()
+; CHECK-NEXT: to label %loop unwind label %lpad
+ invoke void @may_throw()
+ to label %loop unwind label %lpad
+
+lpad:
+; CHECK: lpad:
+; CHECK-NOT: call void @llvm.pseudoprobe
+ %eh = landingpad { ptr, i32 }
+ cleanup
+ ret void
+}
+
+declare void @may_throw()
+declare i32 @__gxx_personality_v0(...)
>From 1bd969507d9ec255669a02a631ca9740e1839e34 Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Wed, 9 Sep 2026 03:35:15 +0000
Subject: [PATCH 2/6] [CSSPGO] [Pseudo-Probe] Probe self-looping invoke blocks
A self-looping invoke is the original block, not a split continuation,
so still insert a probe there. Autogenerate FileCheck to pin probe IDs.
---
.../lib/Transforms/IPO/SampleProfileProbe.cpp | 5 +++-
.../pseudo-probe-cyclic-normal-dest.ll | 23 +++++++++++--------
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
index 7e69fa0b30764..5d5853e1ab873 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
@@ -227,7 +227,10 @@ void SampleProfileProber::findInvokeNormalDests(
auto *TI = BB.getTerminator();
if (auto *II = dyn_cast<InvokeInst>(TI)) {
auto *ND = II->getNormalDest();
- InvokeNormalDests.insert(ND);
+ // A self-looping invoke is the original block, not a split continuation.
+ // Still instrument it.
+ if (ND != &BB)
+ InvokeNormalDests.insert(ND);
// The normal dest and the try/catch block are connected by an
// unconditional branch.
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
index bba7a921360c4..345d848992dc8 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes=pseudo-probe -S | FileCheck %s
; getOriginalTerminator walks invoke normal destinations. If that chain is a
@@ -6,23 +7,27 @@
;
; Matches clang++ -O2 -fexceptions on: try { for (;;) f1(); } catch (...) {}
define void @self_looping_invoke() personality ptr @__gxx_personality_v0 {
-; CHECK-LABEL: define void @self_looping_invoke(
+; CHECK-LABEL: define void @self_looping_invoke() personality ptr @__gxx_personality_v0 {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 1782689716240936679, i64 1, i32 0, i64 -1)
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 1782689716240936679, i64 2, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @may_throw()
+; CHECK-NEXT: to label %[[LOOP]] unwind label %[[LPAD:.*]]
+; CHECK: [[LPAD]]:
+; CHECK-NEXT: [[EH:%.*]] = landingpad { ptr, i32 }
+; CHECK-NEXT: cleanup
+; CHECK-NEXT: ret void
+;
entry:
-; CHECK: entry:
-; CHECK: call void @llvm.pseudoprobe
-; CHECK-NEXT: br label %loop
br label %loop
loop:
-; CHECK: loop:
-; CHECK-NEXT: invoke void @may_throw()
-; CHECK-NEXT: to label %loop unwind label %lpad
invoke void @may_throw()
to label %loop unwind label %lpad
lpad:
-; CHECK: lpad:
-; CHECK-NOT: call void @llvm.pseudoprobe
%eh = landingpad { ptr, i32 }
cleanup
ret void
>From 8f102d17867705910fba609f7e762b51810bd2e4 Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Wed, 9 Sep 2026 03:42:57 +0000
Subject: [PATCH 3/6] NFC: shorten self-looping invoke instrumentation comment
---
llvm/lib/Transforms/IPO/SampleProfileProbe.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
index 5d5853e1ab873..fa14e8ea50442 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
@@ -227,8 +227,7 @@ void SampleProfileProber::findInvokeNormalDests(
auto *TI = BB.getTerminator();
if (auto *II = dyn_cast<InvokeInst>(TI)) {
auto *ND = II->getNormalDest();
- // A self-looping invoke is the original block, not a split continuation.
- // Still instrument it.
+ // instrument self-looping invoke in the original block
if (ND != &BB)
InvokeNormalDests.insert(ND);
>From f1285eb2eeff24dc6e149e5625ff42f13e788a6a Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Mon, 14 Sep 2026 09:40:00 +0000
Subject: [PATCH 4/6] [CSSPGO] [Pseudo-Probe] Probe invoke blocks on
multi-block normal-dest cycles
ND != &BB only covered a self-loop. Walk the invoke-normal-dest chain and
keep probes on any cycle. Split continuations from call-to-invoke are
still ignored.
---
.../lib/Transforms/IPO/SampleProfileProbe.cpp | 21 +++-
.../pseudo-probe-cyclic-normal-dest.ll | 101 ++++++++++++++++--
2 files changed, 109 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
index fa14e8ea50442..4f0cdb10f3750 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
@@ -219,6 +219,19 @@ void SampleProfileProber::findUnreachableBlocks(
}
}
+// True if following invoke normal destinations from BB revisits a block.
+// That is a cycle of invokes, not a split continuation from call-to-invoke.
+static bool isOnInvokeNormalDestCycle(const BasicBlock *BB) {
+ SmallPtrSet<const BasicBlock *, 8> Visited;
+ while (Visited.insert(BB).second) {
+ auto *II = dyn_cast<InvokeInst>(BB->getTerminator());
+ if (!II)
+ return false;
+ BB = II->getNormalDest();
+ }
+ return true;
+}
+
// In call-to-invoke conversion, basic block can be split into multiple blocks,
// only instrument probe in the head block, ignore the normal dests.
void SampleProfileProber::findInvokeNormalDests(
@@ -227,8 +240,8 @@ void SampleProfileProber::findInvokeNormalDests(
auto *TI = BB.getTerminator();
if (auto *II = dyn_cast<InvokeInst>(TI)) {
auto *ND = II->getNormalDest();
- // instrument self-looping invoke in the original block
- if (ND != &BB)
+ // instrument invoke blocks that form a normal-dest cycle
+ if (!isOnInvokeNormalDestCycle(ND))
InvokeNormalDests.insert(ND);
// The normal dest and the try/catch block are connected by an
@@ -254,8 +267,8 @@ const Instruction *SampleProfileProber::getOriginalTerminator(
const BasicBlock *Head, const DenseSet<BasicBlock *> &BlocksToIgnore) {
// This walk follows invoke normal destinations and ignored single-successor
// blocks, which is a finite chain after call-to-invoke conversion. Valid IR
- // can still contain a self-looping invoke (normal dest == this block). Track
- // visited blocks so that case terminates instead of looping forever.
+ // can still contain a cycle of invokes. Track visited blocks so that case
+ // terminates instead of looping forever.
SmallPtrSet<const BasicBlock *, 8> Visited;
const BasicBlock *BB = Head;
Visited.insert(BB);
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
index 345d848992dc8..4ba73ec021c35 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
@@ -2,10 +2,10 @@
; RUN: opt < %s -passes=pseudo-probe -S | FileCheck %s
; getOriginalTerminator walks invoke normal destinations. If that chain is a
-; cycle (self-looping invoke), the walk must stop. This CFG hangs without
-; cycle detection.
-;
-; Matches clang++ -O2 -fexceptions on: try { for (;;) f1(); } catch (...) {}
+; cycle, the walk must stop. These CFGs hang without cycle detection.
+
+; Self-looping invoke: clang++ -O2 -fexceptions on
+; try { for (;;) f1(); } catch (...) {}
define void @self_looping_invoke() personality ptr @__gxx_personality_v0 {
; CHECK-LABEL: define void @self_looping_invoke() personality ptr @__gxx_personality_v0 {
; CHECK-NEXT: [[ENTRY:.*:]]
@@ -13,7 +13,7 @@ define void @self_looping_invoke() personality ptr @__gxx_personality_v0 {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: call void @llvm.pseudoprobe(i64 1782689716240936679, i64 2, i32 0, i64 -1)
-; CHECK-NEXT: invoke void @may_throw()
+; CHECK-NEXT: invoke void @f1()
; CHECK-NEXT: to label %[[LOOP]] unwind label %[[LPAD:.*]]
; CHECK: [[LPAD]]:
; CHECK-NEXT: [[EH:%.*]] = landingpad { ptr, i32 }
@@ -24,14 +24,97 @@ entry:
br label %loop
loop:
- invoke void @may_throw()
- to label %loop unwind label %lpad
+ invoke void @f1()
+ to label %loop unwind label %lpad
+
+lpad:
+ %eh = landingpad { ptr, i32 }
+ cleanup
+ ret void
+}
+
+; Two-invoke cycle: clang++ -O2 -fexceptions on
+; try { for (;;) { f1(); f2(); } } catch (...) {}
+define void @two_invoke_cycle() personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: define void @two_invoke_cycle() personality ptr @__gxx_personality_v0 {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 8105103579929771406, i64 1, i32 0, i64 -1)
+; CHECK-NEXT: br label %[[FOR_COND:.*]]
+; CHECK: [[FOR_COND]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 8105103579929771406, i64 2, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f1()
+; CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
+; CHECK: [[INVOKE_CONT]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 8105103579929771406, i64 4, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f2()
+; CHECK-NEXT: to label %[[FOR_COND]] unwind label %[[LPAD]]
+; CHECK: [[LPAD]]:
+; CHECK-NEXT: [[EH:%.*]] = landingpad { ptr, i32 }
+; CHECK-NEXT: cleanup
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %for.cond
+
+for.cond:
+ invoke void @f1()
+ to label %invoke.cont unwind label %lpad
+
+invoke.cont:
+ invoke void @f2()
+ to label %for.cond unwind label %lpad
+
+lpad:
+ %eh = landingpad { ptr, i32 }
+ cleanup
+ ret void
+}
+
+; Three-invoke cycle.
+define void @three_invoke_cycle() personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: define void @three_invoke_cycle() personality ptr @__gxx_personality_v0 {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 3830051905911222256, i64 1, i32 0, i64 -1)
+; CHECK-NEXT: br label %[[FOR_COND:.*]]
+; CHECK: [[FOR_COND]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 3830051905911222256, i64 2, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f1()
+; CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD:.*]]
+; CHECK: [[CONT1]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 3830051905911222256, i64 4, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f2()
+; CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD]]
+; CHECK: [[CONT2]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 3830051905911222256, i64 6, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f3()
+; CHECK-NEXT: to label %[[FOR_COND]] unwind label %[[LPAD]]
+; CHECK: [[LPAD]]:
+; CHECK-NEXT: [[EH:%.*]] = landingpad { ptr, i32 }
+; CHECK-NEXT: cleanup
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %for.cond
+
+for.cond:
+ invoke void @f1()
+ to label %cont1 unwind label %lpad
+
+cont1:
+ invoke void @f2()
+ to label %cont2 unwind label %lpad
+
+cont2:
+ invoke void @f3()
+ to label %for.cond unwind label %lpad
lpad:
%eh = landingpad { ptr, i32 }
- cleanup
+ cleanup
ret void
}
-declare void @may_throw()
+declare void @f1()
+declare void @f2()
+declare void @f3()
declare i32 @__gxx_personality_v0(...)
>From 9215decc49d7fb47db3b9e5a3623238d74e2392d Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Tue, 15 Sep 2026 08:51:06 +0000
Subject: [PATCH 5/6] [CSSPGO] [Pseudo-Probe] Distinguish invoke-cycle members
from tails
A dest that only reaches a cycle is still a call-to-invoke continuation
and must not get a block probe. Compute cycle membership once per
function and add a tail-into-cycle test.
---
.../lib/Transforms/IPO/SampleProfileProbe.cpp | 60 +++++++++++++------
.../pseudo-probe-cyclic-normal-dest.ll | 43 ++++++++++++-
2 files changed, 83 insertions(+), 20 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
index 4f0cdb10f3750..c736dbf4d83f6 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
@@ -11,7 +11,6 @@
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/IPO/SampleProfileProbe.h"
-#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Analysis/BlockFrequencyInfo.h"
@@ -219,29 +218,56 @@ void SampleProfileProber::findUnreachableBlocks(
}
}
-// True if following invoke normal destinations from BB revisits a block.
-// That is a cycle of invokes, not a split continuation from call-to-invoke.
-static bool isOnInvokeNormalDestCycle(const BasicBlock *BB) {
- SmallPtrSet<const BasicBlock *, 8> Visited;
- while (Visited.insert(BB).second) {
- auto *II = dyn_cast<InvokeInst>(BB->getTerminator());
- if (!II)
- return false;
- BB = II->getNormalDest();
+// Follow invoke normal-dest edges and record blocks that sit on a cycle.
+static void findInvokeNormalDestCycles(
+ const Function &F, DenseSet<const BasicBlock *> &CycleBlocks) {
+ DenseSet<const BasicBlock *> Processed;
+ DenseSet<const BasicBlock *> OnCurrentPath;
+ std::vector<const BasicBlock *> CurrentPath;
+
+ for (const BasicBlock &Start : F) {
+ if (Processed.contains(&Start))
+ continue;
+
+ CurrentPath.clear();
+ OnCurrentPath.clear();
+ const BasicBlock *Cur = &Start;
+ while (Cur) {
+ if (OnCurrentPath.contains(Cur)) {
+ // Back-edge onto CurrentPath: the cycle is the suffix starting at Cur.
+ auto CycleStart = CurrentPath.begin();
+ while (*CycleStart != Cur)
+ ++CycleStart;
+ CycleBlocks.insert(CycleStart, CurrentPath.end());
+ break;
+ }
+ if (Processed.contains(Cur))
+ break;
+ OnCurrentPath.insert(Cur);
+ CurrentPath.push_back(Cur);
+ if (const auto *II = dyn_cast<InvokeInst>(Cur->getTerminator()))
+ Cur = II->getNormalDest();
+ else
+ Cur = nullptr;
+ }
+ for (const BasicBlock *B : CurrentPath)
+ Processed.insert(B);
}
- return true;
}
// In call-to-invoke conversion, basic block can be split into multiple blocks,
// only instrument probe in the head block, ignore the normal dests.
void SampleProfileProber::findInvokeNormalDests(
DenseSet<BasicBlock *> &InvokeNormalDests) {
+ DenseSet<const BasicBlock *> CycleBlocks;
+ findInvokeNormalDestCycles(*F, CycleBlocks);
+
for (auto &BB : *F) {
auto *TI = BB.getTerminator();
if (auto *II = dyn_cast<InvokeInst>(TI)) {
auto *ND = II->getNormalDest();
- // instrument invoke blocks that form a normal-dest cycle
- if (!isOnInvokeNormalDestCycle(ND))
+ // Cycle members are original loop blocks, not split continuations.
+ if (!CycleBlocks.contains(ND))
InvokeNormalDests.insert(ND);
// The normal dest and the try/catch block are connected by an
@@ -265,11 +291,9 @@ void SampleProfileProber::findInvokeNormalDests(
// the tail block's successors are the original block's successors.
const Instruction *SampleProfileProber::getOriginalTerminator(
const BasicBlock *Head, const DenseSet<BasicBlock *> &BlocksToIgnore) {
- // This walk follows invoke normal destinations and ignored single-successor
- // blocks, which is a finite chain after call-to-invoke conversion. Valid IR
- // can still contain a cycle of invokes. Track visited blocks so that case
- // terminates instead of looping forever.
- SmallPtrSet<const BasicBlock *, 8> Visited;
+ // Follow invoke dests and ignored blocks to the original terminator. Stop
+ // if a block repeats; a cycle of invokes has no unique tail.
+ DenseSet<const BasicBlock *> Visited;
const BasicBlock *BB = Head;
Visited.insert(BB);
while (true) {
diff --git a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
index 4ba73ec021c35..407e0f73a7b9f 100644
--- a/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
+++ b/llvm/test/Transforms/SampleProfile/pseudo-probe-cyclic-normal-dest.ll
@@ -2,7 +2,7 @@
; RUN: opt < %s -passes=pseudo-probe -S | FileCheck %s
; getOriginalTerminator walks invoke normal destinations. If that chain is a
-; cycle, the walk must stop. These CFGs hang without cycle detection.
+; cycle, the walk must stop. These CFGs loop infinitely without cycle detection.
; Self-looping invoke: clang++ -O2 -fexceptions on
; try { for (;;) f1(); } catch (...) {}
@@ -70,7 +70,7 @@ lpad:
ret void
}
-; Three-invoke cycle.
+; Three-invoke cycle: try { for (;;) { f1(); f2(); f3(); } } catch (...) {}
define void @three_invoke_cycle() personality ptr @__gxx_personality_v0 {
; CHECK-LABEL: define void @three_invoke_cycle() personality ptr @__gxx_personality_v0 {
; CHECK-NEXT: [[ENTRY:.*:]]
@@ -114,6 +114,45 @@ lpad:
ret void
}
+; entry -> Y -> Z -> Z. Only Z is on the cycle; Y is a split continuation
+; of entry and must NOT be probed.
+; Source: try { f1(); f2(); for (;;) f3(); } catch (...) {}
+define void @tail_into_cycle() personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: define void @tail_into_cycle() personality ptr @__gxx_personality_v0 {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 -2928887130654220254, i64 1, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f1()
+; CHECK-NEXT: to label %[[Y:.*]] unwind label %[[LPAD:.*]]
+; CHECK: [[Y]]:
+; CHECK-NEXT: invoke void @f2()
+; CHECK-NEXT: to label %[[Z:.*]] unwind label %[[LPAD]]
+; CHECK: [[Z]]:
+; CHECK-NEXT: call void @llvm.pseudoprobe(i64 -2928887130654220254, i64 4, i32 0, i64 -1)
+; CHECK-NEXT: invoke void @f3()
+; CHECK-NEXT: to label %[[Z]] unwind label %[[LPAD]]
+; CHECK: [[LPAD]]:
+; CHECK-NEXT: [[EH:%.*]] = landingpad { ptr, i32 }
+; CHECK-NEXT: cleanup
+; CHECK-NEXT: ret void
+;
+entry:
+ invoke void @f1()
+ to label %Y unwind label %lpad
+
+Y:
+ invoke void @f2()
+ to label %Z unwind label %lpad
+
+Z:
+ invoke void @f3()
+ to label %Z unwind label %lpad
+
+lpad:
+ %eh = landingpad { ptr, i32 }
+ cleanup
+ ret void
+}
+
declare void @f1()
declare void @f2()
declare void @f3()
>From cb240b99a40d354c2f9c06aa4ef34c0d6aac5f4a Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Tue, 15 Sep 2026 09:06:50 +0000
Subject: [PATCH 6/6] NFC: clang-format findInvokeNormalDestCycles
---
llvm/lib/Transforms/IPO/SampleProfileProbe.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
index c736dbf4d83f6..127c1beeb5926 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileProbe.cpp
@@ -219,8 +219,9 @@ void SampleProfileProber::findUnreachableBlocks(
}
// Follow invoke normal-dest edges and record blocks that sit on a cycle.
-static void findInvokeNormalDestCycles(
- const Function &F, DenseSet<const BasicBlock *> &CycleBlocks) {
+static void
+findInvokeNormalDestCycles(const Function &F,
+ DenseSet<const BasicBlock *> &CycleBlocks) {
DenseSet<const BasicBlock *> Processed;
DenseSet<const BasicBlock *> OnCurrentPath;
std::vector<const BasicBlock *> CurrentPath;
More information about the llvm-commits
mailing list