[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