[llvm] [TBAA] Recover !tbaa for a memcpy of struct with same type fields (PR #214116)

Abhay Kanhere via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 16:47:18 PDT 2026


https://github.com/AbhayKanhere updated https://github.com/llvm/llvm-project/pull/214116

>From 983c632b059abd49dce79784aa40c3c34927bf73 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 4 Aug 2026 18:31:48 -0700
Subject: [PATCH 1/8] [TBAA] Recover !tbaa for a memcpy of struct

When InstCombine widens a small same-typed aggregate copy into an integer
load/store, it derives no !tbaa, and !tbaa.struct is nulled out, so
the load/store are left untyped. An untyped access may-alias every typed
access and blocks optimization:

  struct Coord { int x, y; };
  void copy_if_inbounds(Coord *dst, const Coord *src, const long *bound, int n) {
    for (int i = 0; i < n; ++i)
      if (i < *bound)
        dst[i] = *src;   // {int,int} copy -> memcpy + !tbaa.struct
  }

compare this to field-by-field copy, which has typed field access
  void copy_fields_if_inbounds(Coord *dst, const Coord *src, const long *bound, int n) {
    for (int i = 0; i < n; ++i)
      if (i < *bound) { dst[i].x = src->x; dst[i].y = src->y; }  // per-field, typed
  }
---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp  | 24 +++++++++++++
 .../InstCombine/struct-assign-tbaa.ll         | 36 +++++++++++++++++--
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index dbe4ccac7801d..2c52153677e26 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -819,6 +819,30 @@ AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
       M->getOperand(2) && isa<MDNode>(M->getOperand(2)))
     New.TBAA = cast<MDNode>(M->getOperand(2));
 
+  // The access may cover several !tbaa.struct fields (e.g. a {int, int} copy
+  // widened to an i64 load/store). If those fields share a single tag and tile
+  // [0, AccessSize) with no gaps, that tag still describes the whole access.
+  if (!New.TBAA && M) {
+    MDNode *CommonTag = nullptr;
+    uint64_t Offset = 0;
+    for (size_t I = 0, E = M->getNumOperands(); I < E; I += 3) {
+      ConstantInt *FieldOffset =
+          mdconst::extract<ConstantInt>(M->getOperand(I));
+      ConstantInt *FieldSize =
+          mdconst::extract<ConstantInt>(M->getOperand(I + 1));
+      MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
+      if (!FieldTag || FieldOffset->getZExtValue() != Offset ||
+          (CommonTag && FieldTag != CommonTag))
+        break;
+      CommonTag = FieldTag;
+      Offset += FieldSize->getZExtValue();
+      if (Offset >= AccessSize)
+        break;
+    }
+    if (Offset == AccessSize)
+      New.TBAA = CommonTag;
+  }
+
   New.TBAAStruct = nullptr;
   return New;
 }
