[llvm] [Hexagon] Fix SplitVectors crash in HVX type legalization (PR #181377)

Brian Cain via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 19 08:59:57 PST 2026


https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/181377

>From 5055aa96f61961c8e5164f9ce81fba431d143dd1 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Thu, 12 Feb 2026 22:10:26 -0800
Subject: [PATCH 1/2] [Hexagon] Fix SplitVectors crash in HVX type legalization

When LegalizeHvxResize splits a multi-step TL_EXTEND (e.g., v128i32
from v128i8, which is i8->i32), SplitVectorOp halves both input and
output types. This creates operand types that are half the HVX vector
width (e.g., v64i8 = 512 bits on 128-byte HVX), which are not legal
HVX types. These sub-HVX intermediate types confuse the DAG type
legalizer's map tracking, causing "Unprocessed value in a map!
SplitVectors" assertions with EXPENSIVE_CHECKS or
-enable-legalize-types-checking.

Fix by first expanding multi-step TL_EXTEND/TL_TRUNCATE operations
into a chain of single-step operations via ExpandHvxResizeIntoSteps
before splitting. Each single-step operation (e.g., i16->i32) can be
safely split because halving its operand type produces a legal HVX
type (e.g., v64i16 = HVX single vector).
---
 .../Target/Hexagon/HexagonISelLoweringHVX.cpp |  9 ++++++++
 .../CodeGen/Hexagon/hvx-zext-split-check.ll   | 23 +++++++++++++++++++
 2 files changed, 32 insertions(+)
 create mode 100644 llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll

diff --git a/llvm/lib/Target/Hexagon/HexagonISelLoweringHVX.cpp b/llvm/lib/Target/Hexagon/HexagonISelLoweringHVX.cpp
index b1181dfa13a10..4eefec95d09d2 100644
--- a/llvm/lib/Target/Hexagon/HexagonISelLoweringHVX.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonISelLoweringHVX.cpp
@@ -3785,6 +3785,15 @@ HexagonTargetLowering::LegalizeHvxResize(SDValue Op, SelectionDAG &DAG) const {
     SDValue T = ExpandHvxResizeIntoSteps(S, DAG);
     return extractSubvector(T, typeLegalize(ResTy, DAG), 0, DAG);
   } else if (shouldSplitToHvx(InpWidth < ResWidth ? ResTy : InpTy, DAG)) {
+    // For multi-step extends/truncates (e.g., i8->i32), expand into
+    // single-step operations first. Splitting a multi-step TL_EXTEND
+    // would halve the operand type to a sub-HVX size (e.g., v128i8 ->
+    // v64i8), creating illegal types that cause issues in the type
+    // legalizer's map tracking. Single-step operations (e.g., i16->i32)
+    // are safe to split because their halved operand types remain legal.
+    SDValue T = ExpandHvxResizeIntoSteps(Op, DAG);
+    if (T != Op)
+      return T;
     return opJoin(SplitVectorOp(Op, DAG), SDLoc(Op), DAG);
   } else {
     assert(isTypeLegal(InpTy) && isTypeLegal(ResTy));
diff --git a/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll b/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll
new file mode 100644
index 0000000000000..b8f2bf672ab96
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll
@@ -0,0 +1,23 @@
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv73 -mattr=+hvxv73,+hvx-length128b \
+; RUN:   -enable-legalize-types-checking < %s | FileCheck %s
+;
+; Check that HVX zero-extend operations that require splitting compile
+; correctly. The bug was that multi-step TL_EXTEND operations (e.g., i8->i32)
+; were split directly, creating sub-HVX operand types (v64i8) that confused
+; the legalizer's map tracking. The fix expands multi-step extends into
+; single steps first.
+
+target datalayout = "e-m:e-p:32:32:32-a:0-n16:32-i64:64:64-i32:32:32-i16:16:16-i1:8:8-f32:32:32-f64:64:64-v32:32:32-v64:64:64-v512:512:512-v1024:1024:1024-v2048:2048:2048"
+target triple = "hexagon-unknown-linux-musl"
+
+; CHECK-LABEL: test_zext_split:
+; CHECK-DAG:   vunpack
+; CHECK-DAG:   vadd
+; CHECK:       jumpr r31
+define fastcc <32 x i32> @test_zext_split(<32 x i8> %a, <32 x i8> %b) {
+entry:
+  %ext_a = zext <32 x i8> %a to <32 x i32>
+  %ext_b = zext <32 x i8> %b to <32 x i32>
+  %sum = add <32 x i32> %ext_a, %ext_b
+  ret <32 x i32> %sum
+}

>From ddedf432b59f921b161016280caa29ec832d79e4 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Thu, 19 Feb 2026 08:59:17 -0800
Subject: [PATCH 2/2] [Hexagon] Improve HVX split-vectors lit test coverage

Add auto-generated CHECK lines via update_llc_test_checks.py, add
sext and trunc test cases that exercise the splitting path in
LegalizeHvxResize, and remove unnecessary target datalayout/triple.
---
 .../CodeGen/Hexagon/hvx-zext-split-check.ll   | 221 +++++++++++++++++-
 1 file changed, 211 insertions(+), 10 deletions(-)

diff --git a/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll b/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll
index b8f2bf672ab96..e5258afb7adcc 100644
--- a/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll
+++ b/llvm/test/CodeGen/Hexagon/hvx-zext-split-check.ll
@@ -1,23 +1,224 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
 ; RUN: llc -mtriple=hexagon -mcpu=hexagonv73 -mattr=+hvxv73,+hvx-length128b \
 ; RUN:   -enable-legalize-types-checking < %s | FileCheck %s
 ;
-; Check that HVX zero-extend operations that require splitting compile
+; Check that HVX extend/truncate operations that require splitting compile
 ; correctly. The bug was that multi-step TL_EXTEND operations (e.g., i8->i32)
 ; were split directly, creating sub-HVX operand types (v64i8) that confused
 ; the legalizer's map tracking. The fix expands multi-step extends into
-; single steps first.
+; single steps first. The same applies to multi-step TL_TRUNCATE operations.
 
-target datalayout = "e-m:e-p:32:32:32-a:0-n16:32-i64:64:64-i32:32:32-i16:16:16-i1:8:8-f32:32:32-f64:64:64-v32:32:32-v64:64:64-v512:512:512-v1024:1024:1024-v2048:2048:2048"
-target triple = "hexagon-unknown-linux-musl"
-
-; CHECK-LABEL: test_zext_split:
-; CHECK-DAG:   vunpack
-; CHECK-DAG:   vadd
-; CHECK:       jumpr r31
-define fastcc <32 x i32> @test_zext_split(<32 x i8> %a, <32 x i8> %b) {
+; Widening path: v32i8 (sub-HVX) widened to v128i8 before multi-step extend.
+define fastcc <32 x i32> @test_zext_widen(<32 x i8> %a, <32 x i8> %b) {
+; CHECK-LABEL: test_zext_widen:
+; CHECK:         .cfi_startproc
+; CHECK-NEXT:  // %bb.0: // %entry
+; CHECK-NEXT:    {
+; CHECK-NEXT:     r7 = #64
+; CHECK-NEXT:     v1 = vxor(v1,v1)
+; CHECK-NEXT:     r13:12 = memd(r29+#24)
+; CHECK-NEXT:     r9:8 = memd(r29+#8)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3:2 = vcombine(v1,v1)
+; CHECK-NEXT:     v4 = v1
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v2.w = vinsert(r12)
+; CHECK-NEXT:     v4.w = vinsert(r4)
+; CHECK-NEXT:     v0 = vror(v1,r7)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3.w = vinsert(r8)
+; CHECK-NEXT:     v1.w = vinsert(r0)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v2 = valign(v2,v2,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v2.w = vinsert(r13)
+; CHECK-NEXT:     v3 = valign(v3,v3,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3.w = vinsert(r9)
+; CHECK-NEXT:     v4 = valign(v4,v4,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v4.w = vinsert(r5)
+; CHECK-NEXT:     v1 = valign(v1,v1,#4)
+; CHECK-NEXT:     r5:4 = memd(r29+#32)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1.w = vinsert(r1)
+; CHECK-NEXT:     v2 = valign(v2,v2,#4)
+; CHECK-NEXT:     r1:0 = memd(r29+#16)
+; CHECK-NEXT:     r11:10 = memd(r29+#0)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v2.w = vinsert(r4)
+; CHECK-NEXT:     v3 = valign(v3,v3,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3.w = vinsert(r0)
+; CHECK-NEXT:     v4 = valign(v4,v4,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v4.w = vinsert(r10)
+; CHECK-NEXT:     v1 = valign(v1,v1,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1.w = vinsert(r2)
+; CHECK-NEXT:     r2 = #116
+; CHECK-NEXT:     v2 = valign(v2,v2,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v2.w = vinsert(r5)
+; CHECK-NEXT:     v3 = valign(v3,v3,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3.w = vinsert(r1)
+; CHECK-NEXT:     v1 = valign(v1,v1,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1.w = vinsert(r3)
+; CHECK-NEXT:     v4 = valign(v4,v4,#4)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v4.w = vinsert(r11)
+; CHECK-NEXT:     v3 = vror(v3,r2)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1 = vror(v1,r2)
+; CHECK-NEXT:     v3 = vor(v3,v0)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v4 = vror(v4,r2)
+; CHECK-NEXT:     v1 = vor(v1,v0)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v2 = vror(v2,r2)
+; CHECK-NEXT:     v4 = vor(v4,v0)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v7:6.uh = vunpack(v1.ub)
+; CHECK-NEXT:     v0 = vor(v2,v0)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v5:4.uh = vunpack(v4.ub)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v31:30.uh = vunpack(v3.ub)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1:0.uh = vunpack(v0.ub)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v7:6.uw = vunpack(v6.uh)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v5:4.uw = vunpack(v4.uh)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3:2.uw = vunpack(v30.uh)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1:0.uw = vunpack(v0.uh)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v5:4 = vshuff(v4,v6,r7)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1:0 = vshuff(v0,v2,r7)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v0.w = vadd(v4.w,v0.w)
+; CHECK-NEXT:     jumpr r31
+; CHECK-NEXT:    }
 entry:
   %ext_a = zext <32 x i8> %a to <32 x i32>
   %ext_b = zext <32 x i8> %b to <32 x i32>
   %sum = add <32 x i32> %ext_a, %ext_b
   ret <32 x i32> %sum
 }
+
+; Splitting path: v128i8 (single HVX vector) -> v128i32 (needs splitting).
+; Multi-step zext i8->i16->i32 with ExpandHvxResizeIntoSteps before split.
+define fastcc <128 x i32> @test_zext_split(<128 x i8> %a) {
+; CHECK-LABEL: test_zext_split:
+; CHECK:         .cfi_startproc
+; CHECK-NEXT:  // %bb.0: // %entry
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1:0.uh = vunpack(v0.ub)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3:2.uw = vunpack(v0.uh)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v31:30.uw = vunpack(v1.uh)
+; CHECK-NEXT:     vmem(r0+#1) = v3
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     vmem(r0+#3) = v31
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     vmem(r0+#2) = v30
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     jumpr r31
+; CHECK-NEXT:     vmem(r0+#0) = v2
+; CHECK-NEXT:    }
+entry:
+  %ext = zext <128 x i8> %a to <128 x i32>
+  ret <128 x i32> %ext
+}
+
+; Splitting path: v128i8 -> v128i32, signed multi-step extend.
+define fastcc <128 x i32> @test_sext_split(<128 x i8> %a) {
+; CHECK-LABEL: test_sext_split:
+; CHECK:         .cfi_startproc
+; CHECK-NEXT:  // %bb.0: // %entry
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v1:0.h = vunpack(v0.b)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v3:2.w = vunpack(v0.h)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v31:30.w = vunpack(v1.h)
+; CHECK-NEXT:     vmem(r0+#1) = v3
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     vmem(r0+#3) = v31
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     vmem(r0+#2) = v30
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     jumpr r31
+; CHECK-NEXT:     vmem(r0+#0) = v2
+; CHECK-NEXT:    }
+entry:
+  %ext = sext <128 x i8> %a to <128 x i32>
+  ret <128 x i32> %ext
+}
+
+; Splitting path: v128i32 (needs splitting) -> v128i8 (single HVX vector).
+; Multi-step trunc i32->i16->i8 with ExpandHvxResizeIntoSteps before split.
+define fastcc <128 x i8> @test_trunc_split(<128 x i32> %a) {
+; CHECK-LABEL: test_trunc_split:
+; CHECK:         .cfi_startproc
+; CHECK-NEXT:  // %bb.0: // %entry
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v0.h = vpacke(v1.w,v0.w)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v31.h = vpacke(v3.w,v2.w)
+; CHECK-NEXT:    }
+; CHECK-NEXT:    {
+; CHECK-NEXT:     v0.b = vpacke(v31.h,v0.h)
+; CHECK-NEXT:     jumpr r31
+; CHECK-NEXT:    }
+entry:
+  %t = trunc <128 x i32> %a to <128 x i8>
+  ret <128 x i8> %t
+}



More information about the llvm-commits mailing list