Skip to content

[AArch64][SVE] Enable factor-3 scalable interleave operations - #200424

Open
Harry-Ramsey wants to merge 1 commit into
llvm:mainfrom
Harry-Ramsey:ramsey/sve-fix-interleave
Open

[AArch64][SVE] Enable factor-3 scalable interleave operations#200424
Harry-Ramsey wants to merge 1 commit into
llvm:mainfrom
Harry-Ramsey:ramsey/sve-fix-interleave

Conversation

@Harry-Ramsey

Copy link
Copy Markdown
Contributor

This commit enables LoopVectorize to lower scalable vector.[de]interleave3 to use SVE ld3/st3 instructions.

@llvmorg-github-actions

llvmorg-github-actions Bot commented May 29, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-backend-aarch64

Author: Harry Ramsey (Harry-Ramsey)

Changes

This commit enables LoopVectorize to lower scalable vector.[de]interleave3 to use SVE ld3/st3 instructions.


Patch is 20.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200424.diff

4 Files Affected:

  • (modified) llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp (+2-5)
  • (modified) llvm/test/Transforms/LoopVectorize/AArch64/force-target-instruction-cost.ll (+40-18)
  • (modified) llvm/test/Transforms/LoopVectorize/AArch64/sve-interleaved-accesses.ll (+15-25)
  • (modified) llvm/test/Transforms/LoopVectorize/AArch64/sve-tail-folding-option.ll (+24-18)
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 730bec428a38e..8708cb5038c91 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -5129,11 +5129,8 @@ InstructionCost AArch64TTIImpl::getInterleavedMemoryOpCost(
   if (VecTy->isScalableTy() && !ST->hasSVE())
     return InstructionCost::getInvalid();
 
-  // Scalable VFs will emit vector.[de]interleave intrinsics, and currently we
-  // only have lowering for power-of-2 factors.
-  // TODO: Add lowering for vector.[de]interleave3 intrinsics and support in
-  // InterleavedAccessPass for ld3/st3
-  if (VecTy->isScalableTy() && !isPowerOf2_32(Factor))
+  // Scalable VFs emit vector.[de]interleave intrinsics.
+  if (VecTy->isScalableTy() && Factor > TLI->getMaxSupportedInterleaveFactor())
     return InstructionCost::getInvalid();
 
   // Vectorization for masked interleaved accesses is only enabled for scalable
diff --git a/llvm/test/Transforms/LoopVectorize/AArch64/force-target-instruction-cost.ll b/llvm/test/Transforms/LoopVectorize/AArch64/force-target-instruction-cost.ll
index 31a1d95dedd3c..7c9762d57d443 100644
--- a/llvm/test/Transforms/LoopVectorize/AArch64/force-target-instruction-cost.ll
+++ b/llvm/test/Transforms/LoopVectorize/AArch64/force-target-instruction-cost.ll
@@ -439,27 +439,40 @@ define void @interleave_group(ptr %dst) #1 {
 ; COST1-NEXT:  [[ITER_CHECK:.*:]]
 ; COST1-NEXT:    br i1 false, label %[[VEC_EPILOG_SCALAR_PH:.*]], label %[[VECTOR_MAIN_LOOP_ITER_CHECK:.*]]
 ; COST1:       [[VECTOR_MAIN_LOOP_ITER_CHECK]]:
-; COST1-NEXT:    br i1 false, label %[[VEC_EPILOG_PH:.*]], label %[[VECTOR_PH:.*]]
+; COST1-NEXT:    [[TMP24:%.*]] = call i64 @llvm.vscale.i64()
+; COST1-NEXT:    [[TMP25:%.*]] = shl nuw i64 [[TMP24]], 5
+; COST1-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp ult i64 101, [[TMP25]]
+; COST1-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[VEC_EPILOG_PH:.*]], label %[[VECTOR_PH:.*]]
 ; COST1:       [[VECTOR_PH]]:
+; COST1-NEXT:    [[TMP26:%.*]] = shl nuw i64 [[TMP24]], 4
+; COST1-NEXT:    [[TMP27:%.*]] = shl nuw i64 [[TMP26]], 1
+; COST1-NEXT:    [[N_MOD_VF:%.*]] = urem i64 101, [[TMP27]]
+; COST1-NEXT:    [[N_VEC:%.*]] = sub i64 101, [[N_MOD_VF]]
 ; COST1-NEXT:    br label %[[VECTOR_BODY:.*]]
 ; COST1:       [[VECTOR_BODY]]:
 ; COST1-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
-; COST1-NEXT:    [[TMP0:%.*]] = add i64 [[INDEX]], 16
+; COST1-NEXT:    [[TMP28:%.*]] = add i64 [[TMP26]], 0
+; COST1-NEXT:    [[TMP5:%.*]] = mul i64 [[TMP28]], 1
+; COST1-NEXT:    [[TMP0:%.*]] = add i64 [[INDEX]], [[TMP5]]
 ; COST1-NEXT:    [[TMP1:%.*]] = mul i64 [[INDEX]], 3
 ; COST1-NEXT:    [[TMP2:%.*]] = mul i64 [[TMP0]], 3
 ; COST1-NEXT:    [[TMP3:%.*]] = getelementptr i8, ptr [[DST]], i64 [[TMP1]]
 ; COST1-NEXT:    [[TMP4:%.*]] = getelementptr i8, ptr [[DST]], i64 [[TMP2]]
