[llvm] [MemDep] Stop traversal if load operand is a constant (PR #223456)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 5 05:54:48 PDT 2026
https://github.com/eleviant updated https://github.com/llvm/llvm-project/pull/223456
>From 028562ce2a24f54b644dc7a038c34466b01aee59 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 14 Sep 2026 18:44:52 +0200
Subject: [PATCH 1/4] [GVN] Fix vtable processing of static object
Patch fixes crash in GVN pass when compiling this C++ code:
https://godbolt.org/z/sbKMa5K1d
---
.../lib/Analysis/MemoryDependenceAnalysis.cpp | 3 +-
llvm/test/Transforms/GVN/strict-vtable.ll | 38 +++++++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Transforms/GVN/strict-vtable.ll
diff --git a/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp b/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
index a5a178c6f2583..730bd1d67f911 100644
--- a/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
@@ -323,7 +323,8 @@ MemoryDependenceResults::getInvariantGroupPointerDependency(LoadInst *LI,
for (const Use &Us : LoadOperand->uses()) {
auto *U = dyn_cast<Instruction>(Us.getUser());
- if (!U || U == LI || !DT.dominates(U, LI))
+ if (!U || U == LI || U->getFunction() != LI->getFunction() ||
+ !DT.dominates(U, LI))
continue;
// If we hit load/store with the same invariant.group metadata (and the
diff --git a/llvm/test/Transforms/GVN/strict-vtable.ll b/llvm/test/Transforms/GVN/strict-vtable.ll
new file mode 100644
index 0000000000000..91c6575d7e2fb
--- /dev/null
+++ b/llvm/test/Transforms/GVN/strict-vtable.ll
@@ -0,0 +1,38 @@
+; RUN: opt -passes=gvn -S %s -o /dev/null
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+
+$_ZTV1i = comdat any
+
+$_ZTI1i = comdat any
+
+$_ZTS1i = comdat any
+
+ at f = dso_local local_unnamed_addr global float 0.000000e+00, align 4
+ at _ZZ1gvE1a = internal global { ptr, ptr } { ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr], [3 x ptr] }, ptr @_ZTV1i, i32 0, i32 0, i32 2), ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr], [3 x ptr] }, ptr @_ZTV1i, i32 0, i32 1, i32 2) }, align 8
+ at _ZTV1i = linkonce_odr dso_local constant { [3 x ptr], [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1i, ptr @_ZN1b1cEv], [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1i, ptr @_ZN1d1eEf] }, comdat, align 8
+ at _ZTI1i = linkonce_odr dso_local constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), ptr @_ZTS1i, i32 0, i32 2, ptr @_ZTI1b, i64 2, ptr @_ZTI1d, i64 2050 }, comdat, align 8
+ at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
+ at _ZTS1i = linkonce_odr dso_local constant [3 x i8] c"1i\00", comdat, align 1
+ at _ZTI1b = external constant ptr
+ at _ZTI1d = external constant ptr
+
+define dso_local void @_Z1hv() local_unnamed_addr {
+entry:
+ %0 = load float, ptr @f, align 4
+ %vtable = load ptr, ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), align 8, !invariant.group !0
+ %1 = load ptr, ptr %vtable, align 8, !invariant.load !0
+ tail call void %1(ptr noundef nonnull align 8 dereferenceable(8) getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), float noundef %0)
+ ret void
+}
+
+define dso_local noundef nonnull align 8 dereferenceable(8) ptr @_Z1gv() local_unnamed_addr {
+entry:
+ ret ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8)
+}
+
+declare noundef i32 @_ZN1b1cEv(ptr noundef nonnull align 8 dereferenceable(8)) unnamed_addr
+
+declare void @_ZN1d1eEf(ptr noundef nonnull align 8 dereferenceable(8), float noundef) unnamed_addr
+
+!0 = !{}
>From e398189d7acbda6c7210cede9b5c8dd98f5fae19 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 1 Oct 2026 13:03:20 +0200
Subject: [PATCH 2/4] Refactor and minimize test case
---
.../lib/Analysis/MemoryDependenceAnalysis.cpp | 7 ++--
llvm/test/Transforms/GVN/strict-vtable.ll | 41 ++++++++++---------
2 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp b/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
index 730bd1d67f911..d888fff250a0e 100644
--- a/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
@@ -304,11 +304,11 @@ MemoryDependenceResults::getInvariantGroupPointerDependency(LoadInst *LI,
// cast graph down only.
Value *LoadOperand = LI->getPointerOperand()->stripPointerCasts();
- // It's is not safe to walk the use list of global value, because function
+ // It's is not safe to walk the use list of constant, because function
// passes aren't allowed to look outside their functions.
// FIXME: this could be fixed by filtering instructions from outside
// of current function.
- if (isa<GlobalValue>(LoadOperand))
+ if (isa<Constant>(LoadOperand))
return MemDepResult::getUnknown();
Instruction *ClosestDependency = nullptr;
@@ -323,8 +323,7 @@ MemoryDependenceResults::getInvariantGroupPointerDependency(LoadInst *LI,
for (const Use &Us : LoadOperand->uses()) {
auto *U = dyn_cast<Instruction>(Us.getUser());
- if (!U || U == LI || U->getFunction() != LI->getFunction() ||
- !DT.dominates(U, LI))
+ if (!U || U == LI || !DT.dominates(U, LI))
continue;
// If we hit load/store with the same invariant.group metadata (and the
diff --git a/llvm/test/Transforms/GVN/strict-vtable.ll b/llvm/test/Transforms/GVN/strict-vtable.ll
index 91c6575d7e2fb..9c0bbb7f66c5f 100644
--- a/llvm/test/Transforms/GVN/strict-vtable.ll
+++ b/llvm/test/Transforms/GVN/strict-vtable.ll
@@ -1,38 +1,39 @@
-; RUN: opt -passes=gvn -S %s -o /dev/null
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=gvn -S %s -o - | FileCheck %s
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
-$_ZTV1i = comdat any
-
-$_ZTI1i = comdat any
-
-$_ZTS1i = comdat any
-
@f = dso_local local_unnamed_addr global float 0.000000e+00, align 4
- at _ZZ1gvE1a = internal global { ptr, ptr } { ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr], [3 x ptr] }, ptr @_ZTV1i, i32 0, i32 0, i32 2), ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr], [3 x ptr] }, ptr @_ZTV1i, i32 0, i32 1, i32 2) }, align 8
- at _ZTV1i = linkonce_odr dso_local constant { [3 x ptr], [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1i, ptr @_ZN1b1cEv], [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr @_ZTI1i, ptr @_ZN1d1eEf] }, comdat, align 8
- at _ZTI1i = linkonce_odr dso_local constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), ptr @_ZTS1i, i32 0, i32 2, ptr @_ZTI1b, i64 2, ptr @_ZTI1d, i64 2050 }, comdat, align 8
- at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
- at _ZTS1i = linkonce_odr dso_local constant [3 x i8] c"1i\00", comdat, align 1
- at _ZTI1b = external constant ptr
- at _ZTI1d = external constant ptr
+
+ at _ZZ1gvE1a = internal global { ptr, ptr } zeroinitializer
define dso_local void @_Z1hv() local_unnamed_addr {
+; CHECK-LABEL: define dso_local void @_Z1hv() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = load float, ptr @f, align 4
+; CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), align 8, !invariant.group [[META0:![0-9]+]]
+; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[VTABLE]], align 8, !invariant.load [[META0]]
+; CHECK-NEXT: tail call void [[TMP1]](ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), float noundef [[TMP0]])
+; CHECK-NEXT: ret void
+;
entry:
%0 = load float, ptr @f, align 4
%vtable = load ptr, ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), align 8, !invariant.group !0
%1 = load ptr, ptr %vtable, align 8, !invariant.load !0
- tail call void %1(ptr noundef nonnull align 8 dereferenceable(8) getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), float noundef %0)
+ tail call void %1(ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), float noundef %0)
ret void
}
-define dso_local noundef nonnull align 8 dereferenceable(8) ptr @_Z1gv() local_unnamed_addr {
+define dso_local ptr @_Z1gv() local_unnamed_addr {
+; CHECK-LABEL: define dso_local ptr @_Z1gv() local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: ret ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8)
+;
entry:
ret ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8)
}
-declare noundef i32 @_ZN1b1cEv(ptr noundef nonnull align 8 dereferenceable(8)) unnamed_addr
-
-declare void @_ZN1d1eEf(ptr noundef nonnull align 8 dereferenceable(8), float noundef) unnamed_addr
-
!0 = !{}
+;.
+; CHECK: [[META0]] = !{}
+;.
>From 9548a5bee49e87011a598de4f59476e37a333d7f Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 5 Oct 2026 14:36:23 +0200
Subject: [PATCH 3/4] Simplify original test case and add new one
---
.../GVN/invariant-group-constexpr.ll | 48 +++++++++++++++++++
llvm/test/Transforms/GVN/strict-vtable.ll | 24 +++++-----
2 files changed, 59 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/Transforms/GVN/invariant-group-constexpr.ll
diff --git a/llvm/test/Transforms/GVN/invariant-group-constexpr.ll b/llvm/test/Transforms/GVN/invariant-group-constexpr.ll
new file mode 100644
index 0000000000000..0501839a5920d
--- /dev/null
+++ b/llvm/test/Transforms/GVN/invariant-group-constexpr.ll
@@ -0,0 +1,48 @@
+; RUN: opt < %s -passes=gvn -S | FileCheck %s
+
+; MemDep must not walk the use list of a constant pointer operand to find an
+; !invariant.group dependency. A constant GEP into a global is shared by every
+; function that uses it, so the walk would find loads and stores in other
+; functions and GVN would forward their values across function boundaries.
+
+ at flag = global [16 x i32] zeroinitializer
+ at arr = global [256 x double] zeroinitializer
+
+define void @init() {
+ store i32 1, ptr getelementptr inbounds (i8, ptr @flag, i64 4), !invariant.group !0
+ ret void
+}
+
+; The store in @init must not be forwarded.
+define i32 @use_flag(ptr %p) {
+; CHECK-LABEL: define i32 @use_flag(
+; CHECK: [[V:%.*]] = load i32, ptr getelementptr inbounds (i8, ptr @flag, i64 4), align 4, !invariant.group
+; CHECK: [[S:%.*]] = add i32 %{{.*}}, [[V]]
+; CHECK: ret i32 [[S]]
+ %x = load i32, ptr %p
+ %v = load i32, ptr getelementptr inbounds (i8, ptr @flag, i64 4), !invariant.group !0
+ %s = add i32 %x, %v
+ ret i32 %s
+}
+
+; The load in @load_other must not be forwarded into @load_first.
+define double @load_first(ptr %p) {
+; CHECK-LABEL: define double @load_first(
+; CHECK: [[V:%.*]] = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), align 8, !invariant.group
+; CHECK: [[M:%.*]] = fmul double %{{.*}}, [[V]]
+; CHECK: ret double [[M]]
+ %x = load double, ptr %p
+ %v = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), !invariant.group !0
+ %m = fmul double %x, %v
+ ret double %m
+}
+
+define double @load_other() {
+; CHECK-LABEL: define double @load_other(
+; CHECK: [[V:%.*]] = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), align 8, !invariant.group
+; CHECK: ret double [[V]]
+ %v = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), !invariant.group !0
+ ret double %v
+}
+
+!0 = !{}
diff --git a/llvm/test/Transforms/GVN/strict-vtable.ll b/llvm/test/Transforms/GVN/strict-vtable.ll
index 9c0bbb7f66c5f..dde1fba97f7eb 100644
--- a/llvm/test/Transforms/GVN/strict-vtable.ll
+++ b/llvm/test/Transforms/GVN/strict-vtable.ll
@@ -1,31 +1,29 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt -passes=gvn -S %s -o - | FileCheck %s
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
-
- at f = dso_local local_unnamed_addr global float 0.000000e+00, align 4
+ at p = global ptr null, align 8
@_ZZ1gvE1a = internal global { ptr, ptr } zeroinitializer
-define dso_local void @_Z1hv() local_unnamed_addr {
-; CHECK-LABEL: define dso_local void @_Z1hv() local_unnamed_addr {
+define void @_Z1hv() {
+; CHECK-LABEL: define void @_Z1hv() {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TMP0:%.*]] = load float, ptr @f, align 4
+; CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8
; CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), align 8, !invariant.group [[META0:![0-9]+]]
-; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[VTABLE]], align 8, !invariant.load [[META0]]
-; CHECK-NEXT: tail call void [[TMP1]](ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), float noundef [[TMP0]])
+; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[VTABLE]], align 8
+; CHECK-NEXT: tail call void [[TMP1]](ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), ptr [[TMP0]])
; CHECK-NEXT: ret void
;
entry:
- %0 = load float, ptr @f, align 4
+ %0 = load ptr, ptr @p, align 8
%vtable = load ptr, ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), align 8, !invariant.group !0
- %1 = load ptr, ptr %vtable, align 8, !invariant.load !0
- tail call void %1(ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), float noundef %0)
+ %1 = load ptr, ptr %vtable, align 8
+ tail call void %1(ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8), ptr %0)
ret void
}
-define dso_local ptr @_Z1gv() local_unnamed_addr {
-; CHECK-LABEL: define dso_local ptr @_Z1gv() local_unnamed_addr {
+define dso_local ptr @_Z1gv() {
+; CHECK-LABEL: define dso_local ptr @_Z1gv() {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: ret ptr getelementptr inbounds nuw (i8, ptr @_ZZ1gvE1a, i64 8)
;
>From 929c380cd71490a319c4716d0418f3ea1530cd5f Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 5 Oct 2026 14:54:20 +0200
Subject: [PATCH 4/4] Run update_test_checks on invariant-group-constexpr
---
.../GVN/invariant-group-constexpr.ll | 33 ++++++++++++++-----
1 file changed, 24 insertions(+), 9 deletions(-)
diff --git a/llvm/test/Transforms/GVN/invariant-group-constexpr.ll b/llvm/test/Transforms/GVN/invariant-group-constexpr.ll
index 0501839a5920d..75c81105dc9a6 100644
--- a/llvm/test/Transforms/GVN/invariant-group-constexpr.ll
+++ b/llvm/test/Transforms/GVN/invariant-group-constexpr.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt < %s -passes=gvn -S | FileCheck %s
; MemDep must not walk the use list of a constant pointer operand to find an
@@ -9,6 +10,10 @@
@arr = global [256 x double] zeroinitializer
define void @init() {
+; CHECK-LABEL: define void @init() {
+; CHECK-NEXT: store i32 1, ptr getelementptr inbounds (i8, ptr @flag, i64 4), align 4, !invariant.group [[META0:![0-9]+]]
+; CHECK-NEXT: ret void
+;
store i32 1, ptr getelementptr inbounds (i8, ptr @flag, i64 4), !invariant.group !0
ret void
}
@@ -16,9 +21,12 @@ define void @init() {
; The store in @init must not be forwarded.
define i32 @use_flag(ptr %p) {
; CHECK-LABEL: define i32 @use_flag(
-; CHECK: [[V:%.*]] = load i32, ptr getelementptr inbounds (i8, ptr @flag, i64 4), align 4, !invariant.group
-; CHECK: [[S:%.*]] = add i32 %{{.*}}, [[V]]
-; CHECK: ret i32 [[S]]
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = load i32, ptr [[P]], align 4
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr getelementptr inbounds (i8, ptr @flag, i64 4), align 4, !invariant.group [[META0]]
+; CHECK-NEXT: [[S:%.*]] = add i32 [[X]], [[V]]
+; CHECK-NEXT: ret i32 [[S]]
+;
%x = load i32, ptr %p
%v = load i32, ptr getelementptr inbounds (i8, ptr @flag, i64 4), !invariant.group !0
%s = add i32 %x, %v
@@ -28,9 +36,12 @@ define i32 @use_flag(ptr %p) {
; The load in @load_other must not be forwarded into @load_first.
define double @load_first(ptr %p) {
; CHECK-LABEL: define double @load_first(
-; CHECK: [[V:%.*]] = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), align 8, !invariant.group
-; CHECK: [[M:%.*]] = fmul double %{{.*}}, [[V]]
-; CHECK: ret double [[M]]
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[X:%.*]] = load double, ptr [[P]], align 8
+; CHECK-NEXT: [[V:%.*]] = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), align 8, !invariant.group [[META0]]
+; CHECK-NEXT: [[M:%.*]] = fmul double [[X]], [[V]]
+; CHECK-NEXT: ret double [[M]]
+;
%x = load double, ptr %p
%v = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), !invariant.group !0
%m = fmul double %x, %v
@@ -38,11 +49,15 @@ define double @load_first(ptr %p) {
}
define double @load_other() {
-; CHECK-LABEL: define double @load_other(
-; CHECK: [[V:%.*]] = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), align 8, !invariant.group
-; CHECK: ret double [[V]]
+; CHECK-LABEL: define double @load_other() {
+; CHECK-NEXT: [[V:%.*]] = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), align 8, !invariant.group [[META0]]
+; CHECK-NEXT: ret double [[V]]
+;
%v = load double, ptr getelementptr inbounds (i8, ptr @arr, i64 16), !invariant.group !0
ret double %v
}
!0 = !{}
+;.
+; CHECK: [[META0]] = !{}
+;.
More information about the llvm-commits
mailing list