diff --git a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
index fd4389ab0f8f2..a33c07171514e 100644
--- a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
+++ b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
@@ -40,8 +40,8 @@ define void @test3_multiple_fields(ptr nocapture %a, ptr nocapture %b) {
 ; CHECK-LABEL: define void @test3_multiple_fields(
 ; CHECK-SAME: ptr captures(none) [[A:%.*]], ptr captures(none) [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[TMP0:%.*]] = load i64, ptr [[B]], align 4
-; CHECK-NEXT:    store i64 [[TMP0]], ptr [[A]], align 4
+; CHECK-NEXT:    [[TMP0:%.*]] = load i64, ptr [[B]], align 4, !tbaa [[FLOAT_TBAA0]]
+; CHECK-NEXT:    store i64 [[TMP0]], ptr [[A]], align 4, !tbaa [[FLOAT_TBAA0]]
 ; CHECK-NEXT:    ret void
 ;
 entry:
@@ -75,6 +75,32 @@ entry:
   ret void
 }
 
+define void @test6_int_int(ptr nocapture %a, ptr nocapture %b) {
+; CHECK-LABEL: define void @test6_int_int(
+; CHECK-SAME: ptr captures(none) [[A:%.*]], ptr captures(none) [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i64, ptr [[B]], align 4, !tbaa [[INT_TBAA3:![0-9]+]]
+; CHECK-NEXT:    store i64 [[TMP0]], ptr [[A]], align 4, !tbaa [[INT_TBAA3]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %b, i64 8, i1 false), !tbaa.struct !8
+  ret void
+}
+
+define void @test7_mixed_type(ptr nocapture %a, ptr nocapture %b) {
+; CHECK-LABEL: define void @test7_mixed_type(
+; CHECK-SAME: ptr captures(none) [[A:%.*]], ptr captures(none) [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i64, ptr [[B]], align 4
+; CHECK-NEXT:    store i64 [[TMP0]], ptr [[A]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %b, i64 8, i1 false), !tbaa.struct !11
+  ret void
+}
+
 !0 = !{!"Simple C/C++ TBAA"}
 !1 = !{!"omnipotent char", !0}
 !2 = !{!5, !5, i64 0}
@@ -83,6 +109,10 @@ entry:
 !5 = !{!"float", !0}
 !6 = !{i64 0, i64 4, !2, i64 4, i64 4, !2}
 !7 = !{i64 0, i64 2, !2, i64 4, i64 6, !2}
+!8 = !{i64 0, i64 4, !9, i64 4, i64 4, !9}
+!9 = !{!10, !10, i64 0}
+!10 = !{!"int", !0}
+!11 = !{i64 0, i64 4, !9, i64 4, i64 4, !2}
 
 ;.
 ; CHECK: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
@@ -90,4 +120,6 @@ entry:
 ; CHECK: [[FLOAT_TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0}
 ; CHECK: [[META1]] = !{!"float", [[META2:![0-9]+]]}
 ; CHECK: [[META2]] = !{!"Simple C/C++ TBAA"}
+; CHECK: [[INT_TBAA3]] = !{[[META4:![0-9]+]], [[META4]], i64 0}
+; CHECK: [[META4]] = !{!"int", [[META2]]}
 ;.

>From 411e1c87c5aae23c712777c46f3a67b56e8b9444 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 5 Aug 2026 09:40:23 -0700
Subject: [PATCH 2/8] [Analysis] updated code formatting.

update to code formatting.
---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp | 40 ++++++++++----------
 1 file changed, 19 insertions(+), 21 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index 2c52153677e26..90117cf78ec05 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -822,28 +822,26 @@ AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
   // The access may cover several !tbaa.struct fields (e.g. a {int, int} copy
   // widened to an i64 load/store). If those fields share a single tag and tile
   // [0, AccessSize) with no gaps, that tag still describes the whole access.
-  if (!New.TBAA && M) {
-    MDNode *CommonTag = nullptr;
-    uint64_t Offset = 0;
-    for (size_t I = 0, E = M->getNumOperands(); I < E; I += 3) {
-      ConstantInt *FieldOffset =
-          mdconst::extract<ConstantInt>(M->getOperand(I));
-      ConstantInt *FieldSize =
-          mdconst::extract<ConstantInt>(M->getOperand(I + 1));
-      MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
-      if (!FieldTag || FieldOffset->getZExtValue() != Offset ||
-          (CommonTag && FieldTag != CommonTag))
-        break;
-      CommonTag = FieldTag;
-      Offset += FieldSize->getZExtValue();
-      if (Offset >= AccessSize)
-        break;
-    }
-    if (Offset == AccessSize)
-      New.TBAA = CommonTag;
-  }
-
   New.TBAAStruct = nullptr;
+  if (New.TBAA || !M)
+    return New;
+  MDNode *CommonTag = nullptr;
+  uint64_t Offset = 0;
+  for (size_t I = 0, E = M->getNumOperands(); I < E; I += 3) {
+    ConstantInt *FieldOffset = mdconst::extract<ConstantInt>(M->getOperand(I));
+    ConstantInt *FieldSize =
+        mdconst::extract<ConstantInt>(M->getOperand(I + 1));
+    MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
+    if (!FieldTag || FieldOffset->getZExtValue() != Offset ||
+        (CommonTag && FieldTag != CommonTag))
+      break;
+    CommonTag = FieldTag;
+    Offset += FieldSize->getZExtValue();
+    if (Offset >= AccessSize)
+      break;
+  }
+  if (Offset == AccessSize)
+    New.TBAA = CommonTag;
   return New;
 }
 

>From d9024d693e20e625203ad5cc877f0305f868f4fa Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 26 Aug 2026 10:42:52 -0700
Subject: [PATCH 3/8] [TBAA] robustness checks for looping over AA records

robust looping over AA records.
---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index 90117cf78ec05..b0b732f844596 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -827,12 +827,14 @@ AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
     return New;
   MDNode *CommonTag = nullptr;
   uint64_t Offset = 0;
-  for (size_t I = 0, E = M->getNumOperands(); I < E; I += 3) {
-    ConstantInt *FieldOffset = mdconst::extract<ConstantInt>(M->getOperand(I));
+  for (size_t I = 0, E = M->getNumOperands(); I + 2 < E; I += 3) {
+    ConstantInt *FieldOffset =
+        mdconst::dyn_extract_or_null<ConstantInt>(M->getOperand(I));
     ConstantInt *FieldSize =
-        mdconst::extract<ConstantInt>(M->getOperand(I + 1));
+        mdconst::dyn_extract_or_null<ConstantInt>(M->getOperand(I + 1));
     MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
-    if (!FieldTag || FieldOffset->getZExtValue() != Offset ||
+    if (!FieldOffset || !FieldSize || !FieldTag ||
+        FieldOffset->getZExtValue() != Offset ||
         (CommonTag && FieldTag != CommonTag))
       break;
     CommonTag = FieldTag;

>From 01cda67c9826b76b6b70ad02a1fbc3ee82ff3d62 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 2 Sep 2026 15:01:37 -0700
Subject: [PATCH 4/8] [TBAA] remove redundant fast path

---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index b0b732f844596..19e14ddd7e306 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -810,14 +810,6 @@ MDNode *AAMDNodes::extendToTBAA(MDNode *MD, ssize_t Len) {
 AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
   AAMDNodes New = *this;
   MDNode *M = New.TBAAStruct;
-  if (!New.TBAA && M && M->getNumOperands() >= 3 && M->getOperand(0) &&
-      mdconst::hasa<ConstantInt>(M->getOperand(0)) &&
-      mdconst::extract<ConstantInt>(M->getOperand(0))->isZero() &&
-      M->getOperand(1) && mdconst::hasa<ConstantInt>(M->getOperand(1)) &&
-      mdconst::extract<ConstantInt>(M->getOperand(1))->getValue() ==
-          AccessSize &&
-      M->getOperand(2) && isa<MDNode>(M->getOperand(2)))
-    New.TBAA = cast<MDNode>(M->getOperand(2));
 
   // The access may cover several !tbaa.struct fields (e.g. a {int, int} copy
   // widened to an i64 load/store). If those fields share a single tag and tile

>From fd810a6354e2c28e0886a149250c34cc51c672d3 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Wed, 2 Sep 2026 15:03:22 -0700
Subject: [PATCH 5/8] [TBAA] guard oversized !tbaa.struct fields via
 getTBAAStructFieldAsInt64

---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp  | 22 +++++++++++++------
 .../InstCombine/struct-assign-tbaa.ll         | 16 ++++++++++++++
 2 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index 19e14ddd7e306..d0c0d5691882d 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -748,6 +748,16 @@ MDNode *AAMDNodes::shiftTBAA(MDNode *MD, size_t Offset) {
   return MD;
 }
 
+// Read a !tbaa.struct field entry (an offset or a size) into Out as a 64-bit
+// value. Returns false if it is not a constant integer that fits in 64 bits.
+static bool getTBAAStructFieldAsInt64(const MDOperand &Op, uint64_t &Out) {
+  auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(Op);
+  if (!CI || !CI->getValue().isIntN(64))
+    return false;
+  Out = CI->getZExtValue();
+  return true;
+}
+
 MDNode *AAMDNodes::shiftTBAAStruct(MDNode *MD, size_t Offset) {
   // Fast path if there's no offset
   if (Offset == 0)
@@ -820,17 +830,15 @@ AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
   MDNode *CommonTag = nullptr;
   uint64_t Offset = 0;
   for (size_t I = 0, E = M->getNumOperands(); I + 2 < E; I += 3) {
-    ConstantInt *FieldOffset =
-        mdconst::dyn_extract_or_null<ConstantInt>(M->getOperand(I));
-    ConstantInt *FieldSize =
-        mdconst::dyn_extract_or_null<ConstantInt>(M->getOperand(I + 1));
+    uint64_t FieldOffset, FieldSize;
     MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
-    if (!FieldOffset || !FieldSize || !FieldTag ||
-        FieldOffset->getZExtValue() != Offset ||
+    if (!getTBAAStructFieldAsInt64(M->getOperand(I), FieldOffset) ||
+        !getTBAAStructFieldAsInt64(M->getOperand(I + 1), FieldSize) ||
+        !FieldTag || FieldOffset != Offset ||
         (CommonTag && FieldTag != CommonTag))
       break;
     CommonTag = FieldTag;
-    Offset += FieldSize->getZExtValue();
+    Offset += FieldSize;
     if (Offset >= AccessSize)
       break;
   }
diff --git a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
index a33c07171514e..66ff443e772d6 100644
--- a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
+++ b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
@@ -101,6 +101,21 @@ entry:
   ret void
 }
 
+; A !tbaa.struct field offset that does not fit in i64 must not assert in
+; getZExtValue(); the walk bails and no tag is recovered (untyped load/store).
+define void @test8_i128_offset(ptr nocapture %a, ptr nocapture %b) {
+; CHECK-LABEL: define void @test8_i128_offset(
+; CHECK-SAME: ptr captures(none) [[A:%.*]], ptr captures(none) [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i64, ptr [[B]], align 4
+; CHECK-NEXT:    store i64 [[TMP0]], ptr [[A]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %b, i64 8, i1 false), !tbaa.struct !12
+  ret void
+}
+
 !0 = !{!"Simple C/C++ TBAA"}
 !1 = !{!"omnipotent char", !0}
 !2 = !{!5, !5, i64 0}
@@ -113,6 +128,7 @@ entry:
 !9 = !{!10, !10, i64 0}
 !10 = !{!"int", !0}
 !11 = !{i64 0, i64 4, !9, i64 4, i64 4, !2}
+!12 = !{i128 0, i128 4, !2, i128 18446744073709551616, i128 4, !2}
 
 ;.
 ; CHECK: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }

>From 59bc9e25c18591220d8b3055c363d24cfc6800ca Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 8 Sep 2026 12:24:13 -0700
Subject: [PATCH 6/8] [TBAA] review comment addressed

use tryZExtValue in getTBAAStructFieldAsInt64.
---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index d0c0d5691882d..f49f0df5dfbaf 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -752,9 +752,12 @@ MDNode *AAMDNodes::shiftTBAA(MDNode *MD, size_t Offset) {
 // value. Returns false if it is not a constant integer that fits in 64 bits.
 static bool getTBAAStructFieldAsInt64(const MDOperand &Op, uint64_t &Out) {
   auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(Op);
-  if (!CI || !CI->getValue().isIntN(64))
+  if (!CI)
     return false;
-  Out = CI->getZExtValue();
+  std::optional<uint64_t> Val = CI->getValue().tryZExtValue();
+  if (!Val)
+    return false;
+  Out = *Val;
   return true;
 }
 

>From e6cc661cdb11223ee386c06cbdc95804808caa30 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Thu, 10 Sep 2026 12:25:28 -0700
Subject: [PATCH 7/8] [TBAA] Reject invalid !tbaa.struct field tags in
 adjustForAccess

Promote a !tbaa.struct field's tag operand to !tbaa only after
checking the tag is a valid access tag; otherwise the module
verifier rejects it.

Resolves crash identified in
https://github.com/cuhk-s3/Archer/issues/21
---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp   | 18 +++++++++++++++++-
 .../InstCombine/struct-assign-tbaa.ll          | 17 +++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index f49f0df5dfbaf..a056b4d2aaeb6 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -761,6 +761,22 @@ static bool getTBAAStructFieldAsInt64(const MDOperand &Op, uint64_t &Out) {
   return true;
 }
 
+// Return true if Tag is a struct-path TBAA access tag: two type nodes (base
+// and access) followed by constant integer fields (offset, and an optional
+// size and/or immutability flag). A !tbaa.struct field operand need not be
+// such a tag, so validate before reusing it as !tbaa.
+static bool isValidTBAAAccessTag(const MDNode *Tag) {
+  unsigned NumOps = Tag->getNumOperands();
+  if (NumOps < 3 || NumOps > 5)
+    return false;
+  if (!isa_and_nonnull<MDNode>(Tag->getOperand(0)) ||
+      !isa_and_nonnull<MDNode>(Tag->getOperand(1)))
+    return false;
+  return all_of(drop_begin(Tag->operands(), 2), [](const MDOperand &Op) {
+    return mdconst::dyn_extract_or_null<ConstantInt>(Op) != nullptr;
+  });
+}
+
 MDNode *AAMDNodes::shiftTBAAStruct(MDNode *MD, size_t Offset) {
   // Fast path if there's no offset
   if (Offset == 0)
@@ -837,7 +853,7 @@ AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
     MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
     if (!getTBAAStructFieldAsInt64(M->getOperand(I), FieldOffset) ||
         !getTBAAStructFieldAsInt64(M->getOperand(I + 1), FieldSize) ||
-        !FieldTag || FieldOffset != Offset ||
+        !FieldTag || !isValidTBAAAccessTag(FieldTag) || FieldOffset != Offset ||
         (CommonTag && FieldTag != CommonTag))
       break;
     CommonTag = FieldTag;
diff --git a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
index 66ff443e772d6..fa8ba0ee968c2 100644
--- a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
+++ b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
@@ -116,6 +116,21 @@ entry:
   ret void
 }
 
+; A !tbaa.struct field tag that is not a valid access tag (here it is itself
+; !tbaa.struct-shaped) must not be promoted to !tbaa.
+define void @test9_invalid_field_tag(ptr nocapture %a, ptr nocapture %b) {
+; CHECK-LABEL: define void @test9_invalid_field_tag(
+; CHECK-SAME: ptr captures(none) [[A:%.*]], ptr captures(none) [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[B]], align 4
+; CHECK-NEXT:    store i32 [[TMP0]], ptr [[A]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  tail call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %b, i64 4, i1 false), !tbaa.struct !13
+  ret void
+}
+
 !0 = !{!"Simple C/C++ TBAA"}
 !1 = !{!"omnipotent char", !0}
 !2 = !{!5, !5, i64 0}
@@ -129,6 +144,8 @@ entry:
 !10 = !{!"int", !0}
 !11 = !{i64 0, i64 4, !9, i64 4, i64 4, !2}
 !12 = !{i128 0, i128 4, !2, i128 18446744073709551616, i128 4, !2}
+!13 = !{i64 0, i64 2, !14, i64 2, i64 2, !14}
+!14 = !{i64 0, i64 4, null}
 
 ;.
 ; CHECK: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }

>From 8651eaee3a8ef2597577e19a66eaeede13657be9 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Mon, 14 Sep 2026 08:52:52 -0700
Subject: [PATCH 8/8] [TBAA] Tighten struct-path access-tag shape check in
 adjustForAccess

Reuse the file-local isNewFormatTypeNode so the !tbaa.struct field-tag
shape check expects the correct operand count per TBAA format, and
return std::optional<uint64_t> from getTBAAStructFieldAsInt64.
---
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp | 58 ++++++++++----------
 1 file changed, 30 insertions(+), 28 deletions(-)

diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index a056b4d2aaeb6..5a21668c42b59 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -748,30 +748,31 @@ MDNode *AAMDNodes::shiftTBAA(MDNode *MD, size_t Offset) {
   return MD;
 }
 
-// Read a !tbaa.struct field entry (an offset or a size) into Out as a 64-bit
-// value. Returns false if it is not a constant integer that fits in 64 bits.
-static bool getTBAAStructFieldAsInt64(const MDOperand &Op, uint64_t &Out) {
+// Read a !tbaa.struct field entry (an offset or a size) as a 64-bit value.
+// Returns std::nullopt if it is not a constant integer that fits in 64 bits.
+static std::optional<uint64_t> getTBAAStructFieldAsInt64(const MDOperand &Op) {
   auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(Op);
-  if (!CI)
+  return CI ? CI->getValue().tryZExtValue() : std::nullopt;
+}
+
+// Return true if Tag is a well-formed struct-path TBAA access tag shape: base
+// and access type nodes followed by constant-integer fields (offset, a size
+// field for the new format, and an optional immutability flag). Structural
+// check only.
+static bool isWellFormedTBAAAccessTagShape(const MDNode *Tag) {
+  const MDNode *AccessType = nullptr;
+  if (Tag->getNumOperands() < 3 ||
+      !dyn_cast_or_null<MDNode>(Tag->getOperand(0)) ||
+      !(AccessType = dyn_cast_or_null<MDNode>(Tag->getOperand(1))))
     return false;
-  std::optional<uint64_t> Val = CI->getValue().tryZExtValue();
-  if (!Val)
-    return false;
-  Out = *Val;
-  return true;
-}
 
-// Return true if Tag is a struct-path TBAA access tag: two type nodes (base
-// and access) followed by constant integer fields (offset, and an optional
-// size and/or immutability flag). A !tbaa.struct field operand need not be
-// such a tag, so validate before reusing it as !tbaa.
-static bool isValidTBAAAccessTag(const MDNode *Tag) {
-  unsigned NumOps = Tag->getNumOperands();
-  if (NumOps < 3 || NumOps > 5)
-    return false;
-  if (!isa_and_nonnull<MDNode>(Tag->getOperand(0)) ||
-      !isa_and_nonnull<MDNode>(Tag->getOperand(1)))
+  // Operand count is format-dependent: the new format carries a size field the
+  // old one lacks; both allow a trailing immutability flag.
+  unsigned MinOps = isNewFormatTypeNode(AccessType) ? 4 : 3;
+  if (Tag->getNumOperands() < MinOps || Tag->getNumOperands() > MinOps + 1)
     return false;
+
+  // Offset, size, and the immutability flag are constant integers.
   return all_of(drop_begin(Tag->operands(), 2), [](const MDOperand &Op) {
     return mdconst::dyn_extract_or_null<ConstantInt>(Op) != nullptr;
   });
@@ -848,18 +849,19 @@ AAMDNodes AAMDNodes::adjustForAccess(unsigned AccessSize) {
     return New;
   MDNode *CommonTag = nullptr;
   uint64_t Offset = 0;
-  for (size_t I = 0, E = M->getNumOperands(); I + 2 < E; I += 3) {
-    uint64_t FieldOffset, FieldSize;
+  for (size_t I = 0, E = M->getNumOperands(); I + 2 < E && Offset < AccessSize;
+       I += 3) {
+    std::optional<uint64_t> FieldOffset =
+        getTBAAStructFieldAsInt64(M->getOperand(I));
+    std::optional<uint64_t> FieldSize =
+        getTBAAStructFieldAsInt64(M->getOperand(I + 1));
     MDNode *FieldTag = dyn_cast_or_null<MDNode>(M->getOperand(I + 2));
-    if (!getTBAAStructFieldAsInt64(M->getOperand(I), FieldOffset) ||
-        !getTBAAStructFieldAsInt64(M->getOperand(I + 1), FieldSize) ||
-        !FieldTag || !isValidTBAAAccessTag(FieldTag) || FieldOffset != Offset ||
+    if (!FieldOffset || !FieldSize || !FieldTag ||
+        !isWellFormedTBAAAccessTagShape(FieldTag) || *FieldOffset != Offset ||
         (CommonTag && FieldTag != CommonTag))
       break;
     CommonTag = FieldTag;
-    Offset += FieldSize;
-    if (Offset >= AccessSize)
-      break;
+    Offset += *FieldSize;
   }
   if (Offset == AccessSize)
     New.TBAA = CommonTag;



More information about the llvm-commits mailing list