-; COST1-NEXT:    store <48 x i8> zeroinitializer, ptr [[TMP3]], align 1
-; COST1-NEXT:    store <48 x i8> zeroinitializer, ptr [[TMP4]], align 1
-; COST1-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 32
-; COST1-NEXT:    [[TMP5:%.*]] = icmp eq i64 [[INDEX_NEXT]], 96
-; COST1-NEXT:    br i1 [[TMP5]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP15:![0-9]+]]
+; COST1-NEXT:    [[INTERLEAVED_VEC:%.*]] = call <vscale x 48 x i8> @llvm.vector.interleave3.nxv48i8(<vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer)
+; COST1-NEXT:    store <vscale x 48 x i8> [[INTERLEAVED_VEC]], ptr [[TMP3]], align 1
+; COST1-NEXT:    [[INTERLEAVED_VEC1:%.*]] = call <vscale x 48 x i8> @llvm.vector.interleave3.nxv48i8(<vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer)
+; COST1-NEXT:    store <vscale x 48 x i8> [[INTERLEAVED_VEC1]], ptr [[TMP4]], align 1
+; COST1-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], [[TMP27]]
+; COST1-NEXT:    [[TMP29:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; COST1-NEXT:    br i1 [[TMP29]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP15:![0-9]+]]
 ; COST1:       [[MIDDLE_BLOCK]]:
-; COST1-NEXT:    br i1 false, [[EXIT:label %.*]], label %[[VEC_EPILOG_ITER_CHECK:.*]]
+; COST1-NEXT:    [[CMP_N:%.*]] = icmp eq i64 101, [[N_VEC]]
+; COST1-NEXT:    br i1 [[CMP_N]], [[EXIT:label %.*]], label %[[VEC_EPILOG_ITER_CHECK:.*]]
 ; COST1:       [[VEC_EPILOG_ITER_CHECK]]:
-; COST1-NEXT:    br i1 false, label %[[VEC_EPILOG_SCALAR_PH]], label %[[VEC_EPILOG_PH]], !prof [[PROF3]]
+; COST1-NEXT:    [[MIN_EPILOG_ITERS_CHECK:%.*]] = icmp ult i64 [[N_MOD_VF]], 4
+; COST1-NEXT:    br i1 [[MIN_EPILOG_ITERS_CHECK]], label %[[VEC_EPILOG_SCALAR_PH]], label %[[VEC_EPILOG_PH]], !prof [[PROF3]]
 ; COST1:       [[VEC_EPILOG_PH]]:
-; COST1-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ 96, %[[VEC_EPILOG_ITER_CHECK]] ], [ 0, %[[VECTOR_MAIN_LOOP_ITER_CHECK]] ]
+; COST1-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ [[N_VEC]], %[[VEC_EPILOG_ITER_CHECK]] ], [ 0, %[[VECTOR_MAIN_LOOP_ITER_CHECK]] ]
 ; COST1-NEXT:    [[BROADCAST_SPLATINSERT:%.*]] = insertelement <4 x i64> poison, i64 [[BC_RESUME_VAL]], i64 0
 ; COST1-NEXT:    [[BROADCAST_SPLAT:%.*]] = shufflevector <4 x i64> [[BROADCAST_SPLATINSERT]], <4 x i64> poison, <4 x i32> zeroinitializer
 ; COST1-NEXT:    [[INDUCTION:%.*]] = add <4 x i64> [[BROADCAST_SPLAT]], <i64 0, i64 1, i64 2, i64 3>
@@ -509,23 +522,32 @@ define void @interleave_group(ptr %dst) #1 {
 ; COST10-NEXT:  [[ITER_CHECK:.*:]]
 ; COST10-NEXT:    br i1 false, label %[[VEC_EPILOG_SCALAR_PH:.*]], label %[[VECTOR_MAIN_LOOP_ITER_CHECK:.*]]
 ; COST10:       [[VECTOR_MAIN_LOOP_ITER_CHECK]]:
-; COST10-NEXT:    br i1 false, label %[[VEC_EPILOG_PH:.*]], label %[[VECTOR_PH:.*]]
+; COST10-NEXT:    [[TMP21:%.*]] = call i64 @llvm.vscale.i64()
+; COST10-NEXT:    [[TMP22:%.*]] = shl nuw i64 [[TMP21]], 4
+; COST10-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp ult i64 101, [[TMP22]]
+; COST10-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[VEC_EPILOG_PH:.*]], label %[[VECTOR_PH:.*]]
 ; COST10:       [[VECTOR_PH]]:
+; COST10-NEXT:    [[TMP2:%.*]] = shl nuw i64 [[TMP21]], 4
+; COST10-NEXT:    [[N_MOD_VF:%.*]] = urem i64 101, [[TMP2]]
+; COST10-NEXT:    [[N_VEC:%.*]] = sub i64 101, [[N_MOD_VF]]
 ; COST10-NEXT:    br label %[[VECTOR_BODY:.*]]
 ; COST10:       [[VECTOR_BODY]]:
 ; COST10-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; COST10-NEXT:    [[TMP0:%.*]] = mul i64 [[INDEX]], 3
 ; COST10-NEXT:    [[TMP1:%.*]] = getelementptr i8, ptr [[DST]], i64 [[TMP0]]
