[llvm] [IR] Preserve TBAA metadata when merging equivalent memory accesses (PR #208499)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 10 00:03:20 PDT 2026
https://github.com/222rohan updated https://github.com/llvm/llvm-project/pull/208499
>From bd04d6adc6c1b762a7ed82f0cf45172a1ff1b802 Mon Sep 17 00:00:00 2001
From: Rohan Shenoy <rshenoy at amd.com>
Date: Thu, 9 Jul 2026 17:38:31 +0530
Subject: [PATCH 1/2] [IR] Preserve TBAA metadata when merging equivalent
memory accesses
When CSE/GVN merge two equivalent memory accesses (e.g. two loads of the same
address) into one, the surviving instruction should keep the !tbaa tag if
either access had one. Currently combineMetadata only keeps TBAA when the
surviving instruction already had a tag, so if the survivor is untyped the tag
from the removed access is dropped. This makes the result depend on source
order (which access becomes the survivor).
---
llvm/lib/Transforms/Utils/Local.cpp | 27 +++++++++----
.../Transforms/EarlyCSE/tbaa-merge-order.ll | 39 +++++++++++++++++++
2 files changed, 59 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll
diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp
index b17740c0bc192..b067dbfb5ac22 100644
--- a/llvm/lib/Transforms/Utils/Local.cpp
+++ b/llvm/lib/Transforms/Utils/Local.cpp
@@ -2964,10 +2964,6 @@ static void combineMetadata(Instruction *K, const Instruction *J,
if (!AAOnly)
K->mergeDIAssignID(J);
break;
- case LLVMContext::MD_tbaa:
- if (DoesKMove)
- K->setMetadata(Kind, MDNode::getMostGenericTBAA(JMD, KMD));
- break;
case LLVMContext::MD_alias_scope:
if (DoesKMove)
K->setMetadata(Kind, MDNode::getMostGenericAliasScope(JMD, KMD));
@@ -3007,9 +3003,10 @@ static void combineMetadata(Instruction *K, const Instruction *J,
if (!AAOnly && (DoesKMove || !K->hasMetadata(LLVMContext::MD_noundef)))
K->setMetadata(Kind, JMD);
break;
- // Keep empty cases for prof, mmra, memprof, and callsite to prevent them
- // from being removed as unknown metadata. The actual merging is handled
- // separately below.
+ // Keep empty cases for tbaa, prof, mmra, memprof, and callsite to prevent
+ // them from being removed as unknown metadata. The actual merging is
+ // handled separately below.
+ case LLVMContext::MD_tbaa:
case LLVMContext::MD_prof:
case LLVMContext::MD_mmra:
case LLVMContext::MD_memprof:
@@ -3113,6 +3110,22 @@ static void combineMetadata(Instruction *K, const Instruction *J,
K->setMetadata(LLVMContext::MD_prof,
MDNode::getMergedProfMetadata(KProf, JProf, K, J));
}
+
+ // Merge TBAA metadata.
+ // Handle separately to support cases where only one instruction has the
+ // metadata.
+ MDNode *JTBAA = J->getMetadata(LLVMContext::MD_tbaa);
+ MDNode *KTBAA = K->getMetadata(LLVMContext::MD_tbaa);
+ if (KTBAA) {
+ if (DoesKMove)
+ K->setMetadata(LLVMContext::MD_tbaa,
+ MDNode::getMostGenericTBAA(JTBAA, KTBAA));
+ } else if (!AAOnly && JTBAA &&
+ isa<LoadInst, StoreInst, CallInst, VAArgInst, AtomicRMWInst,
+ AtomicCmpXchgInst>(K)) {
+ // Only J has a tag: copy it so the result is independent of CSE order.
+ K->setMetadata(LLVMContext::MD_tbaa, JTBAA);
+ }
}
void llvm::combineMetadataForCSE(Instruction *K, const Instruction *J,
diff --git a/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll b/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll
new file mode 100644
index 0000000000000..a3f742a490eec
--- /dev/null
+++ b/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll
@@ -0,0 +1,39 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes=early-cse -S < %s | FileCheck %s
+
+; Two loads of the same address are CSE'd into one. Only one of them carries
+; !tbaa. The surviving load's metadata must not depend on which load was
+; written first in the source (i.e. which becomes the CSE survivor).
+
+; typed (tbaa) load first, untyped load second.
+define i32 @typed_first(ptr %p) {
+; CHECK-LABEL: define i32 @typed_first(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !tbaa [[INT_TBAA0:![0-9]+]]
+; CHECK-NEXT: [[S:%.*]] = add i32 [[A]], [[A]]
+; CHECK-NEXT: ret i32 [[S]]
+;
+ %a = load i32, ptr %p, align 4, !tbaa !0
+ %b = load i32, ptr %p, align 4
+ %s = add i32 %a, %b
+ ret i32 %s
+}
+
+; untyped load first, typed (tbaa) load second. Result must match typed_first.
+define i32 @untyped_first(ptr %p) {
+; CHECK-LABEL: define i32 @untyped_first(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[P]], align 4, !tbaa [[INT_TBAA0]]
+; CHECK-NEXT: [[S:%.*]] = add i32 [[B]], [[B]]
+; CHECK-NEXT: ret i32 [[S]]
+;
+ %b = load i32, ptr %p, align 4
+ %a = load i32, ptr %p, align 4, !tbaa !0
+ %s = add i32 %a, %b
+ ret i32 %s
+}
+
+!0 = !{!1, !1, i64 0}
+!1 = !{!"int", !2, i64 0}
+!2 = !{!"omnipotent char", !3, i64 0}
+!3 = !{!"Simple C/C++ TBAA"}
>From 8287b4237338818d39e282e08789e0306fe9518d Mon Sep 17 00:00:00 2001
From: Rohan Shenoy <rshenoy at amd.com>
Date: Fri, 10 Jul 2026 12:30:52 +0530
Subject: [PATCH 2/2] Reworked to only attach tbaa when execution is guaranteed
---
llvm/lib/Transforms/Utils/Local.cpp | 19 ++++++----
.../Transforms/EarlyCSE/tbaa-merge-order.ll | 35 +++++++++++++++++--
2 files changed, 45 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp
index b067dbfb5ac22..b332bebeed290 100644
--- a/llvm/lib/Transforms/Utils/Local.cpp
+++ b/llvm/lib/Transforms/Utils/Local.cpp
@@ -3111,20 +3111,25 @@ static void combineMetadata(Instruction *K, const Instruction *J,
MDNode::getMergedProfMetadata(KProf, JProf, K, J));
}
- // Merge TBAA metadata.
- // Handle separately to support cases where only one instruction has the
- // metadata.
+ // Merge TBAA metadata. Handled separately to also cover the case where only
+ // one instruction has a tag. A one-sided tag is copied onto K only when K is
+ // guaranteed to execute exactly where J did, so we never assert a type on a
+ // path that never had it.
MDNode *JTBAA = J->getMetadata(LLVMContext::MD_tbaa);
MDNode *KTBAA = K->getMetadata(LLVMContext::MD_tbaa);
if (KTBAA) {
if (DoesKMove)
K->setMetadata(LLVMContext::MD_tbaa,
MDNode::getMostGenericTBAA(JTBAA, KTBAA));
- } else if (!AAOnly && JTBAA &&
+ } else if (!AAOnly && !DoesKMove && JTBAA &&
isa<LoadInst, StoreInst, CallInst, VAArgInst, AtomicRMWInst,
- AtomicCmpXchgInst>(K)) {
- // Only J has a tag: copy it so the result is independent of CSE order.
- K->setMetadata(LLVMContext::MD_tbaa, JTBAA);
+ AtomicCmpXchgInst>(K) &&
+ K->getParent() == J->getParent()) {
+ const Instruction *First = K->comesBefore(J) ? K : J;
+ const Instruction *Second = First == K ? J : K;
+ if (isGuaranteedToTransferExecutionToSuccessor(
+ std::next(First->getIterator()), Second->getIterator()))
+ K->setMetadata(LLVMContext::MD_tbaa, JTBAA);
}
}
diff --git a/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll b/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll
index a3f742a490eec..ba0ac88fb05eb 100644
--- a/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll
+++ b/llvm/test/Transforms/EarlyCSE/tbaa-merge-order.ll
@@ -9,7 +9,7 @@
define i32 @typed_first(ptr %p) {
; CHECK-LABEL: define i32 @typed_first(
; CHECK-SAME: ptr [[P:%.*]]) {
-; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !tbaa [[INT_TBAA0:![0-9]+]]
+; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !tbaa [[TBAA0:![0-9]+]]
; CHECK-NEXT: [[S:%.*]] = add i32 [[A]], [[A]]
; CHECK-NEXT: ret i32 [[S]]
;
@@ -23,7 +23,7 @@ define i32 @typed_first(ptr %p) {
define i32 @untyped_first(ptr %p) {
; CHECK-LABEL: define i32 @untyped_first(
; CHECK-SAME: ptr [[P:%.*]]) {
-; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[P]], align 4, !tbaa [[INT_TBAA0]]
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[P]], align 4, !tbaa [[TBAA0]]
; CHECK-NEXT: [[S:%.*]] = add i32 [[B]], [[B]]
; CHECK-NEXT: ret i32 [[S]]
;
@@ -33,7 +33,38 @@ define i32 @untyped_first(ptr %p) {
ret i32 %s
}
+; A conditionally-executed typed load is CSE'd into an unconditional untyped
+; load. The survivor executes on paths where the typed load never did, so its
+; !tbaa tag must NOT be propagated onto the survivor.
+define i32 @conditional_typed_not_propagated(ptr %p, i1 %c) {
+; CHECK-LABEL: define i32 @conditional_typed_not_propagated(
+; CHECK-SAME: ptr [[P:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[K:%.*]] = load i32, ptr [[P]], align 4
+; CHECK-NEXT: br i1 [[C]], label %[[THEN:.*]], label %[[EXIT:.*]]
+; CHECK: [[THEN]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[K]]
+;
+entry:
+ %k = load i32, ptr %p, align 4
+ br i1 %c, label %then, label %exit
+then:
+ %j = load i32, ptr %p, align 4, !tbaa !0
+ br label %exit
+exit:
+ %r = phi i32 [ %j, %then ], [ %k, %entry ]
+ ret i32 %r
+}
+
!0 = !{!1, !1, i64 0}
!1 = !{!"int", !2, i64 0}
!2 = !{!"omnipotent char", !3, i64 0}
!3 = !{!"Simple C/C++ TBAA"}
+;.
+; CHECK: [[TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0}
+; CHECK: [[META1]] = !{!"int", [[META2:![0-9]+]], i64 0}
+; CHECK: [[META2]] = !{!"omnipotent char", [[META3:![0-9]+]], i64 0}
+; CHECK: [[META3]] = !{!"Simple C/C++ TBAA"}
+;.
More information about the llvm-commits
mailing list