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

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 25 14:19:21 PDT 2026


Author: Abhay Kanhere
Date: 2026-09-25T14:19:15-07:00
New Revision: 35df00b0aa5d9f4deec215306dc24d247f5eb66c

URL: https://github.com/llvm/llvm-project/commit/35df00b0aa5d9f4deec215306dc24d247f5eb66c
DIFF: https://github.com/llvm/llvm-project/commit/35df00b0aa5d9f4deec215306dc24d247f5eb66c.diff

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

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
  }

Added: 
    

Modified: 
    llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
    llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index 134e1c28b4caa..a0e8ab43b2df2 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -759,6 +759,20 @@ MDNode *AAMDNodes::shiftTBAA(MDNode *MD, size_t Offset) {
   return MD;
 }
 
+// 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);
+  return CI ? CI->getValue().tryZExtValue() : std::nullopt;
+}
+
+static bool isScalarAccessTag(const MDNode *Tag) {
+  TBAAStructTagNode T(Tag);
+  const MDNode *AccessType;
+  return Tag->getNumOperands() >= 3 && (AccessType = T.getAccessType()) &&
+         AccessType == T.getBaseType();
+}
+
 MDNode *AAMDNodes::shiftTBAAStruct(MDNode *MD, size_t Offset) {
   // Fast path if there's no offset
   if (Offset == 0)
@@ -821,16 +835,31 @@ 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
+  // [0, AccessSize) with no gaps, that tag still describes the whole access.
   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 + 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 (!FieldOffset || !FieldSize || !FieldTag ||
+        !isScalarAccessTag(FieldTag) || *FieldOffset != Offset ||
+        (CommonTag && FieldTag != CommonTag))
+      break;
+    CommonTag = FieldTag;
+    Offset += *FieldSize;
+  }
+  if (Offset == AccessSize)
+    New.TBAA = CommonTag;
   return New;
 }
 

diff  --git a/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll b/llvm/test/Transforms/InstCombine/struct-assign-tbaa.ll
index fd4389ab0f8f2..fa8ba0ee968c2 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,62 @@ 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
+}
+
+; 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
+}
+
+; 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}
@@ -83,6 +139,13 @@ 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}
+!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) }
@@ -90,4 +153,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]]}
 ;.


        


More information about the llvm-commits mailing list