-; COST10-NEXT:    store <48 x i8> zeroinitializer, ptr [[TMP1]], align 1
-; COST10-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 16
-; COST10-NEXT:    [[TMP2:%.*]] = icmp eq i64 [[INDEX_NEXT]], 96
-; COST10-NEXT:    br i1 [[TMP2]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP15:![0-9]+]]
+; COST10-NEXT:    [[INTERLEAVED_VEC:%.*]] = call <vscale x 48 x i8> @llvm.vector.interleave3.nxv48i8(<vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer)
+; COST10-NEXT:    store <vscale x 48 x i8> [[INTERLEAVED_VEC]], ptr [[TMP1]], align 1
+; COST10-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], [[TMP2]]
+; COST10-NEXT:    [[TMP23:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; COST10-NEXT:    br i1 [[TMP23]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP15:![0-9]+]]
 ; COST10:       [[MIDDLE_BLOCK]]:
-; COST10-NEXT:    br i1 false, [[EXIT:label %.*]], label %[[VEC_EPILOG_ITER_CHECK:.*]]
+; COST10-NEXT:    [[CMP_N:%.*]] = icmp eq i64 101, [[N_VEC]]
+; COST10-NEXT:    br i1 [[CMP_N]], [[EXIT:label %.*]], label %[[VEC_EPILOG_ITER_CHECK:.*]]
 ; COST10:       [[VEC_EPILOG_ITER_CHECK]]:
-; COST10-NEXT:    br i1 false, label %[[VEC_EPILOG_SCALAR_PH]], label %[[VEC_EPILOG_PH]], !prof [[PROF3]]
+; COST10-NEXT:    [[MIN_EPILOG_ITERS_CHECK:%.*]] = icmp ult i64 [[N_MOD_VF]], 4
+; COST10-NEXT:    br i1 [[MIN_EPILOG_ITERS_CHECK]], label %[[VEC_EPILOG_SCALAR_PH]], label %[[VEC_EPILOG_PH]], !prof [[PROF3]]
 ; COST10:       [[VEC_EPILOG_PH]]:
-; COST10-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ 96, %[[VEC_EPILOG_ITER_CHECK]] ], [ 0, %[[VECTOR_MAIN_LOOP_ITER_CHECK]] ]
+; COST10-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ [[N_VEC]], %[[VEC_EPILOG_ITER_CHECK]] ], [ 0, %[[VECTOR_MAIN_LOOP_ITER_CHECK]] ]
 ; COST10-NEXT:    [[BROADCAST_SPLATINSERT:%.*]] = insertelement <4 x i64> poison, i64 [[BC_RESUME_VAL]], i64 0
 ; COST10-NEXT:    [[BROADCAST_SPLAT:%.*]] = shufflevector <4 x i64> [[BROADCAST_SPLATINSERT]], <4 x i64> poison, <4 x i32> zeroinitializer
 ; COST10-NEXT:    [[INDUCTION:%.*]] = add <4 x i64> [[BROADCAST_SPLAT]], <i64 0, i64 1, i64 2, i64 3>
diff --git a/llvm/test/Transforms/LoopVectorize/AArch64/sve-interleaved-accesses.ll b/llvm/test/Transforms/LoopVectorize/AArch64/sve-interleaved-accesses.ll
index 0923260c82a86..31d5a5aeabf3b 100644
--- a/llvm/test/Transforms/LoopVectorize/AArch64/sve-interleaved-accesses.ll
+++ b/llvm/test/Transforms/LoopVectorize/AArch64/sve-interleaved-accesses.ll
@@ -1332,8 +1332,6 @@ end:
 ;   dst[i].z = a[i].z << b[i].z;
 ; }
 ;
-; TODO: Support scalable interleave groups once we can also codegen
-; @llvm.[de]interleave3
 %struct.xyz = type { i32, i32, i32 }
 
 define void @interleave_deinterleave_factor3(ptr writeonly noalias %dst, ptr readonly %a, ptr readonly %b) {
@@ -1347,36 +1345,28 @@ define void @interleave_deinterleave_factor3(ptr writeonly noalias %dst, ptr rea
 ; CHECK-NEXT:    [[TMP3:%.*]] = shl nuw i64 [[TMP0]], 2
 ; CHECK-NEXT:    [[N_MOD_VF:%.*]] = urem i64 1024, [[TMP3]]
 ; CHECK-NEXT:    [[N_VEC:%.*]] = sub i64 1024, [[N_MOD_VF]]
-; CHECK-NEXT:    [[TMP6:%.*]] = call <vscale x 4 x i64> @llvm.stepvector.nxv4i64()
-; CHECK-NEXT:    [[DOTSPLATINSERT:%.*]] = insertelement <vscale x 4 x i64> poison, i64 [[TMP3]], i64 0
-; CHECK-NEXT:    [[DOTSPLAT:%.*]] = shufflevector <vscale x 4 x i64> [[DOTSPLATINSERT]], <vscale x 4 x i64> poison, <vscale x 4 x i32> zeroinitializer
 ; CHECK-NEXT:    br label [[VECTOR_BODY:%.*]]
 ; CHECK:       vector.body:
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, [[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], [[VECTOR_BODY]] ]
-; CHECK-NEXT:    [[VEC_IND:%.*]] = phi <vscale x 4 x i64> [ [[TMP6]], [[VECTOR_PH]] ], [ [[VEC_IND_NEXT:%.*]], [[VECTOR_BODY]] ]
-; CHECK-NEXT:    [[TMP19:%.*]] = getelementptr inbounds [[STRUCT_XYZ:%.*]], ptr [[A:%.*]], <vscale x 4 x i64> [[VEC_IND]]
-; CHECK-NEXT:    [[TMP7:%.*]] = call <vscale x 4 x i32> @llvm.masked.gather.nxv4i32.nxv4p0(<vscale x 4 x ptr> align 4 [[TMP19]], <vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> poison)
-; CHECK-NEXT:    [[TMP20:%.*]] = getelementptr inbounds [[STRUCT_XYZ]], ptr [[B:%.*]], <vscale x 4 x i64> [[VEC_IND]]
-; CHECK-NEXT:    [[TMP11:%.*]] = call <vscale x 4 x i32> @llvm.masked.gather.nxv4i32.nxv4p0(<vscale x 4 x ptr> align 4 [[TMP20]], <vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> poison)
+; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr inbounds [[STRUCT_XYZ:%.*]], ptr [[A:%.*]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_VEC:%.*]] = load <vscale x 12 x i32>, ptr [[TMP4]], align 4
+; CHECK-NEXT:    [[STRIDED_VEC:%.*]] = call { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } @llvm.vector.deinterleave3.nxv12i32(<vscale x 12 x i32> [[WIDE_VEC]])
+; CHECK-NEXT:    [[TMP7:%.*]] = extractvalue { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } [[STRIDED_VEC]], 0
+; CHECK-NEXT:    [[TMP8:%.*]] = extractvalue { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } [[STRIDED_VEC]], 1
+; CHECK-NEXT:    [[TMP9:%.*]] = extractvalue { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } [[STRIDED_VEC]], 2
+; CHECK-NEXT:    [[TMP10:%.*]] = getelementptr inbounds [[STRUCT_XYZ]], ptr [[B:%.*]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_VEC1:%.*]] = load <vscale x 12 x i32>, ptr [[TMP10]], align 4
+; CHECK-NEXT:    [[STRIDED_VEC2:%.*]] = call { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } @llvm.vector.deinterleave3.nxv12i32(<vscale x 12 x i32> [[WIDE_VEC1]])
+; CHECK-NEXT:    [[TMP11:%.*]] = extractvalue { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } [[STRIDED_VEC2]], 0
+; CHECK-NEXT:    [[TMP12:%.*]] = extractvalue { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } [[STRIDED_VEC2]], 1
+; CHECK-NEXT:    [[TMP13:%.*]] = extractvalue { <vscale x 4 x i32>, <vscale x 4 x i32>, <vscale x 4 x i32> } [[STRIDED_VEC2]], 2
 ; CHECK-NEXT:    [[TMP14:%.*]] = add nsw <vscale x 4 x i32> [[TMP11]], [[TMP7]]
