[llvm] [LICM] Improve hoisting of calls marked with invariant_load metadata (PR #193678)
Carl Ritson via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 23 00:10:19 PDT 2026
https://github.com/perlfu created https://github.com/llvm/llvm-project/pull/193678
- Consider calls marked with invariant.load speculatable
- Allow LICM to hoist call instructions marked invariant.load
- Preserve invariant.load metadata when hoisting in LICM
This improves hoisting of AMDGPU s.buffer.load memory intrinsics.
>From 35d66c547809dee5a8dde5678d9dbe997472a545 Mon Sep 17 00:00:00 2001
From: Carl Ritson <carl.ritson at amd.com>
Date: Thu, 23 Apr 2026 15:39:05 +0900
Subject: [PATCH 1/3] [LICM] Improve hoisting of calls marked with
invariant_load metadata
- Consider calls marked with invariant.load speculatable
- Allow LICM to hoist call instructions marked invariant.load
- Preserve invariant.load metadata when hoisting in LICM
This improves hoisting of AMDGPU s.buffer.load memory intrinsics.
---
llvm/lib/Analysis/ValueTracking.cpp | 3 ++
llvm/lib/Transforms/Scalar/LICM.cpp | 7 ++-
llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll | 43 +++++++++++++++++++
3 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index d304c036594c9..7ad0ba2dc7504 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -7305,6 +7305,9 @@ bool llvm::isSafeToSpeculativelyExecuteWithOpcode(
return false;
const Function *Callee = CI->getCalledFunction();
+ // Assume we can always speculate invariant loads.
+ if (CI->hasMetadata(LLVMContext::MD_invariant_load))
+ return true;
// The called function could have undefined behavior or side-effects, even
// if marked readnone nounwind.
if (!Callee || !Callee->isSpeculatable())
diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp
index 03edf9e3b0c1e..028d9719f3112 100644
--- a/llvm/lib/Transforms/Scalar/LICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LICM.cpp
@@ -1232,6 +1232,10 @@ bool llvm::canSinkOrHoistInst(Instruction &I, AAResults *AA, DominatorTree *DT,
// Assumes don't actually alias anything or throw
return true;
+ if (CI->hasMetadata(LLVMContext::MD_invariant_load))
+ // Call is always invariant
+ return true;
+
// Handle simple cases by querying alias analysis.
MemoryEffects Behavior = AA->getMemoryEffects(CI);
@@ -1702,7 +1706,8 @@ static void hoist(Instruction &I, const DominatorTree *DT, const Loop *CurLoop,
// time in isGuaranteedToExecute if we don't actually have anything to
// drop. It is a compile time optimization, not required for correctness.
!SafetyInfo->isGuaranteedToExecute(I, DT, CurLoop)) {
- I.dropUBImplyingAttrsAndMetadata();
+ // Preserve invariant.load as moving load does not change its invariance
+ I.dropUBImplyingAttrsAndMetadata({LLVMContext::MD_invariant_load});
}
if (isa<PHINode>(I))
diff --git a/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll b/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
new file mode 100644
index 0000000000000..dfb3970830864
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
@@ -0,0 +1,43 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -mtriple=amdgcn-amd-amdpal -mcpu=gfx1200 -passes=licm | FileCheck %s
+
+define amdgpu_ps void @licm_s_buffer_load(ptr addrspace(1) nocapture %out, i32 inreg noundef %userdata) {
+entry:
+ %i = call i64 @llvm.amdgcn.s.getpc()
+ %i1 = and i64 %i, -4294967296
+ %i2 = zext i32 %userdata to i64
+ %i3 = or disjoint i64 %i1, %i2
+ %i4 = inttoptr i64 %i3 to ptr addrspace(4)
+ br label %preheader
+
+preheader:
+ %desc = load <4 x i32>, ptr addrspace(4) %i4, align 16
+ br label %loop
+
+loop:
+ %idx = phi i32 [ 0, %preheader ], [ %next.i, %loop.end ]
+ %val.0 = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> %desc, i32 16, i32 0), !invariant.load !0
+ %cond.0 = icmp eq i32 %val.0, 0
+ br i1 %cond.0, label %branch.a, label %loop.end
+
+branch.a:
+ %val.1 = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> %desc, i32 20, i32 0), !invariant.load !0
+ %cond.1 = icmp ne i32 %val.1, 0
+ br i1 %cond.1, label %branch.b, label %loop.end
+
+branch.b:
+ %idx.i64 = sext i32 %idx to i64
+ %ptr = getelementptr inbounds i32, ptr addrspace(1) %out, i64 %idx.i64
+ store i32 %val.1, ptr addrspace(1) %ptr, align 4
+ br label %loop.end
+
+loop.end:
+ %next.i = add nuw nsw i32 %idx, 1
+ %cond.break = icmp eq i32 %next.i, 1024
+ br i1 %cond.break, label %exit, label %loop
+
+exit:
+ ret void
+}
+
+!0 = !{}
>From 3c2f50c937cc0ab79addbf1ccb3b02860964604c Mon Sep 17 00:00:00 2001
From: Carl Ritson <carl.ritson at amd.com>
Date: Thu, 23 Apr 2026 16:00:42 +0900
Subject: [PATCH 2/3] - Test before
---
llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll b/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
index dfb3970830864..dd3d208c11f1a 100644
--- a/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
+++ b/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
@@ -2,6 +2,39 @@
; RUN: opt -S -mtriple=amdgcn-amd-amdpal -mcpu=gfx1200 -passes=licm | FileCheck %s
define amdgpu_ps void @licm_s_buffer_load(ptr addrspace(1) nocapture %out, i32 inreg noundef %userdata) {
+; CHECK-LABEL: define amdgpu_ps void @licm_s_buffer_load(
+; CHECK-SAME: ptr addrspace(1) captures(none) [[OUT:%.*]], i32 inreg noundef [[USERDATA:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[I:%.*]] = call i64 @llvm.amdgcn.s.getpc()
+; CHECK-NEXT: [[I1:%.*]] = and i64 [[I]], -4294967296
+; CHECK-NEXT: [[I2:%.*]] = zext i32 [[USERDATA]] to i64
+; CHECK-NEXT: [[I3:%.*]] = or disjoint i64 [[I1]], [[I2]]
+; CHECK-NEXT: [[I4:%.*]] = inttoptr i64 [[I3]] to ptr addrspace(4)
+; CHECK-NEXT: br label %[[PREHEADER:.*]]
+; CHECK: [[PREHEADER]]:
+; CHECK-NEXT: [[DESC:%.*]] = load <4 x i32>, ptr addrspace(4) [[I4]], align 16
+; CHECK-NEXT: [[VAL_0:%.*]] = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> [[DESC]], i32 16, i32 0), !invariant.load [[META0:![0-9]+]]
+; CHECK-NEXT: [[COND_0:%.*]] = icmp eq i32 [[VAL_0]], 0
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IDX:%.*]] = phi i32 [ 0, %[[PREHEADER]] ], [ [[NEXT_I:%.*]], %[[LOOP_END:.*]] ]
+; CHECK-NEXT: br i1 [[COND_0]], label %[[BRANCH_A:.*]], label %[[LOOP_END]]
+; CHECK: [[BRANCH_A]]:
+; CHECK-NEXT: [[VAL_1:%.*]] = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> [[DESC]], i32 20, i32 0), !invariant.load [[META0]]
+; CHECK-NEXT: [[COND_1:%.*]] = icmp ne i32 [[VAL_1]], 0
+; CHECK-NEXT: br i1 [[COND_1]], label %[[BRANCH_B:.*]], label %[[LOOP_END]]
+; CHECK: [[BRANCH_B]]:
+; CHECK-NEXT: [[IDX_I64:%.*]] = sext i32 [[IDX]] to i64
+; CHECK-NEXT: [[PTR:%.*]] = getelementptr inbounds i32, ptr addrspace(1) [[OUT]], i64 [[IDX_I64]]
+; CHECK-NEXT: store i32 [[VAL_1]], ptr addrspace(1) [[PTR]], align 4
+; CHECK-NEXT: br label %[[LOOP_END]]
+; CHECK: [[LOOP_END]]:
+; CHECK-NEXT: [[NEXT_I]] = add nuw nsw i32 [[IDX]], 1
+; CHECK-NEXT: [[COND_BREAK:%.*]] = icmp eq i32 [[NEXT_I]], 1024
+; CHECK-NEXT: br i1 [[COND_BREAK]], label %[[EXIT:.*]], label %[[LOOP]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
entry:
%i = call i64 @llvm.amdgcn.s.getpc()
%i1 = and i64 %i, -4294967296
@@ -41,3 +74,6 @@ exit:
}
!0 = !{}
+;.
+; CHECK: [[META0]] = !{}
+;.
>From 4b43172624c8195375bbf4550570e967e96b6c09 Mon Sep 17 00:00:00 2001
From: Carl Ritson <carl.ritson at amd.com>
Date: Thu, 23 Apr 2026 16:01:48 +0900
Subject: [PATCH 3/3] - Test after
---
llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll b/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
index dd3d208c11f1a..629336adea33f 100644
--- a/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
+++ b/llvm/test/CodeGen/AMDGPU/licm-sbuffer-load.ll
@@ -15,13 +15,13 @@ define amdgpu_ps void @licm_s_buffer_load(ptr addrspace(1) nocapture %out, i32 i
; CHECK-NEXT: [[DESC:%.*]] = load <4 x i32>, ptr addrspace(4) [[I4]], align 16
; CHECK-NEXT: [[VAL_0:%.*]] = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> [[DESC]], i32 16, i32 0), !invariant.load [[META0:![0-9]+]]
; CHECK-NEXT: [[COND_0:%.*]] = icmp eq i32 [[VAL_0]], 0
+; CHECK-NEXT: [[VAL_1:%.*]] = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> [[DESC]], i32 20, i32 0), !invariant.load [[META0]]
+; CHECK-NEXT: [[COND_1:%.*]] = icmp ne i32 [[VAL_1]], 0
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IDX:%.*]] = phi i32 [ 0, %[[PREHEADER]] ], [ [[NEXT_I:%.*]], %[[LOOP_END:.*]] ]
; CHECK-NEXT: br i1 [[COND_0]], label %[[BRANCH_A:.*]], label %[[LOOP_END]]
; CHECK: [[BRANCH_A]]:
-; CHECK-NEXT: [[VAL_1:%.*]] = call i32 @llvm.amdgcn.s.buffer.load.i32(<4 x i32> [[DESC]], i32 20, i32 0), !invariant.load [[META0]]
-; CHECK-NEXT: [[COND_1:%.*]] = icmp ne i32 [[VAL_1]], 0
; CHECK-NEXT: br i1 [[COND_1]], label %[[BRANCH_B:.*]], label %[[LOOP_END]]
; CHECK: [[BRANCH_B]]:
; CHECK-NEXT: [[IDX_I64:%.*]] = sext i32 [[IDX]] to i64
More information about the llvm-commits
mailing list