[llvm] [LoopVersioningLICM] Do not let llvm.pseudoprobe block loop versioning (PR #209684)
chandan singh via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 20 04:58:34 PDT 2026
https://github.com/chandankds updated https://github.com/llvm/llvm-project/pull/209684
>From 6cfdf62ba92f77fbacd8ca57a22356994a1638be Mon Sep 17 00:00:00 2001
From: chandankds <chandankds at gmail.com>
Date: Thu, 16 Jul 2026 12:38:38 +0000
Subject: [PATCH] [LoopVersioningLICM] Do not let llvm.pseudoprobe block loop
versioning
LoopVersioningLICM::instructionSafeForVersioning() rejects any call that is
not proven to not access memory. llvm.pseudoprobe is declared
IntrInaccessibleMemOnly (so the optimizer will not delete or sink it), so
AA->doesNotAccessMemory() returns false and the probe is treated as an unsafe
call site. This disables loop-versioning LICM for essentially every hot loop
in a sample-based / CSSPGO profile-guided build (in the presence of
-fpseudo-probe-for-profiling), since a pseudo probe is inserted on every basic block.
Pseudo probes are pure profiling placeholders with no observable effect on the
loop's memory accesses, so skip them in the call-safety check.
Also added a willreturn check to the call-safety test, so a call that may not return must not be versioned around.
RFC: https://discourse.llvm.org/t/csspgo-unblocking-pseudo-probe-safe-optimizations/90946
---
.../Transforms/Scalar/LoopVersioningLICM.cpp | 12 ++-
.../LoopVersioningLICM/pseudoprobe.ll | 74 +++++++++++++++++++
2 files changed, 85 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/pseudoprobe.ll
diff --git a/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp b/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp
index 3aed643ee8065..5eca92132578f 100644
--- a/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp
@@ -307,11 +307,21 @@ bool LoopVersioningLICM::instructionSafeForVersioning(Instruction *I) {
LLVM_DEBUG(dbgs() << " Convergent call site found.\n");
return false;
}
+ if (!Call->willReturn()) {
+ LLVM_DEBUG(dbgs() << " Call site that may not return found.\n");
+ return false;
+ }
- if (!AA->doesNotAccessMemory(Call)) {
+ // Calls that only access inaccessible memory cannot alias loop memory and
+ // are safe to duplicate during loop versioning. This covers
+ // llvm.pseudoprobe (used for sample-based profiling under
+ // -fpseudo-probe-for-profiling).
+ if (Call->mayThrow() ||
+ !AA->getMemoryEffects(Call).onlyAccessesInaccessibleMem()) {
LLVM_DEBUG(dbgs() << " Unsafe call site found.\n");
return false;
}
+ return true;
}
// Avoid loops with possiblity of throw
diff --git a/llvm/test/Transforms/LoopVersioningLICM/pseudoprobe.ll b/llvm/test/Transforms/LoopVersioningLICM/pseudoprobe.ll
new file mode 100644
index 0000000000000..cb9e617f0e4d7
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/pseudoprobe.ll
@@ -0,0 +1,74 @@
+; RUN: opt %s -passes='loop(loop-versioning-licm)' -S | FileCheck %s
+;
+; LoopVersioningLICM must not refuse to version a loop solely because of a call
+; that only accesses inaccessible memory. Such a call cannot alias any pointer
+; accessed in the loop, so it is safe for versioning. This notably covers
+; llvm.pseudoprobe, which is inserted on every block under sample-based
+; profiling (in the presence of -fpseudo-probe-for-profiling), as well as other
+; inaccessible-memory intrinsics such as llvm.sideeffect.
+
+; A loop containing an llvm.pseudoprobe intrinsic is still versioned.
+; CHECK-LABEL: @test_pseudoprobe_lvlicm(
+; CHECK: lver.check
+define double @test_pseudoprobe_lvlicm(ptr %x, ptr %y, i32 %n) {
+entry:
+ %cmp = icmp sgt i32 %n, 0
+ br i1 %cmp, label %ph, label %exit
+
+ph: ; preds = %entry
+ br label %body
+
+body: ; preds = %body, %ph
+ %i = phi i32 [ 0, %ph ], [ %inext, %body ]
+ %sum = phi double [ 0.000000e+00, %ph ], [ %sumnext, %body ]
+ %yidx = getelementptr inbounds double, ptr %y, i32 %i
+ %yv = load double, ptr %yidx, align 8
+ %add = fadd double %yv, 1.000000e+00
+ %xidx = getelementptr inbounds double, ptr %x, i32 %i
+ store double %add, ptr %xidx, align 8
+ %x0 = load double, ptr %x, align 8 ; loop-invariant load
+ %sumnext = fadd double %sum, %x0
+ call void @llvm.pseudoprobe(i64 1234, i64 1, i32 0, i64 -1)
+ %inext = add nuw nsw i32 %i, 1
+ %exitcond = icmp eq i32 %inext, %n
+ br i1 %exitcond, label %exit, label %body
+
+exit: ; preds = %body, %entry
+ %sumlcssa = phi double [ 0.000000e+00, %entry ], [ %sumnext, %body ]
+ ret double %sumlcssa
+}
+
+; The same holds for any call that only accesses inaccessible memory, e.g.
+; llvm.sideeffect.
+; CHECK-LABEL: @test_sideeffect_lvlicm(
+; CHECK: lver.check
+define double @test_sideeffect_lvlicm(ptr %x, ptr %y, i32 %n) {
+entry:
+ %cmp = icmp sgt i32 %n, 0
+ br i1 %cmp, label %ph, label %exit
+
+ph: ; preds = %entry
+ br label %body
+
+body: ; preds = %body, %ph
+ %i = phi i32 [ 0, %ph ], [ %inext, %body ]
+ %sum = phi double [ 0.000000e+00, %ph ], [ %sumnext, %body ]
+ %yidx = getelementptr inbounds double, ptr %y, i32 %i
+ %yv = load double, ptr %yidx, align 8
+ %add = fadd double %yv, 1.000000e+00
+ %xidx = getelementptr inbounds double, ptr %x, i32 %i
+ store double %add, ptr %xidx, align 8
+ %x0 = load double, ptr %x, align 8 ; loop-invariant load
+ %sumnext = fadd double %sum, %x0
+ call void @llvm.sideeffect()
+ %inext = add nuw nsw i32 %i, 1
+ %exitcond = icmp eq i32 %inext, %n
+ br i1 %exitcond, label %exit, label %body
+
+exit: ; preds = %body, %entry
+ %sumlcssa = phi double [ 0.000000e+00, %entry ], [ %sumnext, %body ]
+ ret double %sumlcssa
+}
+
+declare void @llvm.pseudoprobe(i64, i64, i32, i64)
+declare void @llvm.sideeffect()
More information about the llvm-commits
mailing list