-; CHECK-NEXT:    [[TMP10:%.*]] = getelementptr inbounds [[STRUCT_XYZ]], ptr [[DST:%.*]], <vscale x 4 x i64> [[VEC_IND]]
-; CHECK-NEXT:    call void @llvm.masked.scatter.nxv4i32.nxv4p0(<vscale x 4 x i32> [[TMP14]], <vscale x 4 x ptr> align 4 [[TMP10]], <vscale x 4 x i1> splat (i1 true))
-; CHECK-NEXT:    [[TMP21:%.*]] = getelementptr inbounds nuw i8, <vscale x 4 x ptr> [[TMP19]], i64 4
-; CHECK-NEXT:    [[TMP8:%.*]] = call <vscale x 4 x i32> @llvm.masked.gather.nxv4i32.nxv4p0(<vscale x 4 x ptr> align 4 [[TMP21]], <vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> poison)
-; CHECK-NEXT:    [[TMP22:%.*]] = getelementptr inbounds nuw i8, <vscale x 4 x ptr> [[TMP20]], i64 4
-; CHECK-NEXT:    [[TMP12:%.*]] = call <vscale x 4 x i32> @llvm.masked.gather.nxv4i32.nxv4p0(<vscale x 4 x ptr> align 4 [[TMP22]], <vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> poison)
+; CHECK-NEXT:    [[TMP15:%.*]] = getelementptr inbounds [[STRUCT_XYZ]], ptr [[DST:%.*]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[TMP16:%.*]] = sub nsw <vscale x 4 x i32> [[TMP8]], [[TMP12]]
-; CHECK-NEXT:    [[TMP23:%.*]] = getelementptr inbounds nuw i8, <vscale x 4 x ptr> [[TMP10]], i64 4
-; CHECK-NEXT:    call void @llvm.masked.scatter.nxv4i32.nxv4p0(<vscale x 4 x i32> [[TMP16]], <vscale x 4 x ptr> align 4 [[TMP23]], <vscale x 4 x i1> splat (i1 true))
-; CHECK-NEXT:    [[TMP15:%.*]] = getelementptr inbounds nuw i8, <vscale x 4 x ptr> [[TMP19]], i64 8
-; CHECK-NEXT:    [[TMP9:%.*]] = call <vscale x 4 x i32> @llvm.masked.gather.nxv4i32.nxv4p0(<vscale x 4 x ptr> align 4 [[TMP15]], <vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> poison)
-; CHECK-NEXT:    [[TMP24:%.*]] = getelementptr inbounds nuw i8, <vscale x 4 x ptr> [[TMP20]], i64 8
-; CHECK-NEXT:    [[TMP13:%.*]] = call <vscale x 4 x i32> @llvm.masked.gather.nxv4i32.nxv4p0(<vscale x 4 x ptr> align 4 [[TMP24]], <vscale x 4 x i1> splat (i1 true), <vscale x 4 x i32> poison)
 ; CHECK-NEXT:    [[TMP17:%.*]] = shl <vscale x 4 x i32> [[TMP9]], [[TMP13]]
