[llvm] [SDAG] Implement SplitVecOp for `ISD::VECTOR_FIND_LAST_ACTIVE` (PR #180290)
Benjamin Maxwell via llvm-commits
llvm-commits at lists.llvm.org
Mon Feb 9 06:09:52 PST 2026
https://github.com/MacDue updated https://github.com/llvm/llvm-project/pull/180290
>From 780716c25bcd3e9c2b337649db2f5329dd14b0a8 Mon Sep 17 00:00:00 2001
From: MacDue <macdue at dueutil.tech>
Date: Fri, 6 Feb 2026 21:11:32 +0000
Subject: [PATCH 1/4] [SDAG] Implement SpltVecOp for
`ISD::VECTOR_FIND_LAST_ACTIVE`
This lowers the splitting as:
```
any_active(hi_mask)
? (find_last_active(hi_mask) + lo_mask.getVectorElementCount());
: find_last_active(lo_mask)
```
The lowering likely can be improved. This patch is for completeness.
---
llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h | 1 +
.../SelectionDAG/LegalizeVectorTypes.cpp | 29 ++++
.../AArch64/vector-extract-last-active.ll | 48 ++++--
.../CodeGen/X86/vector-extract-last-active.ll | 155 ++++++++++++++++++
4 files changed, 215 insertions(+), 18 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h b/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h
index a5efffa2b441c..6646802117f9a 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h
@@ -1040,6 +1040,7 @@ class LLVM_LIBRARY_VISIBILITY DAGTypeLegalizer {
SDValue SplitVecOp_VP_CttzElements(SDNode *N);
SDValue SplitVecOp_VECTOR_HISTOGRAM(SDNode *N);
SDValue SplitVecOp_PARTIAL_REDUCE_MLA(SDNode *N);
+ SDValue SplitVecOp_VECTOR_FIND_LAST_ACTIVE(SDNode *N);
//===--------------------------------------------------------------------===//
// Vector Widening Support: LegalizeVectorTypes.cpp
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
index e1e6252fd8abc..a6eb090aa7d04 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
@@ -3572,6 +3572,9 @@ bool DAGTypeLegalizer::SplitVectorOperand(SDNode *N, unsigned OpNo) {
case ISD::INSERT_SUBVECTOR: Res = SplitVecOp_INSERT_SUBVECTOR(N, OpNo); break;
case ISD::EXTRACT_VECTOR_ELT:Res = SplitVecOp_EXTRACT_VECTOR_ELT(N); break;
case ISD::CONCAT_VECTORS: Res = SplitVecOp_CONCAT_VECTORS(N); break;
+ case ISD::VECTOR_FIND_LAST_ACTIVE:
+ Res = SplitVecOp_VECTOR_FIND_LAST_ACTIVE(N);
+ break;
case ISD::VP_TRUNCATE:
case ISD::TRUNCATE:
Res = SplitVecOp_TruncateHelper(N);
@@ -3732,6 +3735,32 @@ bool DAGTypeLegalizer::SplitVectorOperand(SDNode *N, unsigned OpNo) {
return false;
}
+SDValue DAGTypeLegalizer::SplitVecOp_VECTOR_FIND_LAST_ACTIVE(SDNode *N) {
+ SDLoc DL(N);
+
+ SDValue LoMask, HiMask;
+ GetSplitVector(N->getOperand(0), LoMask, HiMask);
+
+ EVT VT = N->getValueType(0);
+ EVT SplitVT = LoMask.getValueType();
+ ElementCount SplitEC = SplitVT.getVectorElementCount();
+
+ // Find the last active in both the low and the high masks.
+ SDValue LoFind = DAG.getNode(ISD::VECTOR_FIND_LAST_ACTIVE, DL, VT, LoMask);
+ SDValue HiFind = DAG.getNode(ISD::VECTOR_FIND_LAST_ACTIVE, DL, VT, HiMask);
+
+ // Check if any lane is active in the high mask.
+ // FIXME: This would not be necessary if VECTOR_FIND_LAST_ACTIVE returned a
+ // sentinel value for "none active".
+ SDValue AnyHiActive = DAG.getNode(ISD::VECREDUCE_OR, DL, MVT::i32, HiMask);
+
+ // Return: AnyHiActive ? (HiFind + SplitEC) : LoFind;
+ return DAG.getSelect(DL, VT, AnyHiActive,
+ DAG.getNode(ISD::ADD, DL, VT, HiFind,
+ DAG.getElementCount(DL, VT, SplitEC)),
+ LoFind);
+}
+
SDValue DAGTypeLegalizer::SplitVecOp_VSELECT(SDNode *N, unsigned OpNo) {
// The only possibility for an illegal operand is the mask, since result type
// legalization would have handled this node already otherwise.
diff --git a/llvm/test/CodeGen/AArch64/vector-extract-last-active.ll b/llvm/test/CodeGen/AArch64/vector-extract-last-active.ll
index 7ee8f6fda93f5..3f5c30b0c1fe3 100644
--- a/llvm/test/CodeGen/AArch64/vector-extract-last-active.ll
+++ b/llvm/test/CodeGen/AArch64/vector-extract-last-active.ll
@@ -549,22 +549,34 @@ define i32 @extract_last_active_v3i32(<3 x i32> %a, <3 x i1> %c) {
ret i32 %res
}
-declare i8 @llvm.experimental.vector.extract.last.active.v16i8(<16 x i8>, <16 x i1>, i8)
-declare i16 @llvm.experimental.vector.extract.last.active.v8i16(<8 x i16>, <8 x i1>, i16)
-declare i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32>, <4 x i1>, i32)
-declare i64 @llvm.experimental.vector.extract.last.active.v2i64(<2 x i64>, <2 x i1>, i64)
-declare half @llvm.experimental.vector.extract.last.active.v8f16(<8 x half>, <8 x i1>, half)
-declare bfloat @llvm.experimental.vector.extract.last.active.v8bf16(<8 x bfloat>, <8 x i1>, bfloat)
-declare float @llvm.experimental.vector.extract.last.active.v4f32(<4 x float>, <4 x i1>, float)
-declare double @llvm.experimental.vector.extract.last.active.v2f64(<2 x double>, <2 x i1>, double)
-declare i8 @llvm.experimental.vector.extract.last.active.nxv16i8(<vscale x 16 x i8>, <vscale x 16 x i1>, i8)
-declare i16 @llvm.experimental.vector.extract.last.active.nxv8i16(<vscale x 8 x i16>, <vscale x 8 x i1>, i16)
-declare i32 @llvm.experimental.vector.extract.last.active.nxv4i32(<vscale x 4 x i32>, <vscale x 4 x i1>, i32)
-declare i64 @llvm.experimental.vector.extract.last.active.nxv2i64(<vscale x 2 x i64>, <vscale x 2 x i1>, i64)
-declare half @llvm.experimental.vector.extract.last.active.nxv8f16(<vscale x 8 x half>, <vscale x 8 x i1>, half)
-declare bfloat @llvm.experimental.vector.extract.last.active.nxv8bf16(<vscale x 8 x bfloat>, <vscale x 8 x i1>, bfloat)
-declare float @llvm.experimental.vector.extract.last.active.nxv4f32(<vscale x 4 x float>, <vscale x 4 x i1>, float)
-declare double @llvm.experimental.vector.extract.last.active.nxv2f64(<vscale x 2 x double>, <vscale x 2 x i1>, double)
-declare i1 @llvm.experimental.vector.extract.last.active.nxv16i1(<vscale x 16 x i1>, <vscale x 16 x i1>, i1)
+define i8 @extract_last_active_split(<vscale x 32 x i8> %data, <vscale x 32 x i1> %mask, i8 %passthru) #0 {
+; CHECK-LABEL: extract_last_active_split:
+; CHECK: // %bb.0:
+; CHECK-NEXT: str x29, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NEXT: addvl sp, sp, #-2
+; CHECK-NEXT: index z2.b, #0, #1
+; CHECK-NEXT: ptest p1, p1.b
+; CHECK-NEXT: rdvl x10, #1
+; CHECK-NEXT: str z1, [sp, #1, mul vl]
+; CHECK-NEXT: str z0, [sp]
+; CHECK-NEXT: lastb w8, p1, z2.b
+; CHECK-NEXT: lastb w9, p0, z2.b
+; CHECK-NEXT: sel p0.b, p0, p0.b, p1.b
+; CHECK-NEXT: add x8, x8, x10
+; CHECK-NEXT: rdvl x10, #2
+; CHECK-NEXT: csel x8, x8, x9, ne
+; CHECK-NEXT: sub x9, x10, #1
+; CHECK-NEXT: cmp x8, x9
+; CHECK-NEXT: csel x8, x8, x9, lo
+; CHECK-NEXT: mov x9, sp
+; CHECK-NEXT: ptest p0, p0.b
+; CHECK-NEXT: ldrb w8, [x9, x8]
+; CHECK-NEXT: csel w0, w8, w0, ne
+; CHECK-NEXT: addvl sp, sp, #2
+; CHECK-NEXT: ldr x29, [sp], #16 // 8-byte Folded Reload
+; CHECK-NEXT: ret
+ %res = call i8 @llvm.experimental.vector.extract.last.active.nxv32i8(<vscale x 32 x i8> %data, <vscale x 32 x i1> %mask, i8 %passthru)
+ ret i8 %res
+}
-attributes #0 = { "target-features"="+sve" vscale_range(1, 16) }
+attributes #0 = { nounwind "target-features"="+sve" vscale_range(1, 16) }
diff --git a/llvm/test/CodeGen/X86/vector-extract-last-active.ll b/llvm/test/CodeGen/X86/vector-extract-last-active.ll
index e891b27de2756..0fba95c3ed005 100644
--- a/llvm/test/CodeGen/X86/vector-extract-last-active.ll
+++ b/llvm/test/CodeGen/X86/vector-extract-last-active.ll
@@ -271,3 +271,158 @@ define i32 @extract_last_active_v4i32_penryn(<4 x i32> %a, <4 x i1> %c) "target-
%res = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> %a, <4 x i1> %c, i32 poison)
ret i32 %res
}
+
+define i8 @extract_last_active_split(<32 x i8> %data, <32 x i8> %mask, i8 %passthru) {
+; CHECK-LABEL: extract_last_active_split:
+; CHECK: # %bb.0:
+; CHECK-NEXT: pxor %xmm4, %xmm4
+; CHECK-NEXT: movdqa %xmm2, %xmm5
+; CHECK-NEXT: por %xmm3, %xmm5
+; CHECK-NEXT: pcmpeqb %xmm4, %xmm3
+; CHECK-NEXT: pcmpeqd %xmm6, %xmm6
+; CHECK-NEXT: movdqa %xmm3, %xmm7
+; CHECK-NEXT: pxor %xmm6, %xmm7
+; CHECK-NEXT: movdqa %xmm7, -{{[0-9]+}}(%rsp)
+; CHECK-NEXT: pcmpeqb %xmm4, %xmm2
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %eax
+; CHECK-NEXT: movdqa {{.*#+}} xmm7 = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
+; CHECK-NEXT: pandn %xmm7, %xmm2
+; CHECK-NEXT: movdqa %xmm2, -{{[0-9]+}}(%rsp)
+; CHECK-NEXT: pandn %xmm7, %xmm3
+; CHECK-NEXT: movdqa %xmm3, -{{[0-9]+}}(%rsp)
+; CHECK-NEXT: movaps %xmm1, -{{[0-9]+}}(%rsp)
+; CHECK-NEXT: movaps %xmm0, -{{[0-9]+}}(%rsp)
+; CHECK-NEXT: pcmpeqb %xmm4, %xmm5
+; CHECK-NEXT: pxor %xmm6, %xmm5
+; CHECK-NEXT: movdqa %xmm5, -{{[0-9]+}}(%rsp)
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %cl, %dl
+; CHECK-NEXT: cmoval %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: movl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: cmpb %sil, %cl
+; CHECK-NEXT: cmovbel %esi, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: cmpb %dl, %cl
+; CHECK-NEXT: cmovbel %edx, %ecx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %dl, %sil
+; CHECK-NEXT: cmoval %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: movl -{{[0-9]+}}(%rsp), %r8d
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: cmpb %r8b, %dl
+; CHECK-NEXT: cmovbel %r8d, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %esi
+; CHECK-NEXT: cmpb %sil, %dl
+; CHECK-NEXT: cmovbel %esi, %edx
+; CHECK-NEXT: addl $16, %edx
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
+; CHECK-NEXT: movzbl %al, %eax
+; CHECK-NEXT: testl %eax, %eax
+; CHECK-NEXT: cmoveq %rcx, %rdx
+; CHECK-NEXT: andl $31, %edx
+; CHECK-NEXT: movzbl -40(%rsp,%rdx), %eax
+; CHECK-NEXT: movzbl -{{[0-9]+}}(%rsp), %ecx
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %cl
+; CHECK-NEXT: testb $1, %cl
+; CHECK-NEXT: cmovel %edi, %eax
+; CHECK-NEXT: # kill: def $al killed $al killed $eax
+; CHECK-NEXT: retq
+ %notzero = icmp ne <32 x i8> %mask, zeroinitializer
+ %res = call i8 @llvm.experimental.vector.extract.last.active.v16i8(<32 x i8> %data, <32 x i1> %notzero, i8 %passthru)
+ ret i8 %res
+}
>From 44c5d12abaf628ae63e69ee2a346826c779e78a3 Mon Sep 17 00:00:00 2001
From: MacDue <macdue at dueutil.tech>
Date: Sat, 7 Feb 2026 09:07:20 +0000
Subject: [PATCH 2/4] Fixups
---
.../lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp | 12 +++++++-----
llvm/test/CodeGen/X86/vector-extract-last-active.ll | 3 +--
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
index a6eb090aa7d04..15b4296420454 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
@@ -3752,13 +3752,15 @@ SDValue DAGTypeLegalizer::SplitVecOp_VECTOR_FIND_LAST_ACTIVE(SDNode *N) {
// Check if any lane is active in the high mask.
// FIXME: This would not be necessary if VECTOR_FIND_LAST_ACTIVE returned a
// sentinel value for "none active".
- SDValue AnyHiActive = DAG.getNode(ISD::VECREDUCE_OR, DL, MVT::i32, HiMask);
+ SDValue AnyHiActive = DAG.getNode(ISD::VECREDUCE_OR, DL, MVT::i1, HiMask);
+ SDValue Cond = DAG.getSetCC(DL, getSetCCResultType(MVT::i1), AnyHiActive,
+ DAG.getConstant(0, DL, MVT::i1), ISD::SETNE);
// Return: AnyHiActive ? (HiFind + SplitEC) : LoFind;
- return DAG.getSelect(DL, VT, AnyHiActive,
- DAG.getNode(ISD::ADD, DL, VT, HiFind,
- DAG.getElementCount(DL, VT, SplitEC)),
- LoFind);
+ return DAG.getNode(ISD::SELECT, DL, VT, Cond,
+ DAG.getNode(ISD::ADD, DL, VT, HiFind,
+ DAG.getElementCount(DL, VT, SplitEC)),
+ LoFind);
}
SDValue DAGTypeLegalizer::SplitVecOp_VSELECT(SDNode *N, unsigned OpNo) {
diff --git a/llvm/test/CodeGen/X86/vector-extract-last-active.ll b/llvm/test/CodeGen/X86/vector-extract-last-active.ll
index 0fba95c3ed005..2504d4952aeb3 100644
--- a/llvm/test/CodeGen/X86/vector-extract-last-active.ll
+++ b/llvm/test/CodeGen/X86/vector-extract-last-active.ll
@@ -397,8 +397,7 @@ define i8 @extract_last_active_split(<32 x i8> %data, <32 x i8> %mask, i8 %passt
; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
; CHECK-NEXT: orb -{{[0-9]+}}(%rsp), %al
-; CHECK-NEXT: movzbl %al, %eax
-; CHECK-NEXT: testl %eax, %eax
+; CHECK-NEXT: testb $1, %al
; CHECK-NEXT: cmoveq %rcx, %rdx
; CHECK-NEXT: andl $31, %edx
; CHECK-NEXT: movzbl -40(%rsp,%rdx), %eax
>From 67b74f7c3e43fff76d3e7b2f781cf7462e8355c9 Mon Sep 17 00:00:00 2001
From: MacDue <macdue at dueutil.tech>
Date: Sat, 7 Feb 2026 09:15:40 +0000
Subject: [PATCH 3/4] Use ext/trunc
---
llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
index 15b4296420454..8181be2dd70ee 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
@@ -3753,8 +3753,8 @@ SDValue DAGTypeLegalizer::SplitVecOp_VECTOR_FIND_LAST_ACTIVE(SDNode *N) {
// FIXME: This would not be necessary if VECTOR_FIND_LAST_ACTIVE returned a
// sentinel value for "none active".
SDValue AnyHiActive = DAG.getNode(ISD::VECREDUCE_OR, DL, MVT::i1, HiMask);
- SDValue Cond = DAG.getSetCC(DL, getSetCCResultType(MVT::i1), AnyHiActive,
- DAG.getConstant(0, DL, MVT::i1), ISD::SETNE);
+ SDValue Cond = DAG.getBoolExtOrTrunc(AnyHiActive, DL,
+ getSetCCResultType(MVT::i1), MVT::i1);
// Return: AnyHiActive ? (HiFind + SplitEC) : LoFind;
return DAG.getNode(ISD::SELECT, DL, VT, Cond,
>From 95e3e62318ab453a3abc1b885e37f359770ee094 Mon Sep 17 00:00:00 2001
From: Benjamin Maxwell <benjamin.maxwell at arm.com>
Date: Mon, 9 Feb 2026 14:09:03 +0000
Subject: [PATCH 4/4] Wasm
---
llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h | 1 +
.../SelectionDAG/LegalizeVectorTypes.cpp | 15 ++
.../WebAssembly/vector-extract-last-active.ll | 149 ++++++++++++++++++
3 files changed, 165 insertions(+)
create mode 100644 llvm/test/CodeGen/WebAssembly/vector-extract-last-active.ll
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h b/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h
index 6646802117f9a..3274ef1e25358 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeTypes.h
@@ -931,6 +931,7 @@ class LLVM_LIBRARY_VISIBILITY DAGTypeLegalizer {
SDValue ScalarizeVecOp_VECREDUCE_SEQ(SDNode *N);
SDValue ScalarizeVecOp_CMP(SDNode *N);
SDValue ScalarizeVecOp_FAKE_USE(SDNode *N);
+ SDValue ScalarizeVecOp_VECTOR_FIND_LAST_ACTIVE(SDNode *N);
//===--------------------------------------------------------------------===//
// Vector Splitting Support: LegalizeVectorTypes.cpp
diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
index 8181be2dd70ee..dadcca16bd9b8 100644
--- a/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeVectorTypes.cpp
@@ -885,6 +885,9 @@ bool DAGTypeLegalizer::ScalarizeVectorOperand(SDNode *N, unsigned OpNo) {
case ISD::UCMP:
Res = ScalarizeVecOp_CMP(N);
break;
+ case ISD::VECTOR_FIND_LAST_ACTIVE:
+ Res = ScalarizeVecOp_VECTOR_FIND_LAST_ACTIVE(N);
+ break;
}
// If the result is null, the sub-method took care of registering results etc.
@@ -1185,6 +1188,18 @@ SDValue DAGTypeLegalizer::ScalarizeVecOp_CMP(SDNode *N) {
return DAG.getNode(ISD::SCALAR_TO_VECTOR, SDLoc(N), N->getValueType(0), Cmp);
}
+SDValue DAGTypeLegalizer::ScalarizeVecOp_VECTOR_FIND_LAST_ACTIVE(SDNode *N) {
+ // Since there is no "none-active" result, the only valid return for <1 x ty>
+ // is 0. Note: Since we check the high mask during splitting this is safe.
+ // As e.g., a <2 x ty> operation would split to:
+ // any_active(%hi_mask) ? (1 + last_active(%hi_mask))
+ // : `last_active(%lo_mask)`
+ // Which then scalarizes to:
+ // %mask[1] ? 1 : 0
+ EVT VT = N->getValueType(0);
+ return DAG.getConstant(0, SDLoc(N), VT);
+}
+
//===----------------------------------------------------------------------===//
// Result Vector Splitting
//===----------------------------------------------------------------------===//
diff --git a/llvm/test/CodeGen/WebAssembly/vector-extract-last-active.ll b/llvm/test/CodeGen/WebAssembly/vector-extract-last-active.ll
new file mode 100644
index 0000000000000..ba1af61433cb3
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/vector-extract-last-active.ll
@@ -0,0 +1,149 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s | FileCheck %s
+
+target triple = "wasm32-unknown-unknown"
+
+define i32 @extract_last_active_v4i32(<4 x i32> %a, <4 x i1> %c) {
+; CHECK-LABEL: extract_last_active_v4i32:
+; CHECK: .functype extract_last_active_v4i32 (i32, i32, i32, i32, i32, i32, i32, i32) -> (i32)
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: i32.const 16
+; CHECK-NEXT: i32.sub
+; CHECK-NEXT: local.tee 8
+; CHECK-NEXT: local.get 3
+; CHECK-NEXT: i32.store 12
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: i32.store 8
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: i32.store 4
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: i32.store 0
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: i32.const 3
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: local.get 7
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.get 5
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: local.get 6
+; CHECK-NEXT: local.get 7
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: i32.shl
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.load 0
+; CHECK-NEXT: i32.const -1
+; CHECK-NEXT: local.get 4
+; CHECK-NEXT: local.get 6
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: local.get 5
+; CHECK-NEXT: local.get 7
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: # fallthrough-return
+ %res = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> %a, <4 x i1> %c, i32 -1)
+ ret i32 %res
+}
+
+define i32 @extract_last_active_v4i32_no_default(<4 x i32> %a, <4 x i1> %c) {
+; CHECK-LABEL: extract_last_active_v4i32_no_default:
+; CHECK: .functype extract_last_active_v4i32_no_default (i32, i32, i32, i32, i32, i32, i32, i32) -> (i32)
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: i32.const 16
+; CHECK-NEXT: i32.sub
+; CHECK-NEXT: local.tee 8
+; CHECK-NEXT: local.get 3
+; CHECK-NEXT: i32.store 12
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: i32.store 8
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: i32.store 4
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: i32.store 0
+; CHECK-NEXT: local.get 8
+; CHECK-NEXT: i32.const 3
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: local.get 7
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.get 5
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: local.get 6
+; CHECK-NEXT: local.get 7
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: i32.const 2
+; CHECK-NEXT: i32.shl
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.load 0
+; CHECK-NEXT: # fallthrough-return
+ %res = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> %a, <4 x i1> %c, i32 poison)
+ ret i32 %res
+}
+
+; Test v2i32 - smaller vector.
+define i32 @extract_last_active_v2i32(<2 x i32> %a, <2 x i1> %c) {
+; CHECK-LABEL: extract_last_active_v2i32:
+; CHECK: .functype extract_last_active_v2i32 (i32, i32, i32, i32) -> (i32)
+; CHECK-NEXT: .local i32
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: global.get __stack_pointer
+; CHECK-NEXT: i32.const 16
+; CHECK-NEXT: i32.sub
+; CHECK-NEXT: local.tee 4
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: i32.store 12
+; CHECK-NEXT: local.get 4
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: i32.store 8
+; CHECK-NEXT: local.get 4
+; CHECK-NEXT: i32.const 8
+; CHECK-NEXT: i32.add
+; CHECK-NEXT: i32.const 4
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.get 3
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.load 0
+; CHECK-NEXT: i32.const -1
+; CHECK-NEXT: local.get 2
+; CHECK-NEXT: local.get 3
+; CHECK-NEXT: i32.or
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: i32.select
+; CHECK-NEXT: # fallthrough-return
+ %res = call i32 @llvm.experimental.vector.extract.last.active.v2i32(<2 x i32> %a, <2 x i1> %c, i32 -1)
+ ret i32 %res
+}
More information about the llvm-commits
mailing list