Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
SDValue Result;
switch (N->getOpcode()) {
case ISD::SPLAT_VECTOR: {
if (Subtarget->enablePExtCodeGen())
break;
// Convert integer SPLAT_VECTOR to VMV_V_X_VL and floating-point
// SPLAT_VECTOR to VFMV_V_F_VL to reduce isel burden.
MVT VT = N->getSimpleValueType(0);
Expand Down
33 changes: 1 addition & 32 deletions llvm/lib/Target/RISCV/RISCVISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ RISCVTargetLowering::RISCVTargetLowering(const TargetMachine &TM,
setOperationAction(ISD::SSUBSAT, VTs, Legal);
setOperationAction({ISD::AVGFLOORS, ISD::AVGFLOORU}, VTs, Legal);
setOperationAction({ISD::ABDS, ISD::ABDU}, VTs, Legal);
setOperationAction(ISD::BUILD_VECTOR, VTs, Custom);
setOperationAction(ISD::SPLAT_VECTOR, VTs, Legal);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there an instruction that can splat a scalar that isn't a constant? If you make an operation Legal, we must have isel patterns for all possible inputs.

Copy link
Member Author

@4vtomat 4vtomat Nov 16, 2025

Choose a reason for hiding this comment

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

that makes sense!
Does it mean when we set an operation to Custom, we also have to handle operation legalization for every case, in this case SPLAT_VECTOR for non-const? If so, is it also possible to miss some opportunities for better instruction selection in pattern matching?
for example, if we try to legalize v2i16 SPLAT_VECTOR(i16 a) to something like v2i16 cast(or(a, shl(a, 16))) and we have a pattern that match v2i16 shl SPLAT_VECTOR(i16 a) -> PSLL_H a, it will fail to match unless we don't handle legalization for non-const case and consider it as legal

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't we use PADD.BS, PADD.HS, PADD.WS with X0 for splat_vector?

Copy link
Member Author

Choose a reason for hiding this comment

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

Does it mean theoretically we should handle both const and non-const case when we set the operation to custom?

Copy link
Collaborator

@topperc topperc Nov 17, 2025

Choose a reason for hiding this comment

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

It means we can make it legal and add patterns to use PADD.BS, PADD.HS, PADD.WS with X0 when the operand isn't a constant.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh I see

setOperationAction(ISD::BITCAST, VTs, Custom);
setOperationAction(ISD::EXTRACT_VECTOR_ELT, VTs, Custom);
}
Expand Down Expand Up @@ -4437,37 +4437,6 @@ static SDValue lowerBUILD_VECTOR(SDValue Op, SelectionDAG &DAG,
MVT XLenVT = Subtarget.getXLenVT();

SDLoc DL(Op);
// Handle P extension packed vector BUILD_VECTOR with PLI for splat constants
if (Subtarget.enablePExtCodeGen()) {
bool IsPExtVector =
(VT == MVT::v2i16 || VT == MVT::v4i8) ||
(Subtarget.is64Bit() &&
(VT == MVT::v4i16 || VT == MVT::v8i8 || VT == MVT::v2i32));
if (IsPExtVector) {
if (SDValue SplatValue = cast<BuildVectorSDNode>(Op)->getSplatValue()) {
if (auto *C = dyn_cast<ConstantSDNode>(SplatValue)) {
int64_t SplatImm = C->getSExtValue();
bool IsValidImm = false;

// Check immediate range based on vector type
if (VT == MVT::v8i8 || VT == MVT::v4i8) {
// PLI_B uses 8-bit unsigned or unsigned immediate
IsValidImm = isUInt<8>(SplatImm) || isInt<8>(SplatImm);
if (isUInt<8>(SplatImm))
SplatImm = (int8_t)SplatImm;
} else {
// PLI_H and PLI_W use 10-bit signed immediate
IsValidImm = isInt<10>(SplatImm);
}

if (IsValidImm) {
SDValue Imm = DAG.getSignedTargetConstant(SplatImm, DL, XLenVT);
return DAG.getNode(RISCVISD::PLI, DL, VT, Imm);
}
}
}
}
}

// Proper support for f16 requires Zvfh. bf16 always requires special
// handling. We need to cast the scalar to integer and create an integer
Expand Down
20 changes: 11 additions & 9 deletions llvm/lib/Target/RISCV/RISCVInstrInfoP.td
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
// Operand and SDNode transformation definitions.
//===----------------------------------------------------------------------===//

def simm10 : RISCVSImmOp<10>, TImmLeaf<XLenVT, "return isInt<10>(Imm);">;
def simm10 : RISCVSImmOp<10>, ImmLeaf<XLenVT, "return isInt<10>(Imm);">;

def SImm8UnsignedAsmOperand : SImmAsmOperand<8, "Unsigned"> {
let RenderMethod = "addSImm8UnsignedOperands";
}

// A 8-bit signed immediate allowing range [-128, 255]
// but represented as [-128, 127].
def simm8_unsigned : RISCVOp, TImmLeaf<XLenVT, "return isInt<8>(Imm);"> {
def simm8_unsigned : RISCVOp, ImmLeaf<XLenVT, "return isInt<8>(Imm);"> {
let ParserMatchClass = SImm8UnsignedAsmOperand;
let EncoderMethod = "getImmOpValue";
let DecoderMethod = "decodeSImmOperand<8>";
Expand Down Expand Up @@ -1463,10 +1463,6 @@ let Predicates = [HasStdExtP, IsRV32] in {

def riscv_absw : RVSDNode<"ABSW", SDTIntUnaryOp>;

def SDT_RISCVPLI : SDTypeProfile<1, 1, [SDTCisVec<0>,
SDTCisInt<0>,
SDTCisInt<1>]>;
def riscv_pli : RVSDNode<"PLI", SDT_RISCVPLI>;
def SDT_RISCVPASUB : SDTypeProfile<1, 2, [SDTCisVec<0>,
SDTCisInt<0>,
SDTCisSameAs<0, 1>,
Expand Down Expand Up @@ -1519,10 +1515,13 @@ let Predicates = [HasStdExtP] in {


// 8-bit PLI SD node pattern
def: Pat<(XLenVecI8VT (riscv_pli simm8_unsigned:$imm8)), (PLI_B simm8_unsigned:$imm8)>;
def: Pat<(XLenVecI8VT (splat_vector simm8_unsigned:$imm8)), (PLI_B simm8_unsigned:$imm8)>;
// 16-bit PLI SD node pattern
def: Pat<(XLenVecI16VT (riscv_pli simm10:$imm10)), (PLI_H simm10:$imm10)>;
def: Pat<(XLenVecI16VT (splat_vector simm10:$imm10)), (PLI_H simm10:$imm10)>;

// // splat pattern
def: Pat<(XLenVecI8VT (splat_vector (XLenVT GPR:$rs2))), (PADD_BS (XLenVT X0), GPR:$rs2)>;
def: Pat<(XLenVecI16VT (splat_vector (XLenVT GPR:$rs2))), (PADD_HS (XLenVT X0), GPR:$rs2)>;
} // Predicates = [HasStdExtP]

let Predicates = [HasStdExtP, IsRV32] in {
Expand All @@ -1537,7 +1536,7 @@ let Predicates = [HasStdExtP, IsRV64] in {
def : PatGpr<riscv_absw, ABSW>;

// 32-bit PLI SD node pattern
def: Pat<(v2i32 (riscv_pli simm10:$imm10)), (PLI_W simm10:$imm10)>;
def: Pat<(v2i32 (splat_vector simm10:$imm10)), (PLI_W simm10:$imm10)>;

// Basic 32-bit arithmetic patterns
def: Pat<(v2i32 (add GPR:$rs1, GPR:$rs2)), (PADD_W GPR:$rs1, GPR:$rs2)>;
Expand All @@ -1557,6 +1556,9 @@ let Predicates = [HasStdExtP, IsRV64] in {
def: Pat<(v2i32 (riscv_pasub GPR:$rs1, GPR:$rs2)), (PASUB_W GPR:$rs1, GPR:$rs2)>;
def: Pat<(v2i32 (riscv_pasubu GPR:$rs1, GPR:$rs2)), (PASUBU_W GPR:$rs1, GPR:$rs2)>;

// splat pattern
def: Pat<(v2i32 (splat_vector (XLenVT GPR:$rs2))), (PADD_WS (XLenVT X0), GPR:$rs2)>;

// Load/Store patterns
def : StPat<store, SD, GPR, v8i8>;
def : StPat<store, SD, GPR, v4i16>;
Expand Down
27 changes: 27 additions & 0 deletions llvm/test/CodeGen/RISCV/rvp-ext-rv32.ll
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,33 @@ define void @test_extract_vector_8(ptr %ret_ptr, ptr %a_ptr) {
ret void
}

; Test for splat
define void @test_non_const_splat_i8(ptr %ret_ptr, ptr %a_ptr, i8 %elt) {
; CHECK-LABEL: test_non_const_splat_i8:
; CHECK: # %bb.0:
; CHECK-NEXT: padd.bs a1, zero, a2
; CHECK-NEXT: sw a1, 0(a0)
; CHECK-NEXT: ret
%a = load <4 x i8>, ptr %a_ptr
%insert = insertelement <4 x i8> poison, i8 %elt, i32 0
%splat = shufflevector <4 x i8> %insert, <4 x i8> poison, <4 x i32> zeroinitializer
store <4 x i8> %splat, ptr %ret_ptr
ret void
}

define void @test_non_const_splat_i16(ptr %ret_ptr, ptr %a_ptr, i16 %elt) {
; CHECK-LABEL: test_non_const_splat_i16:
; CHECK: # %bb.0:
; CHECK-NEXT: padd.hs a1, zero, a2
; CHECK-NEXT: sw a1, 0(a0)
; CHECK-NEXT: ret
%a = load <2 x i16>, ptr %a_ptr
%insert = insertelement <2 x i16> poison, i16 %elt, i32 0
%splat = shufflevector <2 x i16> %insert, <2 x i16> poison, <2 x i32> zeroinitializer
store <2 x i16> %splat, ptr %ret_ptr
ret void
}

; Intrinsic declarations
declare <2 x i16> @llvm.sadd.sat.v2i16(<2 x i16>, <2 x i16>)
declare <2 x i16> @llvm.uadd.sat.v2i16(<2 x i16>, <2 x i16>)
Expand Down
14 changes: 14 additions & 0 deletions llvm/test/CodeGen/RISCV/rvp-ext-rv64.ll
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,20 @@ define void @test_pasubu_w(ptr %ret_ptr, ptr %a_ptr, ptr %b_ptr) {
ret void
}

; Test for splat
define void @test_non_const_splat_i32(ptr %ret_ptr, ptr %a_ptr, i32 %elt) {
; CHECK-LABEL: test_non_const_splat_i32:
; CHECK: # %bb.0:
; CHECK-NEXT: padd.ws a1, zero, a2
; CHECK-NEXT: sd a1, 0(a0)
; CHECK-NEXT: ret
%a = load <2 x i32>, ptr %a_ptr
%insert = insertelement <2 x i32> poison, i32 %elt, i32 0
%splat = shufflevector <2 x i32> %insert, <2 x i32> poison, <2 x i32> zeroinitializer
store <2 x i32> %splat, ptr %ret_ptr
ret void
}

; Intrinsic declarations
declare <4 x i16> @llvm.sadd.sat.v4i16(<4 x i16>, <4 x i16>)
declare <4 x i16> @llvm.uadd.sat.v4i16(<4 x i16>, <4 x i16>)
Expand Down
Loading