-; CHECK-NEXT:    [[TMP25:%.*]] = getelementptr inbounds nuw i8, <vscale x 4 x ptr> [[TMP10]], i64 8
-; CHECK-NEXT:    call void @llvm.masked.scatter.nxv4i32.nxv4p0(<vscale x 4 x i32> [[TMP17]], <vscale x 4 x ptr> align 4 [[TMP25]], <vscale x 4 x i1> splat (i1 true))
+; CHECK-NEXT:    [[INTERLEAVED_VEC:%.*]] = call <vscale x 12 x i32> @llvm.vector.interleave3.nxv12i32(<vscale x 4 x i32> [[TMP14]], <vscale x 4 x i32> [[TMP16]], <vscale x 4 x i32> [[TMP17]])
+; CHECK-NEXT:    store <vscale x 12 x i32> [[INTERLEAVED_VEC]], ptr [[TMP15]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], [[TMP3]]
-; CHECK-NEXT:    [[VEC_IND_NEXT]] = add nuw nsw <vscale x 4 x i64> [[VEC_IND]], [[DOTSPLAT]]
 ; CHECK-NEXT:    [[TMP18:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
 ; CHECK-NEXT:    br i1 [[TMP18]], label [[MIDDLE_BLOCK:%.*]], label [[VECTOR_BODY]], !llvm.loop [[LOOP41:![0-9]+]]
 ; CHECK:       middle.block:
diff --git a/llvm/test/Transforms/LoopVectorize/AArch64/sve-tail-folding-option.ll b/llvm/test/Transforms/LoopVectorize/AArch64/sve-tail-folding-option.ll
index 21d5a2e74028b..43a38193d5585 100644
--- a/llvm/test/Transforms/LoopVectorize/AArch64/sve-tail-folding-option.ll
+++ b/llvm/test/Transforms/LoopVectorize/AArch64/sve-tail-folding-option.ll
@@ -283,39 +283,45 @@ for.end:
 define void @interleave(ptr noalias %dst, ptr noalias %src, i64 %n) #0 {
 ; CHECK-NOTF-LABEL: @interleave(
 ; CHECK-NOTF:       vector.body:
-; CHECK-NOTF:         %[[LOAD:.*]] = load <8 x float>, ptr
-; CHECK-NOTF:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NOTF:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+; CHECK-NOTF:         %[[LOAD:.*]] = load <vscale x 8 x float>, ptr
+; CHECK-NOTF:         %{{.*}} = call { <vscale x 4 x float>, <vscale x 4 x float> } @llvm.vector.deinterleave2.nxv8f32(<vscale x 8 x float> %[[LOAD]])
+; CHECK-NOTF:         %{{.*}} = call <vscale x 12 x float> @llvm.vector.interleave3.nxv12f32
+; CHECK-NOTF:         store <vscale x 12 x float>
 
 ; CHECK-TF-LABEL: @interleave(
 ; CHECK-TF:       vector.body:
-; CHECK-TF:         %[[LOAD:.*]] = load <8 x float>, ptr
-; CHECK-TF:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-TF:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+; CHECK-TF:         %[[LOAD:.*]] = load <vscale x 8 x float>, ptr
+; CHECK-TF:         %{{.*}} = call { <vscale x 4 x float>, <vscale x 4 x float> } @llvm.vector.deinterleave2.nxv8f32(<vscale x 8 x float> %[[LOAD]])
+; CHECK-TF:         %{{.*}} = call <vscale x 12 x float> @llvm.vector.interleave3.nxv12f32
+; CHECK-TF:         store <vscale x 12 x float>
 
 ; CHECK-TF-NORED-LABEL: @interleave(
 ; CHECK-TF-NORED:       vector.body:
-; CHECK-TF-NORED:         %[[LOAD:.*]] = load <8 x float>, ptr
-; CHECK-TF-NORED:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-TF-NORED:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+; CHECK-TF-NORED:         %[[LOAD:.*]] = load <vscale x 8 x float>, ptr
+; CHECK-TF-NORED:         %{{.*}} = call { <vscale x 4 x float>, <vscale x 4 x float> } @llvm.vector.deinterleave2.nxv8f32(<vscale x 8 x float> %[[LOAD]])
+; CHECK-TF-NORED:         %{{.*}} = call <vscale x 12 x float> @llvm.vector.interleave3.nxv12f32
+; CHECK-TF-NORED:         store <vscale x 12 x float>
 
 ; CHECK-TF-NOREC-LABEL: @interleave(
 ; CHECK-TF-NOREC:       vector.body:
-; CHECK-TF-NOREC:         %[[LOAD:.*]] = load <8 x float>, ptr
-; CHECK-TF-NOREC:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-TF-NOREC:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+; CHECK-TF-NOREC:         %[[LOAD:.*]] = load <vscale x 8 x float>, ptr
+; CHECK-TF-NOREC:         %{{.*}} = call { <vscale x 4 x float>, <vscale x 4 x float> } @llvm.vector.deinterleave2.nxv8f32(<vscale x 8 x float> %[[LOAD]])
+; CHECK-TF-NOREC:         %{{.*}} = call <vscale x 12 x float> @llvm.vector.interleave3.nxv12f32
+; CHECK-TF-NOREC:         store <vscale x 12 x float>
 
 ; CHECK-TF-NOREV-LABEL: @interleave(
 ; CHECK-TF-NOREV:       vector.body:
-; CHECK-TF-NOREV:         %[[LOAD:.*]] = load <8 x float>, ptr
-; CHECK-TF-NOREV:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-TF-NOREV:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+; CHECK-TF-NOREV:         %[[LOAD:.*]] = load <vscale x 8 x float>, ptr
+; CHECK-TF-NOREV:         %{{.*}} = call { <vscale x 4 x float>, <vscale x 4 x float> } @llvm.vector.deinterleave2.nxv8f32(<vscale x 8 x float> %[[LOAD]])
+; CHECK-TF-NOREV:         %{{.*}} = call <vscale x 12 x float> @llvm.vector.interleave3.nxv12f32
+; CHECK-TF-NOREV:         store <vscale x 12 x float>
 
 ; CHECK-NEOVERSE-V1-LABEL: @interleave(
 ; CHECK-NEOVERSE-V1:       vector.body:
-; CHECK-NEOVERSE-V1:         %[[LOAD:.*]] = load <8 x float>, ptr
-; CHECK-NEOVERSE-V1:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
-; CHECK-NEOVERSE-V1:         %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
+; CHECK-NEOVERSE-V1:         %[[LOAD:.*]] = load <vscale x 8 x float>, ptr
+; CHECK-NEOVERSE-V1:         %{{.*}} = call { <vscale x 4 x float>, <vscale x 4 x float> } @llv...
[truncated]

@david-arm

Copy link
Copy Markdown
Contributor

I assume this patch is waiting on the codegen side of things?

@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch from 66f28b8 to ff301a9 Compare June 29, 2026 10:55
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the undef deprecator.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 201571 tests passed
  • 5543 tests skipped

✅ The build succeeded and all tests passed.

@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch from ff301a9 to 8a49c4f Compare July 3, 2026 16:42
@Harry-Ramsey
Harry-Ramsey marked this pull request as draft July 3, 2026 16:42
@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch from 8a49c4f to d6a0f2b Compare July 9, 2026 14:52
@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch 2 times, most recently from edf7470 to 41c9213 Compare July 15, 2026 09:33
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 139432 tests passed
  • 3644 tests skipped

✅ The build succeeded and all tests passed.

@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch 3 times, most recently from e495a98 to f6d54f9 Compare July 16, 2026 12:25
@Harry-Ramsey
Harry-Ramsey marked this pull request as ready for review July 16, 2026 12:33
@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch 3 times, most recently from 9f74882 to a1c6c6b Compare July 20, 2026 11:32
; COST1-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 32
; COST1-NEXT: [[TMP5:%.*]] = icmp eq i64 [[INDEX_NEXT]], 96
; COST1-NEXT: br i1 [[TMP5]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP15:![0-9]+]]
; COST1-NEXT: [[INTERLEAVED_VEC:%.*]] = call <vscale x 48 x i8> @llvm.vector.interleave3.nxv48i8(<vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this looks like a bad decision by the loop vectoriser and could lead to regressions. Perhaps the code gets cleaned up in a later pass? I'll play around with this patch for a bit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this isn't a problem for the common case when we're not forcing target costs. For example, for C code like this:

void foo(unsigned char *dst, int n) {
  for (int i = 0; i < n; i += 3) {
    dst[(i * 3) + 2] = 3;
    dst[(i * 3) + 1] = 3;
    dst[i * 3] = 3;
  }
}

when compiling with clang -O3 -mcpu=neoverse-v1 -S -emit-llvm foo.c we end up replicating instead. What's interesting about this case is that the vectorised form (store <48 x i8> zeroinitializer, ptr [[TMP3]], align 1) may actually be the best, but the cost model still prefers replication.

@@ -1,9 +1,10 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very hard to see what's actually changed in this diff. Can you either:

  1. Pre-commit a NFC patch to automatically generate CHECK lines, then rebase this PR on top? Or,
  2. Keep the existing style of the test without autogeneration and manually update the CHECK lines in a similar way?

Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment still not addressed, although I've added a new suggestion for updating the @interleave function in a pre-commit PR.

; COST1-NEXT: [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 32
; COST1-NEXT: [[TMP5:%.*]] = icmp eq i64 [[INDEX_NEXT]], 96
; COST1-NEXT: br i1 [[TMP5]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP15:![0-9]+]]
; COST1-NEXT: [[INTERLEAVED_VEC:%.*]] = call <vscale x 48 x i8> @llvm.vector.interleave3.nxv48i8(<vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer, <vscale x 16 x i8> zeroinitializer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this isn't a problem for the common case when we're not forcing target costs. For example, for C code like this:

void foo(unsigned char *dst, int n) {
  for (int i = 0; i < n; i += 3) {
    dst[(i * 3) + 2] = 3;
    dst[(i * 3) + 1] = 3;
    dst[i * 3] = 3;
  }
}

when compiling with clang -O3 -mcpu=neoverse-v1 -S -emit-llvm foo.c we end up replicating instead. What's interesting about this case is that the vectorised form (store <48 x i8> zeroinitializer, ptr [[TMP3]], align 1) may actually be the best, but the cost model still prefers replication.

return InstructionCost::getInvalid();

if (Factor == 3 &&
DL.getTypeSizeInBits(VecTy).getKnownMinValue() < Factor * 128)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggests we support things like:

  %wide.masked.vec = call <vscale x 96 x i8> @llvm.masked.load.nxv96i8(ptr %p, i32 1, <vscale x 96 x i1> %interleaved.mask, <vscale x 96 x i8> poison)
  %deinterleaved.vec = call { <vscale x 32 x i8>, <vscale x 32 x i8>, <vscale x 32 x i8> } @llvm.vector.deinterleave3.nxv96i8(<vscale x 96 x i8> %wide.masked.vec)

However, I don't think the loop vectoriser will generate code like that. Perhaps for now it's worth being more precise, i.e.

    if (Factor == 3 &&
        DL.getTypeSizeInBits(VecTy).getKnownMinValue() != (Factor * 128))
      return InstructionCost::getInvalid();

} else if (!isSplatVectorInterleaveOps(DAG, DL, WideValue,
ValueInterleaveOps)) {
return SDValue();
// A vector.interleave3 defined in another basic block is unknown here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this only a problem for factor 3 interleaves and not 2 or 4?

@Harry-Ramsey Harry-Ramsey Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Across blocks, we cannot see the original DAG node. I believe for 2 and 4 we can use the power-of-two to identify if it is compatible. We do not have that with factor of 3, so I have had to implement deinterleaveInterleavedValueForSVESt3 as a way to help with that.

Additionally, it is why there is a test specific to factor-3.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to include deinterleaveInterleavedValueForSVESt3 in this PR then as it complicates things. I personally think it makes sense for this initial PR to be purely about adding the ability to lower simple cases to ld3/st3. Then a follow-on PR can add deinterleaveInterleavedValueForSVESt3 and be reviewed separately.

ret void
}

; This test is specific to interleaves of factor 3.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why specific to factor 3? I don't see anything in the IR that's specifically a problem for factor 3.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is really problem then perhaps we should be adding similar tests for factors 2 and 4?

@davemgreen

Copy link
Copy Markdown
Contributor

Sorry for the delay, there is a release that we were prioritising first. Can we make sure that i1 interleaves / deinterleaves work first, if they might be generated by the vectorizer:
https://godbolt.org/z/PrWf1ThbK

@david-arm

Copy link
Copy Markdown
Contributor

Sorry for the delay, there is a release that we were prioritising first. Can we make sure that i1 interleaves / deinterleaves work first, if they might be generated by the vectorizer: https://godbolt.org/z/PrWf1ThbK

There was an unsafe PR for i1 deinterleaves/interleaves here: #209231.

@davemgreen

Copy link
Copy Markdown
Contributor

There was an unsafe PR for i1 deinterleaves/interleaves here: #209231.

That was something a little different. This is for standard i1 intrinsics, not deinterleaving-loads / stores. They might need to be lowered to stack loads/stores still, but should do so in a way that uses valid types not attempt to store the i1 vectors directly.

@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch 3 times, most recently from 3e02970 to b6775d6 Compare July 22, 2026 11:09
@Harry-Ramsey

Copy link
Copy Markdown
Contributor Author

After some investigating, I believe there will be some potential unoptimal code generation regressions for enabling LD3/ST3 in the loop vectorizer.

; CHECK-NEXT: addvl sp, sp, #3
; CHECK-NEXT: ldr x29, [sp], #16 // 8-byte Folded Reload
; CHECK-NEXT: ret
%retval = call {<vscale x 8 x i1>, <vscale x 8 x i1>, <vscale x 8 x i1>} @llvm.vector.deinterleave3.nxv24i1(<vscale x 24 x i1> %vec)

@Harry-Ramsey Harry-Ramsey Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, we were unable to generate tests interleave/deinterleave for values of i1.

@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch 2 times, most recently from bdb3c40 to 10bd8e3 Compare July 30, 2026 16:21

@david-arm david-arm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I think this PR needs splitting up into at least two PRs. I think we should sort out all the codegen issues first before enabling loop vectorisation of factor-3 interleaving loads and stores. Roughly I think it needs to be something like:

  1. Add the changes in performInterleavedStoreCombine and performVectorDeinterleaveCombine for the simple load/store without the specialisations added in deinterleaveInterleavedValueForSVESt3.
  2. Add the specialisations added in deinterleaveInterleavedValueForSVESt3. I'd also like to understand more thoroughly why we don't need similar tests and logic for factor-2 and factor-4.
  3. Once the backend can support everything the loop vectoriser can throw at it, then we can fix the cost model.

At the moment the PR is difficult to review and assess because there is quite a lot going on. Also, if there is any kind of regression post-commit you'll need to revert everything including the codegen changes, which would be a shame.

; RUN: opt < %s -passes=loop-vectorize -sve-tail-folding-insn-threshold=0 -sve-tail-folding=simple+reductions+recurrences+reverse -S | FileCheck %s -check-prefix=CHECK-TF
; RUN: opt < %s -passes=loop-vectorize -sve-tail-folding-insn-threshold=0 -S -mcpu=neoverse-v1 -sve-tail-folding=default+reductions+recurrences+reverse | FileCheck %s -check-prefix=CHECK-TF
; RUN: opt < %s -passes=loop-vectorize -sve-tail-folding-insn-threshold=0 -S -mcpu=neoverse-v1 -sve-tail-folding=default+reductions+recurrences+reverse | FileCheck %s -check-prefix=CHECK-TF-DEFAULT

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this isn't really the tail-folding default because it also includes reductions+recurrences+reverse loops. I think this should be the same as this RUN line:

; RUN: opt < %s -passes=loop-vectorize -sve-tail-folding-insn-threshold=0 -sve-tail-folding=simple+reductions+recurrences+reverse -S | FileCheck %s -check-prefix=CHECK-TF

; CHECK-NEOVERSE-V1: %[[LOAD:.*]] = load <8 x float>, ptr
; CHECK-NEOVERSE-V1: %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 0, i32 2, i32 4, i32 6>
; CHECK-NEOVERSE-V1: %{{.*}} = shufflevector <8 x float> %[[LOAD]], <8 x float> poison, <4 x i32> <i32 1, i32 3, i32 5, i32 7>
; CHECK-NOTF-LABEL: define void @interleave(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the CHECK lines prior to this PR were much nicer and I'd prefer not to create thousands more CHECK lines when we only really care about a few specific things. It looks like the only reason you've had to change this file is because the @interleave function is using factor-3 interleaves that are affected by this PR. I think it would be better to have a separate pre-commit PR that changes this specific test to use factor-4 or factor-2 interleaved accesses. We'd still be testing that tail-folding wasn't enabled by default, but it would then mean the test is unaffected by this PR.

@@ -1,9 +1,10 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment still not addressed, although I've added a new suggestion for updating the @interleave function in a pre-commit PR.

} else if (!isSplatVectorInterleaveOps(DAG, DL, WideValue,
ValueInterleaveOps)) {
return SDValue();
// A vector.interleave3 defined in another basic block is unknown here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to include deinterleaveInterleavedValueForSVESt3 in this PR then as it complicates things. I personally think it makes sense for this initial PR to be purely about adding the ability to lower simple cases to ld3/st3. Then a follow-on PR can add deinterleaveInterleavedValueForSVESt3 and be reviewed separately.

ret void
}

; This test is specific to interleaves of factor 3.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is really problem then perhaps we should be adding similar tests for factors 2 and 4?

DAG.getMemBasePlusOffset(StackPtr, PackedVT.getStoreSize() * I, DL);
SDValue L = DAG.getLoad(PackedVT, DL, Chain, Ptr, MachinePointerInfo());
Results.push_back(getSVESafeBitCast(OpVT, L, DAG));
Results.push_back(IsPredicate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks more complicated than it should be. I was expecting this PR to be very simple and just a case of reusing the existing lowering code for factors 2 and 4. Why do we need the IsPredicate check?

@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch from 10bd8e3 to 454dfa0 Compare August 3, 2026 09:55
@Harry-Ramsey Harry-Ramsey changed the title [AArch64][SVE] Allow scalable factor-3 interleaved accesses [AArch64][SVE] Enable factor-3 scalable interleave operations Aug 3, 2026
@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch 2 times, most recently from b4460d1 to 64db40e Compare August 3, 2026 10:03
@Harry-Ramsey
Harry-Ramsey requested a review from david-arm August 4, 2026 09:38
SDLoc DL(Op);
EVT OpVT = Op.getValueType();

if (OpVT.isScalableVector() && OpVT.getVectorElementType() == MVT::i1 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be neater to fold this into the existing

  if (OpVT.isScalableVector() && Op->getNumOperands() == 3) {

block below, i.e.

  if (OpVT.isScalableVector() && Op->getNumOperands() == 3) {
    if (OpVT.getVectorElementType() == MVT::i1)
      return lowerPredicateInterleaveWithPackedVectors(Op, DAG);

SDLoc DL(Op);
EVT OpVT = Op.getValueType();

if (OpVT.isScalableVector() && OpVT.getVectorElementType() == MVT::i1 &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as in LowerVECTOR_DEINTERLEAVE.

for (SDValue V : Op->ops())
PackedOps.push_back(DAG.getNode(ISD::ZERO_EXTEND, DL, PackedVT, V));

SmallVector<EVT, 3> PackedVTs(Op->getNumValues(), PackedVT);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just 3 instead of Op->getNumValues() because num values should equal num operands according to the documentation for the nodes.


SDValue Zero = DAG.getConstant(0, DL, PackedVT);
SmallVector<SDValue, 3> Results;
for (unsigned I = 0; I != Op->getNumValues(); ++I)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You can replace Op->getNumValues() with 3 here I think

ret { <vscale x 2 x double>, <vscale x 2 x double> } %deinterleaved.vec
}

define { <vscale x 16 x i8>, <vscale x 16 x i8>, <vscale x 16 x i8> } @foo_ld3_nxv16i8(<vscale x 16 x i1> %mask, ptr %p) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have variants for other element sizes too, similar to the ld4 case below.

ret void
}

define void @foo_st3_nxv16i8(<vscale x 16 x i1> %mask, <vscale x 16 x i8> %val1, <vscale x 16 x i8> %val2, <vscale x 16 x i8> %val3, ptr %p) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have variants for other element sizes too similar to the st4 cases below.

; CHECK-NEXT: cmpne p1.h, p0/z, z0.h, #0
; CHECK-NEXT: cmpne p2.h, p0/z, z1.h, #0
; CHECK-NEXT: cmpne p3.h, p0/z, z2.h, #0
; CHECK-NEXT: uzp1 p0.b, p2.b, p1.b

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confused me at first until I realised the <vscale x 24 x i1> type has been widened to <vscale x 32 x i1> and the <vscale x 8 x i1> predicates have been packed into it. This might be ok, but it would be good to have other variants tested too to make sure they're all correct, e.g. @llvm.vector.interleave3.nxv12i1 and @llvm.vector.interleave3.nxv6i1

; CHECK-NEXT: ldr z0, [sp]
; CHECK-NEXT: ldr z1, [sp, #1, mul vl]
; CHECK-NEXT: ldr z2, [sp, #2, mul vl]
; CHECK-NEXT: cmpne p0.b, p2/z, z0.b, #0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks better because the 3 parts are naturally packed into 3 registers that make up <vscale x 48 x i1>

}
}

static SDValue lowerPredicateInterleaveWithPackedVectors(SDValue Op,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a separate PR, in case there are independent problems

Enable factor-3 scalable vector interleave and deinterleave lowering for SVE,
including the corresponding interleaved load/store combines.

Factor-3 operations are lowered through the existing LD3/ST3-based path, which
operates on packed data vectors.

Unlike factors 2 and 4, SVE has no ZIP/UZP instruction sequence specifically
ror factor-3 operations. For LD3/ST3 predicate operands must be promoted
into Z-register data vectors before lowering, then convert the results
back to predicates by comparing against zero. This avoids invalid
predicate uses of getSVESafeBitCast.
@Harry-Ramsey
Harry-Ramsey force-pushed the ramsey/sve-fix-interleave branch from 64db40e to f00460c Compare August 7, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants