[Hexagon] Fix truncation to boolean vector that need widening (#182528)

When truncating a sub-HVX-width vector to a boolean vector (e.g., v64i8
-> v64i1 in 128-byte HVX mode), the operation would crash with
"Unhandled HVX operation" UNREACHABLE. This happened because the
condition in LowerHvxOperationWrapper/ReplaceHvxNodeResults did not
handle the case where the input vector needs widening and the result is
a boolean vector.

The fix adds WidenHvxTruncateToBool which widens the input to HVX
register width (e.g., v64i8 -> v128i8), performs the truncate to widened
bool type (v128i8 -> v128i1), extracts the result subvector (v128i1 ->
v64i1).

This allows the widened truncate to match the existing V6_vandvrt
pattern in HexagonPatternsHVX.td.
This commit is contained in:
Brian Cain 2026-02-25 13:55:32 -06:00 committed by GitHub
parent 8d625621eb
commit 0c53a89b22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 205 additions and 15 deletions

View file

@ -501,6 +501,7 @@ private:
SDValue CreateTLWrapper(SDValue Op, SelectionDAG &DAG) const;
SDValue RemoveTLWrapper(SDValue Op, SelectionDAG &DAG) const;
SDValue WidenHvxTruncateToBool(SDValue Op, SelectionDAG &DAG) const;
std::pair<const TargetRegisterClass*, uint8_t>
findRepresentativeClass(const TargetRegisterInfo *TRI, MVT VT)

View file

@ -3616,6 +3616,43 @@ HexagonTargetLowering::WidenHvxSetCC(SDValue Op, SelectionDAG &DAG) const {
{SetCC, getZero(dl, MVT::i32, DAG)});
}
SDValue HexagonTargetLowering::WidenHvxTruncateToBool(SDValue Op,
SelectionDAG &DAG) const {
// Handle truncation to boolean vector where the result boolean type
// needs widening (e.g., v16i32 -> v16i1 where v16i1 is not a standard
// HVX predicate type, or v16i8 -> v16i1 in 128-byte mode).
// Widen the input to HVX width, perform the truncate to the widened
// boolean type, then extract the result.
const SDLoc &dl(Op);
SDValue Inp = Op.getOperand(0);
MVT InpTy = ty(Inp);
MVT ResTy = ty(Op);
assert(ResTy.getVectorElementType() == MVT::i1 &&
"Expected boolean result type");
MVT ElemTy = InpTy.getVectorElementType();
unsigned HwLen = Subtarget.getVectorLength();
// Calculate the widened input type that fills the HVX register.
unsigned WideLen = (8 * HwLen) / ElemTy.getSizeInBits();
MVT WideInpTy = MVT::getVectorVT(ElemTy, WideLen);
if (!Subtarget.isHVXVectorType(WideInpTy, false))
return SDValue();
// Widen the input to HVX width.
SDValue WideInp = appendUndef(Inp, WideInpTy, DAG);
// Perform the truncate to widened boolean type.
MVT WideBoolTy = MVT::getVectorVT(MVT::i1, WideLen);
SDValue WideTrunc = DAG.getNode(ISD::TRUNCATE, dl, WideBoolTy, WideInp);
// Extract the result.
EVT RetTy = typeLegalize(ResTy, DAG);
return DAG.getNode(ISD::EXTRACT_SUBVECTOR, dl, RetTy,
{WideTrunc, getZero(dl, MVT::i32, DAG)});
}
SDValue
HexagonTargetLowering::LowerHvxOperation(SDValue Op, SelectionDAG &DAG) const {
unsigned Opc = Op.getOpcode();
@ -3866,12 +3903,28 @@ HexagonTargetLowering::LowerHvxOperationWrapper(SDNode *N,
case ISD::ANY_EXTEND:
case ISD::SIGN_EXTEND:
case ISD::ZERO_EXTEND:
case ISD::TRUNCATE:
if (Subtarget.isHVXElementType(ty(Op)) &&
Subtarget.isHVXElementType(ty(Inp0))) {
Results.push_back(CreateTLWrapper(Op, DAG));
}
break;
case ISD::TRUNCATE:
// Handle truncate to boolean vector when the input is not a
// standard HVX vector type (single or pair). This covers cases
// where the input needs widening (e.g., v64i8 -> v64i1 in
// 128-byte mode) and cases where the result boolean type itself
// needs widening (e.g., v16i32 -> v16i1). When the input is
// already an HVX type, tablegen patterns handle the truncation
// directly (e.g., v64i16 -> v64i1 via V6_vandvrt).
if (ty(Op).getVectorElementType() == MVT::i1 &&
!Subtarget.isHVXVectorType(ty(Inp0), false)) {
if (SDValue T = WidenHvxTruncateToBool(Op, DAG))
Results.push_back(T);
} else if (Subtarget.isHVXElementType(ty(Op)) &&
Subtarget.isHVXElementType(ty(Inp0))) {
Results.push_back(CreateTLWrapper(Op, DAG));
}
break;
case ISD::SETCC:
if (shouldWidenToHvx(ty(Inp0), DAG)) {
if (SDValue T = WidenHvxSetCC(Op, DAG))
@ -3932,12 +3985,23 @@ HexagonTargetLowering::ReplaceHvxNodeResults(SDNode *N,
case ISD::ANY_EXTEND:
case ISD::SIGN_EXTEND:
case ISD::ZERO_EXTEND:
case ISD::TRUNCATE:
if (Subtarget.isHVXElementType(ty(Op)) &&
Subtarget.isHVXElementType(ty(Inp0))) {
Results.push_back(CreateTLWrapper(Op, DAG));
}
break;
case ISD::TRUNCATE:
// Handle truncate to boolean vector when the input is not a
// standard HVX vector type. See comment in LowerHvxOperationWrapper.
if (ty(Op).getVectorElementType() == MVT::i1 &&
!Subtarget.isHVXVectorType(ty(Inp0), false)) {
if (SDValue T = WidenHvxTruncateToBool(Op, DAG))
Results.push_back(T);
} else if (Subtarget.isHVXElementType(ty(Op)) &&
Subtarget.isHVXElementType(ty(Inp0))) {
Results.push_back(CreateTLWrapper(Op, DAG));
}
break;
case ISD::SETCC:
if (shouldWidenToHvx(ty(Op), DAG)) {
if (SDValue T = WidenHvxSetCC(Op, DAG))

View file

@ -742,6 +742,9 @@ let Predicates = [UseHVX] in {
(V6_vandvrt HvxVR:$Vs, (ToI32 0x01010101))>;
def: Pat<(VecQ32 (trunc HVI32:$Vs)),
(V6_vandvrt HvxVR:$Vs, (ToI32 0x01010101))>;
def: Pat<(VecQ8 (trunc HWI16:$Vss)),
(Combineq(VecQ16(V6_vandvrt (HiVec $Vss), (ToI32 0x01010101))),
(VecQ16 (V6_vandvrt (LoVec $Vss), (ToI32 0x01010101))))>;
def: Pat<(VecQ16 (trunc HWI32:$Vss)),
(Combineq(VecQ32(V6_vandvrt (HiVec $Vss), (ToI32 0x01010101))),
(VecQ32 (V6_vandvrt (LoVec $Vss), (ToI32 0x01010101))))>;

View file

@ -1,18 +1,140 @@
; RUN: llc --mtriple=hexagon -mattr=+hvxv79,+hvx-length128b < %s | FileCheck %s
define void @f5(<64 x i32> %a0, ptr %a1) {
; CHECK-LABEL: f5:
; CHECK: [[REG0:(r[0-9]+)]] = ##16843009
; CHECK-DAG: q[[Q0:[0-9]+]] = vand(v{{[0-9]+}},[[REG0]])
; CHECK-DAG: q[[Q1:[0-9]+]] = vand(v{{[0-9]+}},[[REG0]])
; CHECK: v{{[0-9]+}}.b = vpacke(v{{[0-9]+}}.h,v{{[0-9]+}}.h)
; CHECK: v{{[0-9]+}}.b = vpacke(v{{[0-9]+}}.h,v{{[0-9]+}}.h)
; CHECK: v[[VROR:[0-9]+]] = vror(v{{[0-9]+}},r{{[0-9]+}})
; CHECK: v[[VOR:[0-9]+]] = vor(v[[VROR]],v{{[0-9]+}})
; CHECK: q{{[0-9]+}} = vand(v[[VOR]],r{{[0-9]+}})
b0:
%v0 = trunc <64 x i32> %a0 to <64 x i1>
store <64 x i1> %v0, ptr %a1, align 1
;
; Exhaustive tests for truncation of HVX vectors to boolean (i1) vectors.
; In 128-byte HVX mode:
; Single register (HvxVR): v128i8, v64i16, v32i32 (1024 bits)
; Pair register (HvxWR): v256i8, v128i16, v64i32 (2048 bits)
; Predicates (HvxQR): v128i1, v64i1, v32i1
;
; Tests cover:
; - Full HVX width (single register): tablegen V6_vandvrt patterns
; - HVX pairs: tablegen Combineq patterns
; - Sub-HVX width: C++ WidenHvxTruncateToBool (widen + trunc + extract)
;
; Very small vectors (v8i16, v8i32) are scalarized by the legalizer
; and do not exercise HVX paths, so they are not included here.
;
; ===========================================================================
; Category 1: Full HVX single-register width -> HVX predicate (tablegen)
; These match tablegen patterns VecQ{8,16,32} (trunc HVI{8,16,32})
; directly via V6_vandvrt.
; ===========================================================================
; CHECK-LABEL: trunc_v128i8_to_v128i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define void @trunc_v128i8_to_v128i1(<128 x i8> %v, ptr %p) {
%1 = trunc <128 x i8> %v to <128 x i1>
store <128 x i1> %1, ptr %p, align 16
ret void
}
; CHECK-LABEL: trunc_v64i16_to_v64i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i64 @trunc_v64i16_to_v64i1(<64 x i16> %v) {
%1 = trunc <64 x i16> %v to <64 x i1>
%2 = bitcast <64 x i1> %1 to i64
ret i64 %2
}
; CHECK-LABEL: trunc_v32i32_to_v32i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i32 @trunc_v32i32_to_v32i1(<32 x i32> %v) {
%1 = trunc <32 x i32> %v to <32 x i1>
%2 = bitcast <32 x i1> %1 to i32
ret i32 %2
}
; ===========================================================================
; Category 2: HVX pair -> HVX predicate (tablegen, via Combineq)
; These split the pair, apply V6_vandvrt to each half, and combine
; the predicate halves.
; ===========================================================================
; CHECK-LABEL: trunc_v64i32_to_v64i1:
; CHECK-DAG: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
; CHECK-DAG: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define void @trunc_v64i32_to_v64i1(<64 x i32> %v, ptr %p) {
%1 = trunc <64 x i32> %v to <64 x i1>
store <64 x i1> %1, ptr %p, align 8
ret void
}
; CHECK-LABEL: trunc_v128i16_to_v128i1:
; CHECK-DAG: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
; CHECK-DAG: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define void @trunc_v128i16_to_v128i1(<128 x i16> %v, ptr %p) {
%1 = trunc <128 x i16> %v to <128 x i1>
store <128 x i1> %1, ptr %p, align 16
ret void
}
; ===========================================================================
; Category 3: Sub-HVX width -> predicate (C++ WidenHvxTruncateToBool)
; Input is narrower than a single HVX register. The input is widened
; to full HVX width, truncated to widened bool, then the result
; subvector is extracted.
; ===========================================================================
; --- Half HVX width (512-bit input) ---
; CHECK-LABEL: trunc_v64i8_to_v64i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i64 @trunc_v64i8_to_v64i1(<64 x i8> %v) {
%1 = trunc <64 x i8> %v to <64 x i1>
%2 = bitcast <64 x i1> %1 to i64
ret i64 %2
}
; CHECK-LABEL: trunc_v32i16_to_v32i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i32 @trunc_v32i16_to_v32i1(<32 x i16> %v) {
%1 = trunc <32 x i16> %v to <32 x i1>
%2 = bitcast <32 x i1> %1 to i32
ret i32 %2
}
; CHECK-LABEL: trunc_v16i32_to_v16i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i16 @trunc_v16i32_to_v16i1(<16 x i32> %v) {
%1 = trunc <16 x i32> %v to <16 x i1>
%2 = bitcast <16 x i1> %1 to i16
ret i16 %2
}
; --- Quarter HVX width (256-bit input) ---
; CHECK-LABEL: trunc_v32i8_to_v32i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i32 @trunc_v32i8_to_v32i1(<32 x i8> %v) {
%1 = trunc <32 x i8> %v to <32 x i1>
%2 = bitcast <32 x i1> %1 to i32
ret i32 %2
}
; CHECK-LABEL: trunc_v16i16_to_v16i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i16 @trunc_v16i16_to_v16i1(<16 x i16> %v) {
%1 = trunc <16 x i16> %v to <16 x i1>
%2 = bitcast <16 x i1> %1 to i16
ret i16 %2
}
; --- Eighth HVX width and smaller (128-bit input) ---
; CHECK-LABEL: trunc_v16i8_to_v16i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i16 @trunc_v16i8_to_v16i1(<16 x i8> %v) {
%1 = trunc <16 x i8> %v to <16 x i1>
%2 = bitcast <16 x i1> %1 to i16
ret i16 %2
}
; CHECK-LABEL: trunc_v4i32_to_v4i1:
; CHECK: q{{[0-9]+}} = vand(v{{[0-9]+}},r{{[0-9]+}})
define i4 @trunc_v4i32_to_v4i1(<4 x i32> %v) {
%1 = trunc <4 x i32> %v to <4 x i1>
%2 = bitcast <4 x i1> %1 to i4
ret i4 %2
}