[llvm] [SelectionDAG] Handle undef at any position in isConstantSequence (PR #176671)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Jan 18 15:12:29 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-selectiondag
Author: Philip Ginsbach-Chen (ginsbach)
<details>
<summary>Changes</summary>
This patch extends `BuildVectorSDNode::isConstantSequence` to recognize constant sequences that contain undef elements at any position.
The new implementation finds the first two non-undef constant elements, computes the stride from their difference, then verifies all other defined elements match the sequence. This enables SVE's INDEX instruction to be used in more cases.
This change particularly benefits ZIP1/ZIP2 patterns where one operand is a constant sequence. When a smaller constant vector like `<0, 1, 2, 3>` is used in a ZIP1 shuffle producing a wider result, it gets expanded with trailing undefs. Similarly, for ZIP2 patterns, the DAG combiner transforms the constant to have leading undefs since ZIP2 only uses the upper half of its operands.
In particular, these patterns arise naturally from `VectorCombine`'s `compactShuffleOperands` optimization (see #<!-- -->176074) that I am suggesting as a fix for #<!-- -->137447.
---
Full diff: https://github.com/llvm/llvm-project/pull/176671.diff
2 Files Affected:
- (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp (+34-14)
- (modified) llvm/test/CodeGen/AArch64/sve-fixed-length-build-vector.ll (+125)
``````````diff
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
index 35e443b40c41f..74cc8fb2ed366 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
@@ -14037,26 +14037,46 @@ BuildVectorSDNode::isConstantSequence() const {
if (NumOps < 2)
return std::nullopt;
- if (!isa<ConstantSDNode>(getOperand(0)) ||
- !isa<ConstantSDNode>(getOperand(1)))
- return std::nullopt;
-
unsigned EltSize = getValueType(0).getScalarSizeInBits();
- APInt Start = getConstantOperandAPInt(0).trunc(EltSize);
- APInt Stride = getConstantOperandAPInt(1).trunc(EltSize) - Start;
-
- if (Stride.isZero())
- return std::nullopt;
+ APInt Start, Stride;
+ int FirstIdx = -1, SecondIdx = -1;
- for (unsigned i = 2; i < NumOps; ++i) {
- if (!isa<ConstantSDNode>(getOperand(i)))
+ // Find the first two non-undef constant elements to determine Start and
+ // Stride, then verify all remaining elements match the sequence.
+ for (unsigned I = 0; I < NumOps; ++I) {
+ SDValue Op = getOperand(I);
+ if (Op->isUndef())
+ continue;
+ if (!isa<ConstantSDNode>(Op))
return std::nullopt;
- APInt Val = getConstantOperandAPInt(i).trunc(EltSize);
- if (Val != (Start + (Stride * i)))
- return std::nullopt;
+ APInt Val = getConstantOperandAPInt(I).trunc(EltSize);
+ if (FirstIdx < 0) {
+ FirstIdx = I;
+ Start = Val;
+ } else if (SecondIdx < 0) {
+ SecondIdx = I;
+ // Compute stride based on the difference between the two elements.
+ unsigned IdxDiff = I - FirstIdx;
+ APInt ValDiff = Val - Start;
+ if (ValDiff.srem(IdxDiff) != 0)
+ return std::nullopt;
+ Stride = ValDiff.sdiv(IdxDiff);
+ if (Stride.isZero())
+ return std::nullopt;
+ // Adjust Start based on the first defined element's index.
+ Start -= Stride * FirstIdx;
+ } else {
+ // Verify this element matches the sequence.
+ if (Val != Start + Stride * I)
+ return std::nullopt;
+ }
}
+ // Need at least two defined elements.
+ if (SecondIdx < 0)
+ return std::nullopt;
+
return std::make_pair(Start, Stride);
}
diff --git a/llvm/test/CodeGen/AArch64/sve-fixed-length-build-vector.ll b/llvm/test/CodeGen/AArch64/sve-fixed-length-build-vector.ll
index 47fda39d84001..09f6693373642 100644
--- a/llvm/test/CodeGen/AArch64/sve-fixed-length-build-vector.ll
+++ b/llvm/test/CodeGen/AArch64/sve-fixed-length-build-vector.ll
@@ -65,4 +65,129 @@ define void @build_vector_no_stride_v4i64(ptr %a) #0 {
ret void
}
+; Sequence with trailing poison elements.
+define void @build_vector_trailing_poison_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_trailing_poison_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.s, #0, #3
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 0, i32 3, i32 6, i32 9, i32 12, i32 15, i32 poison, i32 poison>, ptr %a, align 4
+ ret void
+}
+
+; Sequence with leading poison elements.
+define void @build_vector_leading_poison_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_leading_poison_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.s, #0, #3
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 poison, i32 poison, i32 6, i32 9, i32 12, i32 15, i32 18, i32 21>, ptr %a, align 4
+ ret void
+}
+
+; Sequence with poison elements in the middle.
+define void @build_vector_middle_poison_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_middle_poison_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.s, #0, #3
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 0, i32 3, i32 poison, i32 poison, i32 12, i32 15, i32 18, i32 21>, ptr %a, align 4
+ ret void
+}
+
+; Sequence with poison elements scattered throughout.
+define void @build_vector_scattered_poison_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_scattered_poison_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.s, #0, #3
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 poison, i32 3, i32 poison, i32 9, i32 poison, i32 15, i32 poison, i32 21>, ptr %a, align 4
+ ret void
+}
+
+; Sequence with only two defined elements (minimum required).
+define void @build_vector_two_defined_v4i64(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_two_defined_v4i64:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.d, #5, #7
+; VBITS_GE_256-NEXT: ptrue p0.d, vl4
+; VBITS_GE_256-NEXT: st1d { z0.d }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <4 x i64> <i64 poison, i64 12, i64 poison, i64 26>, ptr %a, align 8
+ ret void
+}
+
+; Sequence with negative stride and poison elements.
+define void @build_vector_neg_stride_poison_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_neg_stride_poison_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.s, #0, #-2
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 poison, i32 -2, i32 -4, i32 poison, i32 -8, i32 -10, i32 poison, i32 -14>, ptr %a, align 4
+ ret void
+}
+
+; Only one defined element - cannot determine stride, so no index instruction.
+define void @build_vector_single_defined_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_single_defined_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: mov z0.s, #42 // =0x2a
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 poison, i32 poison, i32 poison, i32 42, i32 poison, i32 poison, i32 poison, i32 poison>, ptr %a, align 4
+ ret void
+}
+
+; Fractional stride: elements at indices 1 and 3 differ by 3, so stride would be 3/2.
+define void @build_vector_fractional_stride_v8i32(ptr %a) #0 {
+; VBITS_GE_256-LABEL: build_vector_fractional_stride_v8i32:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: ptrue p0.s, vl8
+; VBITS_GE_256-NEXT: adrp x8, .LCPI12_0
+; VBITS_GE_256-NEXT: add x8, x8, :lo12:.LCPI12_0
+; VBITS_GE_256-NEXT: ld1w { z0.s }, p0/z, [x8]
+; VBITS_GE_256-NEXT: st1w { z0.s }, p0, [x0]
+; VBITS_GE_256-NEXT: ret
+ store <8 x i32> <i32 poison, i32 0, i32 poison, i32 3, i32 poison, i32 poison, i32 poison, i32 poison>, ptr %a, align 4
+ ret void
+}
+
+; zip1 pattern: constant <0, 1, 2, 3> is expanded to <0, 1, 2, 3, poison, poison, poison, poison>
+; to match the shuffle result width. isConstantSequence recognizes this as a sequence.
+define <8 x i8> @zip_const_seq_with_variable(i8 %x) #0 {
+; VBITS_GE_256-LABEL: zip_const_seq_with_variable:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z0.b, #0, #1
+; VBITS_GE_256-NEXT: dup v1.8b, w0
+; VBITS_GE_256-NEXT: zip1 v0.8b, v0.8b, v1.8b
+; VBITS_GE_256-NEXT: ret
+ %ins = insertelement <4 x i8> poison, i8 %x, i32 0
+ %splat = shufflevector <4 x i8> %ins, <4 x i8> poison, <4 x i32> zeroinitializer
+ %interleave = shufflevector <4 x i8> <i8 0, i8 1, i8 2, i8 3>, <4 x i8> %splat, <8 x i32> <i32 0, i32 4, i32 1, i32 5, i32 2, i32 6, i32 3, i32 7>
+ ret <8 x i8> %interleave
+}
+
+; zip2 pattern: constant <0, 1, 2, 3, 4, 5, 6, 7> is transformed by the DAG combiner to
+; <poison, poison, poison, poison, 4, 5, 6, 7> since zip2 only uses elements 4-7.
+define <8 x i8> @zip2_const_seq_with_variable(<8 x i8> %x) #0 {
+; VBITS_GE_256-LABEL: zip2_const_seq_with_variable:
+; VBITS_GE_256: // %bb.0:
+; VBITS_GE_256-NEXT: index z1.b, #0, #1
+; VBITS_GE_256-NEXT: zip2 v0.8b, v1.8b, v0.8b
+; VBITS_GE_256-NEXT: ret
+ %interleave = shufflevector <8 x i8> <i8 0, i8 1, i8 2, i8 3, i8 4, i8 5, i8 6, i8 7>, <8 x i8> %x, <8 x i32> <i32 4, i32 12, i32 5, i32 13, i32 6, i32 14, i32 7, i32 15>
+ ret <8 x i8> %interleave
+}
+
attributes #0 = { "target-features"="+sve" }
``````````
</details>
https://github.com/llvm/llvm-project/pull/176671
More information about the llvm-commits
mailing list