[llvm] [SROA] Update the struct-to-vector test to be more representative (NFC) (PR #217498)
Yonah Goldberg via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 20 11:03:45 PDT 2026
https://github.com/YonahGoldberg updated https://github.com/llvm/llvm-project/pull/217498
>From 36e8d1180663b2853ad9dbdcd10f264f29e0bff5 Mon Sep 17 00:00:00 2001
From: Yonah Goldberg <ygoldberg at nvidia.com>
Date: Thu, 20 Aug 2026 01:10:33 +0000
Subject: [PATCH 1/3] fix
---
llvm/test/Transforms/SROA/struct-to-vector.ll | 532 ++++++------------
1 file changed, 180 insertions(+), 352 deletions(-)
diff --git a/llvm/test/Transforms/SROA/struct-to-vector.ll b/llvm/test/Transforms/SROA/struct-to-vector.ll
index b7fb3cc97d66a..d89923981e1f7 100644
--- a/llvm/test/Transforms/SROA/struct-to-vector.ll
+++ b/llvm/test/Transforms/SROA/struct-to-vector.ll
@@ -1,420 +1,248 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt -passes='sroa<aggregate-to-vector>,gvn,instcombine,simplifycfg' -S %s | FileCheck %s
-%struct.myint4 = type { i32, i32, i32, i32 }
+; RUN: opt -passes='sroa<aggregate-to-vector>' -S %s | FileCheck %s
-define dso_local void @foo_flat(ptr noundef %x, i64 %y.coerce0, i64 %y.coerce1, i32 noundef %cond) {
-; CHECK-LABEL: define dso_local void @foo_flat(
-; CHECK-SAME: ptr noundef [[X:%.*]], i64 [[Y_COERCE0:%.*]], i64 [[Y_COERCE1:%.*]], i32 noundef [[COND:%.*]]) {
+%struct.i32x4 = type { i32, i32, i32, i32 }
+
+; memcpyopt can not optimize memcpy src -> tmp -> C=dst to memcpy src -> dst because the
+; in-between store may alias A. Instead, we can promote the tmp in SROA,
+; which may eliminate some loads and stores (if the value does not spill).
+define void @copy_across_clobber(ptr %src, ptr %dst, ptr %alias, i8 %value) {
+; CHECK-LABEL: define void @copy_across_clobber(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST:%.*]], ptr [[ALIAS:%.*]], i8 [[VALUE:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[COND]], 0
-; CHECK-NEXT: [[DOTY_COERCE1:%.*]] = select i1 [[TOBOOL_NOT]], i64 0, i64 [[Y_COERCE1]]
-; CHECK-NEXT: [[DOTY_COERCE0:%.*]] = select i1 [[TOBOOL_NOT]], i64 0, i64 [[Y_COERCE0]]
-; CHECK-NEXT: store i64 [[DOTY_COERCE0]], ptr [[X]], align 16
-; CHECK-NEXT: [[X_REPACK7:%.*]] = getelementptr inbounds nuw i8, ptr [[X]], i64 8
-; CHECK-NEXT: store i64 [[DOTY_COERCE1]], ptr [[X_REPACK7]], align 8
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD:%.*]] = load <4 x i32>, ptr [[SRC]], align 4
+; CHECK-NEXT: store i8 [[VALUE]], ptr [[ALIAS]], align 1
+; CHECK-NEXT: store <4 x i32> [[TMP_SROA_0_0_COPYLOAD]], ptr [[DST]], align 4
; CHECK-NEXT: ret void
;
entry:
- %y = alloca %struct.myint4, align 16
- %x.addr = alloca ptr, align 8
- %cond.addr = alloca i32, align 4
- %temp = alloca %struct.myint4, align 16
- %zero = alloca %struct.myint4, align 16
- %data = alloca %struct.myint4, align 16
- %0 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 0
- store i64 %y.coerce0, ptr %0, align 16
- %1 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 1
- store i64 %y.coerce1, ptr %1, align 8
- store ptr %x, ptr %x.addr, align 8
- store i32 %cond, ptr %cond.addr, align 4
- call void @llvm.lifetime.start.p0(ptr %temp)
- call void @llvm.memcpy.p0.p0.i64(ptr align 16 %temp, ptr align 16 %y, i64 16, i1 false)
- call void @llvm.lifetime.start.p0(ptr %zero)
- call void @llvm.memset.p0.i64(ptr align 16 %zero, i8 0, i64 16, i1 false)
- call void @llvm.lifetime.start.p0(ptr %data)
- %2 = load i32, ptr %cond.addr, align 4
- %tobool = icmp ne i32 %2, 0
- br i1 %tobool, label %cond.true, label %cond.false
-
-cond.true:
- br label %cond.end
-
-cond.false:
- br label %cond.end
-
-cond.end:
- %cond1 = phi ptr [ %temp, %cond.true ], [ %zero, %cond.false ]
- %whole = load { i64, i64 }, ptr %cond1, align 16
- store { i64, i64 } %whole, ptr %data, align 16
- %3 = load ptr, ptr %x.addr, align 8
- %whole2 = load { i64, i64 }, ptr %data, align 16
- store { i64, i64 } %whole2, ptr %3, align 16
- call void @llvm.lifetime.end.p0(ptr %data)
- call void @llvm.lifetime.end.p0(ptr %zero)
- call void @llvm.lifetime.end.p0(ptr %temp)
+ %tmp = alloca %struct.i32x4, align 4
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src, i64 16, i1 false)
+ store i8 %value, ptr %alias, align 1
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 16, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %tmp)
ret void
}
-%struct.myint4_base_n = type { i32, i32, i32, i32 }
-%struct.myint4_nested = type { %struct.myint4_base_n }
-define dso_local void @foo_nested(ptr noundef %x, i64 %y.coerce0, i64 %y.coerce1, i32 noundef %cond) {
-; CHECK-LABEL: define dso_local void @foo_nested(
-; CHECK-SAME: ptr noundef [[X:%.*]], i64 [[Y_COERCE0:%.*]], i64 [[Y_COERCE1:%.*]], i32 noundef [[COND:%.*]]) {
+; Similarly, if we copy tmp to multiple destinations, the destinations may
+; alias the source, so it's not safe to get rid of tmp. Tmp ensures all
+; destinations get the original value. We can promote tmp to a value.
+define void @copy_to_multiple_destinations(ptr %src, ptr %dst0, ptr %dst1) {
+; CHECK-LABEL: define void @copy_to_multiple_destinations(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST0:%.*]], ptr [[DST1:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp eq i32 [[COND]], 0
-; CHECK-NEXT: [[DOTY_COERCE1:%.*]] = select i1 [[TOBOOL_NOT]], i64 0, i64 [[Y_COERCE1]]
-; CHECK-NEXT: [[DOTY_COERCE0:%.*]] = select i1 [[TOBOOL_NOT]], i64 0, i64 [[Y_COERCE0]]
-; CHECK-NEXT: store i64 [[DOTY_COERCE0]], ptr [[X]], align 16
-; CHECK-NEXT: [[X_REPACK7:%.*]] = getelementptr inbounds nuw i8, ptr [[X]], i64 8
-; CHECK-NEXT: store i64 [[DOTY_COERCE1]], ptr [[X_REPACK7]], align 8
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD:%.*]] = load <4 x i32>, ptr [[SRC]], align 4
+; CHECK-NEXT: store <4 x i32> [[TMP_SROA_0_0_COPYLOAD]], ptr [[DST0]], align 4
+; CHECK-NEXT: store <4 x i32> [[TMP_SROA_0_0_COPYLOAD]], ptr [[DST1]], align 4
; CHECK-NEXT: ret void
;
entry:
- %y = alloca %struct.myint4_nested, align 16
- %x.addr = alloca ptr, align 8
- %cond.addr = alloca i32, align 4
- %temp = alloca %struct.myint4_nested, align 16
- %zero = alloca %struct.myint4_nested, align 16
- %data = alloca %struct.myint4_nested, align 16
- %0 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 0
- store i64 %y.coerce0, ptr %0, align 16
- %1 = getelementptr inbounds nuw { i64, i64 }, ptr %y, i32 0, i32 1
- store i64 %y.coerce1, ptr %1, align 8
- store ptr %x, ptr %x.addr, align 8
- store i32 %cond, ptr %cond.addr, align 4
- call void @llvm.lifetime.start.p0(ptr %temp)
- call void @llvm.memcpy.p0.p0.i64(ptr align 16 %temp, ptr align 16 %y, i64 16, i1 false)
- call void @llvm.lifetime.start.p0(ptr %zero)
- call void @llvm.memset.p0.i64(ptr align 16 %zero, i8 0, i64 16, i1 false)
- call void @llvm.lifetime.start.p0(ptr %data)
- %2 = load i32, ptr %cond.addr, align 4
- %tobool = icmp ne i32 %2, 0
- br i1 %tobool, label %cond.true, label %cond.false
-
-cond.true:
- br label %cond.end
-
-cond.false:
- br label %cond.end
-
-cond.end:
- %cond1 = phi ptr [ %temp, %cond.true ], [ %zero, %cond.false ]
- %whole = load { i64, i64 }, ptr %cond1, align 16
- store { i64, i64 } %whole, ptr %data, align 16
- %3 = load ptr, ptr %x.addr, align 8
- %whole2 = load { i64, i64 }, ptr %data, align 16
- store { i64, i64 } %whole2, ptr %3, align 16
- call void @llvm.lifetime.end.p0(ptr %data)
- call void @llvm.lifetime.end.p0(ptr %zero)
- call void @llvm.lifetime.end.p0(ptr %temp)
+ %tmp = alloca %struct.i32x4, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src, i64 16, i1 false)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst0, ptr align 4 %tmp, i64 16, i1 false)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst1, ptr align 4 %tmp, i64 16, i1 false)
ret void
}
-%struct.padded = type { i32, i8, i32, i8 }
-define dso_local void @foo_padded(ptr noundef %x, i32 %a0, i8 %a1,
-; CHECK-LABEL: define dso_local void @foo_padded(
-; CHECK-SAME: ptr noundef [[X:%.*]], i32 [[A0:%.*]], i8 [[A1:%.*]], i32 [[A2:%.*]], i8 [[A3:%.*]], i32 noundef [[COND:%.*]]) {
+; Different control-flow paths can define the source. SROA promotes the
+; memory merge to a vector phi.
+define void @conditional_copy(ptr %src0, ptr %src1, ptr %dst, i1 %cond) {
+; CHECK-LABEL: define void @conditional_copy(
+; CHECK-SAME: ptr [[SRC0:%.*]], ptr [[SRC1:%.*]], ptr [[DST:%.*]], i1 [[COND:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_PADDED:%.*]], align 4
-; CHECK-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_PADDED]], align 4
-; CHECK-NEXT: [[DATA:%.*]] = alloca [[STRUCT_PADDED]], align 4
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
-; CHECK-NEXT: store i32 [[A0]], ptr [[TEMP]], align 4
-; CHECK-NEXT: [[Y_SROA_2_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 4
-; CHECK-NEXT: store i8 [[A1]], ptr [[Y_SROA_2_0_TEMP_SROA_IDX]], align 4
-; CHECK-NEXT: [[Y_SROA_31_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 8
-; CHECK-NEXT: store i32 [[A2]], ptr [[Y_SROA_31_0_TEMP_SROA_IDX]], align 4
-; CHECK-NEXT: [[Y_SROA_4_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 12
-; CHECK-NEXT: store i8 [[A3]], ptr [[Y_SROA_4_0_TEMP_SROA_IDX]], align 4
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
-; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 4 dereferenceable(16) [[ZERO]], i8 0, i64 16, i1 false)
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DATA]])
-; CHECK-NEXT: [[TOBOOL_PAD_NOT:%.*]] = icmp eq i32 [[COND]], 0
-; CHECK-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_PAD_NOT]], ptr [[ZERO]], ptr [[TEMP]]
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 4 dereferenceable(16) [[DATA]], ptr noundef nonnull align 4 dereferenceable(16) [[ZERO_TEMP]], i64 16, i1 false)
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 4 dereferenceable(16) [[X]], ptr noundef nonnull align 4 dereferenceable(16) [[DATA]], i64 16, i1 false)
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DATA]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; CHECK-NEXT: br i1 [[COND]], label %[[COPY0:.*]], label %[[COPY1:.*]]
+; CHECK: [[COPY0]]:
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD:%.*]] = load <4 x i32>, ptr [[SRC0]], align 4
+; CHECK-NEXT: br label %[[MERGE:.*]]
+; CHECK: [[COPY1]]:
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD1:%.*]] = load <4 x i32>, ptr [[SRC1]], align 4
+; CHECK-NEXT: br label %[[MERGE]]
+; CHECK: [[MERGE]]:
+; CHECK-NEXT: [[TMP_SROA_0_0:%.*]] = phi <4 x i32> [ [[TMP_SROA_0_0_COPYLOAD]], %[[COPY0]] ], [ [[TMP_SROA_0_0_COPYLOAD1]], %[[COPY1]] ]
+; CHECK-NEXT: store <4 x i32> [[TMP_SROA_0_0]], ptr [[DST]], align 4
; CHECK-NEXT: ret void
;
- i32 %a2, i8 %a3,
- i32 noundef %cond) {
entry:
- %y = alloca %struct.padded, align 4
- %x.addr = alloca ptr, align 8
- %cond.addr = alloca i32, align 4
- %temp = alloca %struct.padded, align 4
- %zero = alloca %struct.padded, align 4
- %data = alloca %struct.padded, align 4
- %y_i32_0 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 0
- store i32 %a0, ptr %y_i32_0, align 4
- %y_i8_1 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 1
- store i8 %a1, ptr %y_i8_1, align 1
- %y_i32_2 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 2
- store i32 %a2, ptr %y_i32_2, align 4
- %y_i8_3 = getelementptr inbounds %struct.padded, ptr %y, i32 0, i32 3
- store i8 %a3, ptr %y_i8_3, align 1
- store ptr %x, ptr %x.addr, align 8
- store i32 %cond, ptr %cond.addr, align 4
- call void @llvm.lifetime.start.p0(ptr %temp)
- call void @llvm.memcpy.p0.p0.i64(ptr align 4 %temp, ptr align 4 %y,
- i64 16, i1 false)
- call void @llvm.lifetime.start.p0(ptr %zero)
- call void @llvm.memset.p0.i64(ptr align 4 %zero, i8 0, i64 16, i1 false)
- call void @llvm.lifetime.start.p0(ptr %data)
- %c.pad = load i32, ptr %cond.addr, align 4
- %tobool.pad = icmp ne i32 %c.pad, 0
- br i1 %tobool.pad, label %cond.true.pad, label %cond.false.pad
+ %tmp = alloca %struct.i32x4, align 4
+ br i1 %cond, label %copy0, label %copy1
-cond.true.pad:
- br label %cond.end.pad
+copy0:
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src0, i64 16, i1 false)
+ br label %merge
-cond.false.pad:
- br label %cond.end.pad
+copy1:
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src1, i64 16, i1 false)
+ br label %merge
-cond.end.pad:
- %cond1.pad = phi ptr [ %temp, %cond.true.pad ], [ %zero, %cond.false.pad ]
- call void @llvm.memcpy.p0.p0.i64(ptr align 4 %data, ptr align 4 %cond1.pad,
- i64 16, i1 false)
- %xv.pad = load ptr, ptr %x.addr, align 8
- call void @llvm.memcpy.p0.p0.i64(ptr align 4 %xv.pad, ptr align 4 %data,
- i64 16, i1 false)
- call void @llvm.lifetime.end.p0(ptr %data)
- call void @llvm.lifetime.end.p0(ptr %zero)
- call void @llvm.lifetime.end.p0(ptr %temp)
+merge:
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 16, i1 false)
ret void
}
-%struct.nonhomo = type { i32, i64, i32, i64 }
-define dso_local void @foo_nonhomo(ptr noundef %x, i32 %a0, i64 %a1,
-; CHECK-LABEL: define dso_local void @foo_nonhomo(
-; CHECK-SAME: ptr noundef [[X:%.*]], i32 [[A0:%.*]], i64 [[A1:%.*]], i32 [[A2:%.*]], i64 [[A3:%.*]], i32 noundef [[COND:%.*]]) {
+; A fully memset source becomes a vector constant.
+define void @memset_to_constant(ptr %dst) {
+; CHECK-LABEL: define void @memset_to_constant(
+; CHECK-SAME: ptr [[DST:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_NONHOMO:%.*]], align 8
-; CHECK-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_NONHOMO]], align 8
-; CHECK-NEXT: [[DATA:%.*]] = alloca [[STRUCT_NONHOMO]], align 8
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
-; CHECK-NEXT: store i32 [[A0]], ptr [[TEMP]], align 8
-; CHECK-NEXT: [[Y_SROA_2_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 4
-; CHECK-NEXT: store i64 [[A1]], ptr [[Y_SROA_2_0_TEMP_SROA_IDX]], align 4
-; CHECK-NEXT: [[Y_SROA_3_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 12
-; CHECK-NEXT: store i32 [[A2]], ptr [[Y_SROA_3_0_TEMP_SROA_IDX]], align 4
-; CHECK-NEXT: [[Y_SROA_4_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 16
-; CHECK-NEXT: store i64 [[A3]], ptr [[Y_SROA_4_0_TEMP_SROA_IDX]], align 8
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
-; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[ZERO]], i8 0, i64 32, i1 false)
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[DATA]])
-; CHECK-NEXT: [[TOBOOL_NH_NOT:%.*]] = icmp eq i32 [[COND]], 0
-; CHECK-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_NH_NOT]], ptr [[ZERO]], ptr [[TEMP]]
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[DATA]], ptr noundef nonnull align 8 dereferenceable(32) [[ZERO_TEMP]], i64 32, i1 false)
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[X]], ptr noundef nonnull align 8 dereferenceable(32) [[DATA]], i64 32, i1 false)
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[DATA]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; CHECK-NEXT: store <4 x i32> zeroinitializer, ptr [[DST]], align 4
; CHECK-NEXT: ret void
;
- i32 %a2, i64 %a3,
- i32 noundef %cond) {
entry:
- %y = alloca %struct.nonhomo, align 8
- %x.addr = alloca ptr, align 8
- %cond.addr = alloca i32, align 4
- %temp = alloca %struct.nonhomo, align 8
- %zero = alloca %struct.nonhomo, align 8
- %data = alloca %struct.nonhomo, align 8
- %y_i32_0n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 0
- store i32 %a0, ptr %y_i32_0n, align 4
- %y_i64_1n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 1
- store i64 %a1, ptr %y_i64_1n, align 8
- %y_i32_2n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 2
- store i32 %a2, ptr %y_i32_2n, align 4
- %y_i64_3n = getelementptr inbounds %struct.nonhomo, ptr %y, i32 0, i32 3
- store i64 %a3, ptr %y_i64_3n, align 8
- store ptr %x, ptr %x.addr, align 8
- store i32 %cond, ptr %cond.addr, align 4
- call void @llvm.lifetime.start.p0(ptr %temp)
- call void @llvm.memcpy.p0.p0.i64(ptr align 8 %temp, ptr align 8 %y,
- i64 32, i1 false)
- call void @llvm.lifetime.start.p0(ptr %zero)
- call void @llvm.memset.p0.i64(ptr align 8 %zero, i8 0, i64 32, i1 false)
- call void @llvm.lifetime.start.p0(ptr %data)
- %c.nh = load i32, ptr %cond.addr, align 4
- %tobool.nh = icmp ne i32 %c.nh, 0
- br i1 %tobool.nh, label %cond.true.nh, label %cond.false.nh
-
-cond.true.nh:
- br label %cond.end.nh
+ %tmp = alloca %struct.i32x4, align 4
+ call void @llvm.memset.p0.i64(ptr align 4 %tmp, i8 0, i64 16, i1 false)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 16, i1 false)
+ ret void
+}
-cond.false.nh:
- br label %cond.end.nh
+; Partially initialized aggregate memory becomes an SSA value with undef lanes.
-cond.end.nh:
- %cond1.nh = phi ptr [ %temp, %cond.true.nh ], [ %zero, %cond.false.nh ]
- call void @llvm.memcpy.p0.p0.i64(ptr align 8 %data, ptr align 8 %cond1.nh,
- i64 32, i1 false)
- %xv.nh = load ptr, ptr %x.addr, align 8
- call void @llvm.memcpy.p0.p0.i64(ptr align 8 %xv.nh, ptr align 8 %data,
- i64 32, i1 false)
- call void @llvm.lifetime.end.p0(ptr %data)
- call void @llvm.lifetime.end.p0(ptr %zero)
- call void @llvm.lifetime.end.p0(ptr %temp)
+define void @partial_memset(ptr %dst) {
+; CHECK-LABEL: define void @partial_memset(
+; CHECK-SAME: ptr [[DST:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP_SROA_0_4_VECBLEND:%.*]] = shufflevector <4 x i32> <i32 undef, i32 0, i32 0, i32 0>, <4 x i32> undef, <4 x i32> <i32 4, i32 1, i32 2, i32 3>
+; CHECK-NEXT: store <4 x i32> [[TMP_SROA_0_4_VECBLEND]], ptr [[DST]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %tmp = alloca %struct.i32x4, align 4
+ %tail = getelementptr i8, ptr %tmp, i64 4
+ call void @llvm.memset.p0.i64(ptr align 4 %tail, i8 0, i64 12, i1 false)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 16, i1 false)
ret void
}
-%struct.i1x4 = type { i1, i1, i1, i1 }
-define dso_local void @foo_i1(ptr noundef %x, i64 %dummy0, i64 %dummy1,
-; CHECK-LABEL: define dso_local void @foo_i1(
-; CHECK-SAME: ptr noundef [[X:%.*]], i64 [[DUMMY0:%.*]], i64 [[DUMMY1:%.*]], i32 noundef [[COND:%.*]]) {
+; TODO: Lifetime intrinsics keep the whole aggregate in one partition. Partial
+; copies therefore canonicalize its type, but cannot promote the vector alloca.
+define void @partial_copy(ptr %src, ptr %dst) {
+; CHECK-LABEL: define void @partial_copy(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_I1X4:%.*]], align 1
-; CHECK-NEXT: [[ZERO:%.*]] = alloca [[STRUCT_I1X4]], align 1
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[ZERO]])
-; CHECK-NEXT: store i32 0, ptr [[ZERO]], align 1
-; CHECK-NEXT: [[TOBOOL_I1_NOT:%.*]] = icmp eq i32 [[COND]], 0
-; CHECK-NEXT: [[ZERO_TEMP:%.*]] = select i1 [[TOBOOL_I1_NOT]], ptr [[ZERO]], ptr [[TEMP]]
-; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ZERO_TEMP]], align 1
-; CHECK-NEXT: store i32 [[TMP0]], ptr [[X]], align 1
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[ZERO]])
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; CHECK-NEXT: [[TMP_SROA_0:%.*]] = alloca <4 x i32>, align 16
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP_SROA_0]])
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 16 [[TMP_SROA_0]], ptr align 4 [[SRC]], i64 12, i1 false)
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[DST]], ptr align 16 [[TMP_SROA_0]], i64 12, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP_SROA_0]])
; CHECK-NEXT: ret void
;
- i32 noundef %cond) {
entry:
- %y = alloca %struct.i1x4, align 1
- %x.addr = alloca ptr, align 8
- %cond.addr = alloca i32, align 4
- %temp = alloca %struct.i1x4, align 1
- %zero = alloca %struct.i1x4, align 1
- %data = alloca %struct.i1x4, align 1
- store ptr %x, ptr %x.addr, align 8
- store i32 %cond, ptr %cond.addr, align 4
- call void @llvm.lifetime.start.p0(ptr %temp)
- call void @llvm.memcpy.p0.p0.i64(ptr align 1 %temp, ptr align 1 %y,
- i64 4, i1 false)
- call void @llvm.lifetime.start.p0(ptr %zero)
- call void @llvm.memset.p0.i64(ptr align 1 %zero, i8 0, i64 4, i1 false)
- call void @llvm.lifetime.start.p0(ptr %data)
- %c.i1 = load i32, ptr %cond.addr, align 4
- %tobool.i1 = icmp ne i32 %c.i1, 0
- br i1 %tobool.i1, label %cond.true.i1, label %cond.false.i1
+ %tmp = alloca %struct.i32x4, align 4
+ call void @llvm.lifetime.start.p0(ptr %tmp)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src, i64 12, i1 false)
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 12, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %tmp)
+ ret void
+}
+
+; Type and layout coverage.
-cond.true.i1:
- br label %cond.end.i1
+; getTypePartition looks through the single-element wrapper and finds the
+; homogeneous inner struct.
-cond.false.i1:
- br label %cond.end.i1
+%struct.nested_i32x4 = type { %struct.i32x4 }
-cond.end.i1:
- %cond1.i1 = phi ptr [ %temp, %cond.true.i1 ], [ %zero, %cond.false.i1 ]
- call void @llvm.memcpy.p0.p0.i64(ptr align 1 %data, ptr align 1 %cond1.i1,
- i64 4, i1 false)
- %xv.i1 = load ptr, ptr %x.addr, align 8
- call void @llvm.memcpy.p0.p0.i64(ptr align 1 %xv.i1, ptr align 1 %data,
- i64 4, i1 false)
- call void @llvm.lifetime.end.p0(ptr %data)
- call void @llvm.lifetime.end.p0(ptr %zero)
- call void @llvm.lifetime.end.p0(ptr %temp)
+define void @nested_homogeneous_i32(ptr %src, ptr %dst, ptr %alias, i8 %value) {
+; CHECK-LABEL: define void @nested_homogeneous_i32(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST:%.*]], ptr [[ALIAS:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD:%.*]] = load <4 x i32>, ptr [[SRC]], align 4
+; CHECK-NEXT: store i8 [[VALUE]], ptr [[ALIAS]], align 1
+; CHECK-NEXT: store <4 x i32> [[TMP_SROA_0_0_COPYLOAD]], ptr [[DST]], align 4
+; CHECK-NEXT: ret void
+;
+entry:
+ %tmp = alloca %struct.nested_i32x4, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src, i64 16, i1 false)
+ store i8 %value, ptr %alias, align 1
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 16, i1 false)
ret void
}
-%struct.ptr4 = type { ptr, ptr, ptr, ptr }
-define dso_local void @foo_ptr(ptr noundef %x, ptr %p0, ptr %p1,
-; CHECK-LABEL: define dso_local void @foo_ptr(
-; CHECK-SAME: ptr noundef [[X:%.*]], ptr [[P0:%.*]], ptr [[P1:%.*]], ptr [[P2:%.*]], ptr [[P3:%.*]], i32 noundef [[COND:%.*]]) {
+; Homogeneous structs of integral pointers can also be canonicalized.
+
+%struct.ptrx2 = type { ptr, ptr }
+
+define void @homogeneous_ptr(ptr %src, ptr %dst, ptr %alias, i8 %value) {
+; CHECK-LABEL: define void @homogeneous_ptr(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST:%.*]], ptr [[ALIAS:%.*]], i8 [[VALUE:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[TEMP:%.*]] = alloca [[STRUCT_PTR4:%.*]], align 8
-; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[TEMP]])
-; CHECK-NEXT: store ptr [[P0]], ptr [[TEMP]], align 8
-; CHECK-NEXT: [[Y_SROA_2_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 8
-; CHECK-NEXT: store ptr [[P1]], ptr [[Y_SROA_2_0_TEMP_SROA_IDX]], align 8
-; CHECK-NEXT: [[Y_SROA_3_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 16
-; CHECK-NEXT: store ptr [[P2]], ptr [[Y_SROA_3_0_TEMP_SROA_IDX]], align 8
-; CHECK-NEXT: [[Y_SROA_4_0_TEMP_SROA_IDX:%.*]] = getelementptr inbounds nuw i8, ptr [[TEMP]], i64 24
-; CHECK-NEXT: store ptr [[P3]], ptr [[Y_SROA_4_0_TEMP_SROA_IDX]], align 8
-; CHECK-NEXT: [[TOBOOL_PTR_NOT:%.*]] = icmp eq i32 [[COND]], 0
-; CHECK-NEXT: [[DATA_SROA_0_0_COPYLOAD_PRE:%.*]] = load <4 x ptr>, ptr [[TEMP]], align 8
-; CHECK-NEXT: [[DATA_SROA_0_0_COPYLOAD:%.*]] = select i1 [[TOBOOL_PTR_NOT]], <4 x ptr> splat (ptr null), <4 x ptr> [[DATA_SROA_0_0_COPYLOAD_PRE]]
-; CHECK-NEXT: store <4 x ptr> [[DATA_SROA_0_0_COPYLOAD]], ptr [[X]], align 8
-; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[TEMP]])
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD:%.*]] = load <2 x ptr>, ptr [[SRC]], align 8
+; CHECK-NEXT: store i8 [[VALUE]], ptr [[ALIAS]], align 1
+; CHECK-NEXT: store <2 x ptr> [[TMP_SROA_0_0_COPYLOAD]], ptr [[DST]], align 8
; CHECK-NEXT: ret void
;
- ptr %p2, ptr %p3,
- i32 noundef %cond) {
entry:
- %y = alloca %struct.ptr4, align 8
- %x.addr = alloca ptr, align 8
- %cond.addr = alloca i32, align 4
- %temp = alloca %struct.ptr4, align 8
- %zero = alloca %struct.ptr4, align 8
- %data = alloca %struct.ptr4, align 8
- %y_p0 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 0
- store ptr %p0, ptr %y_p0, align 8
- %y_p1 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 1
- store ptr %p1, ptr %y_p1, align 8
- %y_p2 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 2
- store ptr %p2, ptr %y_p2, align 8
- %y_p3 = getelementptr inbounds %struct.ptr4, ptr %y, i32 0, i32 3
- store ptr %p3, ptr %y_p3, align 8
- store ptr %x, ptr %x.addr, align 8
- store i32 %cond, ptr %cond.addr, align 4
- call void @llvm.lifetime.start.p0(ptr %temp)
- call void @llvm.memcpy.p0.p0.i64(ptr align 8 %temp, ptr align 8 %y,
- i64 32, i1 false)
- call void @llvm.lifetime.start.p0(ptr %zero)
- call void @llvm.memset.p0.i64(ptr align 8 %zero, i8 0, i64 32, i1 false)
- call void @llvm.lifetime.start.p0(ptr %data)
- %c.ptr = load i32, ptr %cond.addr, align 4
- %tobool.ptr = icmp ne i32 %c.ptr, 0
- br i1 %tobool.ptr, label %cond.true.ptr, label %cond.false.ptr
+ %tmp = alloca %struct.ptrx2, align 8
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %tmp, ptr align 8 %src, i64 16, i1 false)
+ store i8 %value, ptr %alias, align 1
+ call void @llvm.memcpy.p0.p0.i64(ptr align 8 %dst, ptr align 8 %tmp, i64 16, i1 false)
+ ret void
+}
-cond.true.ptr:
- br label %cond.end.ptr
+; A <3 x i32> has a 12-byte store size but a 16-byte allocation size. The
+; struct may still be canonicalized because its layout matches the bytes
+; accessed by a vector load or store.
-cond.false.ptr:
- br label %cond.end.ptr
+%struct.i32x3 = type { i32, i32, i32 }
-cond.end.ptr:
- %cond1.ptr = phi ptr [ %temp, %cond.true.ptr ], [ %zero, %cond.false.ptr ]
- call void @llvm.memcpy.p0.p0.i64(ptr align 8 %data, ptr align 8 %cond1.ptr,
- i64 32, i1 false)
- %xv.ptr = load ptr, ptr %x.addr, align 8
- call void @llvm.memcpy.p0.p0.i64(ptr align 8 %xv.ptr, ptr align 8 %data,
- i64 32, i1 false)
- call void @llvm.lifetime.end.p0(ptr %data)
- call void @llvm.lifetime.end.p0(ptr %zero)
- call void @llvm.lifetime.end.p0(ptr %temp)
+define void @homogeneous_i32x3_volatile_memcpy(ptr %src) {
+; CHECK-LABEL: define void @homogeneous_i32x3_volatile_memcpy(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP_SROA_0:%.*]] = alloca <3 x i32>, align 16
+; CHECK-NEXT: [[TMP_SROA_0_0_COPYLOAD:%.*]] = load volatile <3 x i32>, ptr [[SRC]], align 4
+; CHECK-NEXT: store volatile <3 x i32> [[TMP_SROA_0_0_COPYLOAD]], ptr [[TMP_SROA_0]], align 16
+; CHECK-NEXT: ret void
+;
+entry:
+ %tmp = alloca %struct.i32x3, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src, i64 12, i1 true)
ret void
}
-%struct.i5x2 = type { i5, i5 }
-define void @struct_i5x2_memcpy_into_alloca(ptr %c) {
-; CHECK-LABEL: define void @struct_i5x2_memcpy_into_alloca(
-; CHECK-SAME: ptr [[C:%.*]]) {
+; Non-homogeneous structs cannot be represented by a single vector type.
+
+%struct.nonhomogeneous = type { i32, i64 }
+
+define void @nonhomogeneous(ptr %src, ptr %dst, ptr %alias, i8 %value) {
+; CHECK-LABEL: define void @nonhomogeneous(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST:%.*]], ptr [[ALIAS:%.*]], i8 [[VALUE:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[E:%.*]] = alloca [[STRUCT_I5X2:%.*]], align 1
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[E]], ptr align 1 [[C]], i32 2, i1 true)
+; CHECK-NEXT: [[TMP:%.*]] = alloca [[STRUCT_NONHOMOGENEOUS:%.*]], align 4
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[TMP]], ptr align 4 [[SRC]], i64 12, i1 false)
+; CHECK-NEXT: store i8 [[VALUE]], ptr [[ALIAS]], align 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[DST]], ptr align 4 [[TMP]], i64 12, i1 false)
; CHECK-NEXT: ret void
;
entry:
- %e = alloca %struct.i5x2, align 1
- call void @llvm.memcpy.p0.p0.i32(ptr align 1 %e, ptr align 1 %c, i32 2, i1 true)
+ %tmp = alloca %struct.nonhomogeneous, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmp, ptr align 4 %src, i64 12, i1 false)
+ store i8 %value, ptr %alias, align 1
+ call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dst, ptr align 4 %tmp, i64 12, i1 false)
ret void
}
-%struct.i32x3 = type { i32, i32, i32 }
-define void @struct_i32x3_memcpy_into_alloca(ptr %c) {
-; CHECK-LABEL: define void @struct_i32x3_memcpy_into_alloca(
-; CHECK-SAME: ptr [[C:%.*]]) {
+; Each i5 struct element occupies one byte, while <2 x i5> packs both elements
+; into two bytes with different padding. Canonicalizing would change the
+; byte-level layout.
+
+%struct.i5x2 = type { i5, i5 }
+
+define void @element_padding(ptr %src, ptr %dst, ptr %alias, i8 %value) {
+; CHECK-LABEL: define void @element_padding(
+; CHECK-SAME: ptr [[SRC:%.*]], ptr [[DST:%.*]], ptr [[ALIAS:%.*]], i8 [[VALUE:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
-; CHECK-NEXT: [[E_SROA_0:%.*]] = alloca <3 x i32>, align 16
-; CHECK-NEXT: [[E_SROA_0_0_COPYLOAD:%.*]] = load volatile <3 x i32>, ptr [[C]], align 4
-; CHECK-NEXT: store volatile <3 x i32> [[E_SROA_0_0_COPYLOAD]], ptr [[E_SROA_0]], align 16
+; CHECK-NEXT: [[TMP:%.*]] = alloca [[STRUCT_I5X2:%.*]], align 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[TMP]], ptr align 1 [[SRC]], i64 2, i1 false)
+; CHECK-NEXT: store i8 [[VALUE]], ptr [[ALIAS]], align 1
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[DST]], ptr align 1 [[TMP]], i64 2, i1 false)
; CHECK-NEXT: ret void
;
entry:
- %e = alloca %struct.i32x3, align 4
- call void @llvm.memcpy.p0.p0.i32(ptr align 4 %e, ptr align 4 %c, i32 12, i1 true)
+ %tmp = alloca %struct.i5x2, align 1
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %tmp, ptr align 1 %src, i64 2, i1 false)
+ store i8 %value, ptr %alias, align 1
+ call void @llvm.memcpy.p0.p0.i64(ptr align 1 %dst, ptr align 1 %tmp, i64 2, i1 false)
ret void
}
>From 290636488ed2d7a4e36cfca000afe2a3fe3a2c11 Mon Sep 17 00:00:00 2001
From: Yonah Goldberg <ygoldberg at nvidia.com>
Date: Thu, 20 Aug 2026 01:17:26 +0000
Subject: [PATCH 2/3] fix wording
---
llvm/test/Transforms/SROA/struct-to-vector.ll | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/llvm/test/Transforms/SROA/struct-to-vector.ll b/llvm/test/Transforms/SROA/struct-to-vector.ll
index d89923981e1f7..a16fc53b734fa 100644
--- a/llvm/test/Transforms/SROA/struct-to-vector.ll
+++ b/llvm/test/Transforms/SROA/struct-to-vector.ll
@@ -45,7 +45,13 @@ entry:
ret void
}
-; Different control-flow paths can define the source. SROA promotes the
+; Different control-flow paths can define the source, so memcpyopt doesn't do
+; anything. Currently we will promote tmp and load the source under each branch
+; and then merge them togeher for the store.
+; TODO: doing the actual memcpy src -> dst under each branch might be more
+; efficient.
+
+SROA promotes the
; memory merge to a vector phi.
define void @conditional_copy(ptr %src0, ptr %src1, ptr %dst, i1 %cond) {
; CHECK-LABEL: define void @conditional_copy(
>From 1f4ce4af039340809c9ade799f45c4cf9d982cfc Mon Sep 17 00:00:00 2001
From: Yonah Goldberg <ygoldberg at nvidia.com>
Date: Thu, 20 Aug 2026 18:03:21 +0000
Subject: [PATCH 3/3] fix
---
llvm/test/Transforms/SROA/struct-to-vector.ll | 3 ---
1 file changed, 3 deletions(-)
diff --git a/llvm/test/Transforms/SROA/struct-to-vector.ll b/llvm/test/Transforms/SROA/struct-to-vector.ll
index a16fc53b734fa..3cd925438d7ad 100644
--- a/llvm/test/Transforms/SROA/struct-to-vector.ll
+++ b/llvm/test/Transforms/SROA/struct-to-vector.ll
@@ -50,9 +50,6 @@ entry:
; and then merge them togeher for the store.
; TODO: doing the actual memcpy src -> dst under each branch might be more
; efficient.
-
-SROA promotes the
-; memory merge to a vector phi.
define void @conditional_copy(ptr %src0, ptr %src1, ptr %dst, i1 %cond) {
; CHECK-LABEL: define void @conditional_copy(
; CHECK-SAME: ptr [[SRC0:%.*]], ptr [[SRC1:%.*]], ptr [[DST:%.*]], i1 [[COND:%.*]]) {
More information about the llvm-commits
mailing list