[clang] [llvm] [RISCV][P-ext] Add scalar mulq(r) intrinsics (PR #232316)

via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 15 06:06:02 PDT 2026


https://github.com/sihuan created https://github.com/llvm/llvm-project/pull/232316

Add the scalar "Q-format" Multiplication intrinsics from the P-extension
intrinsic spec:
https://github.com/riscv/riscv-p-spec/blob/master/P-ext-intrinsics.adoc#q-format-multiplication

- `__riscv_mulq_i32`
- `__riscv_mulqr_i32`

RV32 selects `mulq`/`mulqr` directly. RV64 has no scalar forms, so the
intrinsics use `pmulq.w`/`pmulqr.w` on the low words, the same way as the
scalar `aadd`/`asub` intrinsics (#230710).


>From d664b38942b82c3661c99bfb7fc53a9a45acaed9 Mon Sep 17 00:00:00 2001
From: SiHuaN <liyongtai at iscas.ac.cn>
Date: Thu, 15 Oct 2026 12:28:40 +0000
Subject: [PATCH] [RISCV][P-ext] Add scalar mulq(r) intrinsics

Add __riscv_mulq_i32 and __riscv_mulqr_i32. RV32 selects mulq/mulqr
directly. RV64 has no scalar forms and uses pmulq.w/pmulqr.w on the low
words.
---
 clang/include/clang/Basic/BuiltinsRISCV.td    |  4 +++
 clang/lib/CodeGen/TargetBuiltins/RISCV.cpp    |  8 +++++
 clang/lib/Headers/riscv_packed_simd.h         |  4 +++
 clang/test/CodeGen/RISCV/rvp-intrinsics.c     | 34 +++++++++++++++++++
 .../riscv_packed_simd.c                       | 15 ++++++++
 llvm/include/llvm/IR/IntrinsicsRISCV.td       |  5 +++
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   | 34 +++++++++++++++++++
 llvm/test/CodeGen/RISCV/rv32p.ll              | 18 ++++++++++
 llvm/test/CodeGen/RISCV/rv64p.ll              | 18 ++++++++++
 9 files changed, 140 insertions(+)

diff --git a/clang/include/clang/Basic/BuiltinsRISCV.td b/clang/include/clang/Basic/BuiltinsRISCV.td
index 9004c815c8a1e..44ab52124d117 100644
--- a/clang/include/clang/Basic/BuiltinsRISCV.td
+++ b/clang/include/clang/Basic/BuiltinsRISCV.td
@@ -278,6 +278,10 @@ def mulhru_u32 : RISCVBuiltin<"uint32_t(uint32_t, uint32_t)">;
 def mulhsu_i32 : RISCVBuiltin<"int32_t(int32_t, uint32_t)">;
 def mulhrsu_i32 : RISCVBuiltin<"int32_t(int32_t, uint32_t)">;
 
+// Scalar "Q-format" Multiplication
+def mulq_i32 : RISCVBuiltin<"int32_t(int32_t, int32_t)">;
+def mulqr_i32 : RISCVBuiltin<"int32_t(int32_t, int32_t)">;
+
 // Packed Multiply High (32-bit)
 def pmulh_i16x2 : RISCVBuiltin<"_Vector<2, short>(_Vector<2, short>, _Vector<2, short>)">;
 def pmulhr_i16x2 : RISCVBuiltin<"_Vector<2, short>(_Vector<2, short>, _Vector<2, short>)">;
diff --git a/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp b/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
index e6f1d3c59b946..08c237196d5a0 100644
--- a/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/RISCV.cpp
@@ -1719,6 +1719,14 @@ Value *CodeGenFunction::EmitRISCVBuiltinExpr(unsigned BuiltinID,
     break;
   }
 
+  // Scalar "Q-format" Multiplication
+  case RISCV::BI__builtin_riscv_mulq_i32:
+    ID = Intrinsic::riscv_mulq_i32;
+    break;
+  case RISCV::BI__builtin_riscv_mulqr_i32:
+    ID = Intrinsic::riscv_mulqr_i32;
+    break;
+
   // Packed "Q-format" Multiply with Widening Accumulate
   case RISCV::BI__builtin_riscv_pmqwacc_i32x2:
   case RISCV::BI__builtin_riscv_pmqrwacc_i32x2: {
diff --git a/clang/lib/Headers/riscv_packed_simd.h b/clang/lib/Headers/riscv_packed_simd.h
index 4bf017f81db9f..3627776c290f6 100644
--- a/clang/lib/Headers/riscv_packed_simd.h
+++ b/clang/lib/Headers/riscv_packed_simd.h
@@ -478,6 +478,10 @@ __packed_mulh_builtin(mulhsu_i32, int32_t, int32_t, uint32_t)
 __packed_mulh_builtin(mulhrsu_i32, int32_t, int32_t, uint32_t)
 #undef __packed_mulh_builtin
 
+/* Scalar "Q-format" Multiplication */
+__packed_binary_builtin(mulq_i32, int32_t, __builtin_riscv_mulq_i32)
+__packed_binary_builtin(mulqr_i32, int32_t, __builtin_riscv_mulqr_i32)
+
 /* Packed Splat (32-bit) */
 __packed_splat(pmv_s_u8x4, uint8x4_t, uint8_t, __packed_splat4)
 __packed_splat(pmv_s_i8x4, int8x4_t, int8_t, __packed_splat4)
diff --git a/clang/test/CodeGen/RISCV/rvp-intrinsics.c b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
index b672c04bc35d5..cdc5c7b980f4b 100644
--- a/clang/test/CodeGen/RISCV/rvp-intrinsics.c
+++ b/clang/test/CodeGen/RISCV/rvp-intrinsics.c
@@ -396,6 +396,40 @@ int32_t test_mulhrsu_i32(int32_t a, uint32_t b) {
   return __riscv_mulhrsu_i32(a, b);
 }
 
+/* Scalar "Q-format" Multiplication */
+
+// RV32-LABEL: define dso_local i32 @test_mulq_i32(
+// RV32-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = call i32 @llvm.riscv.mulq.i32(i32 [[A]], i32 [[B]])
+// RV32-NEXT:    ret i32 [[TMP0]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_mulq_i32(
+// RV64-SAME: i32 noundef signext [[A:%.*]], i32 noundef signext [[B:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = call i32 @llvm.riscv.mulq.i32(i32 [[A]], i32 [[B]])
+// RV64-NEXT:    ret i32 [[TMP0]]
+//
+int32_t test_mulq_i32(int32_t a, int32_t b) {
+  return __riscv_mulq_i32(a, b);
+}
+
+// RV32-LABEL: define dso_local i32 @test_mulqr_i32(
+// RV32-SAME: i32 noundef [[A:%.*]], i32 noundef [[B:%.*]]) #[[ATTR0]] {
+// RV32-NEXT:  [[ENTRY:.*:]]
+// RV32-NEXT:    [[TMP0:%.*]] = call i32 @llvm.riscv.mulqr.i32(i32 [[A]], i32 [[B]])
+// RV32-NEXT:    ret i32 [[TMP0]]
+//
+// RV64-LABEL: define dso_local signext i32 @test_mulqr_i32(
+// RV64-SAME: i32 noundef signext [[A:%.*]], i32 noundef signext [[B:%.*]]) #[[ATTR0]] {
+// RV64-NEXT:  [[ENTRY:.*:]]
+// RV64-NEXT:    [[TMP0:%.*]] = call i32 @llvm.riscv.mulqr.i32(i32 [[A]], i32 [[B]])
+// RV64-NEXT:    ret i32 [[TMP0]]
+//
+int32_t test_mulqr_i32(int32_t a, int32_t b) {
+  return __riscv_mulqr_i32(a, b);
+}
+
 /* Packed Splat (32-bit) */
 
 // CHECK-LABEL: define dso_local i32 @test_pmv_s_u8x4(
diff --git a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
index 75c5a1ff8b75d..5b580bf13cc93 100644
--- a/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
+++ b/cross-project-tests/intrinsic-header-tests/riscv_packed_simd.c
@@ -3122,6 +3122,21 @@ int32_t test_mulhrsu_i32(int32_t rs1, uint32_t rs2) {
   return __riscv_mulhrsu_i32(rs1, rs2);
 }
 
+// Scalar "Q-format" multiplication
+// CHECK-LABEL: test_mulq_i32:
+// RV32:        mulq{{[[:space:]]}}
+// RV64:        pmulq.w
+int32_t test_mulq_i32(int32_t rs1, int32_t rs2) {
+  return __riscv_mulq_i32(rs1, rs2);
+}
+
+// CHECK-LABEL: test_mulqr_i32:
+// RV32:        mulqr{{[[:space:]]}}
+// RV64:        pmulqr.w
+int32_t test_mulqr_i32(int32_t rs1, int32_t rs2) {
+  return __riscv_mulqr_i32(rs1, rs2);
+}
+
 // CHECK-LABEL: test_pmulh_i16x2:
 // CHECK:       pmulh.h
 int16x2_t test_pmulh_i16x2(int16x2_t rs1, int16x2_t rs2) {
diff --git a/llvm/include/llvm/IR/IntrinsicsRISCV.td b/llvm/include/llvm/IR/IntrinsicsRISCV.td
index c33f0fa792f0f..926641511ae38 100644
--- a/llvm/include/llvm/IR/IntrinsicsRISCV.td
+++ b/llvm/include/llvm/IR/IntrinsicsRISCV.td
@@ -2186,6 +2186,11 @@ class RVPBinaryIntrinsic
   def int_riscv_mulhsu_i32  : RVPScalarMulHighIntrinsic;
   def int_riscv_mulhrsu_i32 : RVPScalarMulHighIntrinsic;
 
+  // Scalar "Q-format" Multiplication. On RV64 these map to the packed
+  // pmulq.w family on the low words.
+  def int_riscv_mulq_i32  : RVPScalarBinaryIntrinsic;
+  def int_riscv_mulqr_i32 : RVPScalarBinaryIntrinsic;
+
   // Packed Multiply High Accumulate.
   def int_riscv_pmhacc    : RVPTernaryIntrinsic;
   def int_riscv_pmhracc   : RVPTernaryIntrinsic;
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 00dbaeab507cb..f6afbc158baad 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -12433,6 +12433,17 @@ static unsigned getRVScalarMulHighOpcode(unsigned IntNo) {
   }
 }
 
+static unsigned getRVScalarQFormatMulOpcode(unsigned IntNo) {
+  switch (IntNo) {
+  default:
+    llvm_unreachable("Unexpected RISC-V scalar Q-format multiply intrinsic");
+  case Intrinsic::riscv_mulq_i32:
+    return RISCVISD::MULQ;
+  case Intrinsic::riscv_mulqr_i32:
+    return RISCVISD::MULQR;
+  }
+}
+
 static unsigned getRVPMulHighAccumulateOpcode(unsigned IntNo) {
   switch (IntNo) {
   default:
@@ -13364,6 +13375,13 @@ SDValue RISCVTargetLowering::LowerINTRINSIC_WO_CHAIN(SDValue Op,
     unsigned Opc = getRVScalarMulHighOpcode(IntNo);
     return DAG.getNode(Opc, DL, MVT::i32, Op.getOperand(1), Op.getOperand(2));
   }
+  case Intrinsic::riscv_mulq_i32:
+  case Intrinsic::riscv_mulqr_i32: {
+    // RV32 selects the scalar P instructions directly. RV64 goes via
+    // ReplaceNodeResults.
+    unsigned Opc = getRVScalarQFormatMulOpcode(IntNo);
+    return DAG.getNode(Opc, DL, MVT::i32, Op.getOperand(1), Op.getOperand(2));
+  }
   case Intrinsic::riscv_pmhacc:
   case Intrinsic::riscv_pmhracc:
   case Intrinsic::riscv_pmhaccu:
@@ -18059,6 +18077,22 @@ void RISCVTargetLowering::ReplaceNodeResults(SDNode *N,
       Results.push_back(DAG.getExtractVectorElt(DL, MVT::i32, Res, 0));
       return;
     }
+    case Intrinsic::riscv_mulq_i32:
+    case Intrinsic::riscv_mulqr_i32: {
+      // RV64 has no scalar Q-format multiply instructions; reuse the packed
+      // pmulq.w family on the low words, whose element 0 is the scalar result.
+      MVT VT = N->getSimpleValueType(0);
+      if (!Subtarget.is64Bit() || VT != MVT::i32)
+        return;
+      unsigned Opc = getRVScalarQFormatMulOpcode(IntNo);
+      SDValue Rs1 =
+          DAG.getNode(ISD::SCALAR_TO_VECTOR, DL, MVT::v2i32, N->getOperand(1));
+      SDValue Rs2 =
+          DAG.getNode(ISD::SCALAR_TO_VECTOR, DL, MVT::v2i32, N->getOperand(2));
+      SDValue Res = DAG.getNode(Opc, DL, MVT::v2i32, Rs1, Rs2);
+      Results.push_back(DAG.getExtractVectorElt(DL, MVT::i32, Res, 0));
+      return;
+    }
     case Intrinsic::riscv_pnclipp:
     case Intrinsic::riscv_pnclipup: {
       bool IsSigned = IntNo == Intrinsic::riscv_pnclipp;
diff --git a/llvm/test/CodeGen/RISCV/rv32p.ll b/llvm/test/CodeGen/RISCV/rv32p.ll
index 1a4d623427a0a..9bd8e87a1e253 100644
--- a/llvm/test/CodeGen/RISCV/rv32p.ll
+++ b/llvm/test/CodeGen/RISCV/rv32p.ll
@@ -2421,3 +2421,21 @@ define i32 @test_mulhrsu_i32(i32 %rs1, i32 %rs2) {
   %res = call i32 @llvm.riscv.mulhrsu.i32(i32 %rs1, i32 %rs2)
   ret i32 %res
 }
+
+define i32 @test_mulq_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: test_mulq_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    mulq a0, a0, a1
+; CHECK-NEXT:    ret
+  %res = call i32 @llvm.riscv.mulq.i32(i32 %a, i32 %b)
+  ret i32 %res
+}
+
+define i32 @test_mulqr_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: test_mulqr_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    mulqr a0, a0, a1
+; CHECK-NEXT:    ret
+  %res = call i32 @llvm.riscv.mulqr.i32(i32 %a, i32 %b)
+  ret i32 %res
+}
diff --git a/llvm/test/CodeGen/RISCV/rv64p.ll b/llvm/test/CodeGen/RISCV/rv64p.ll
index 5ceae2996fe3d..d94ff361cf4f1 100644
--- a/llvm/test/CodeGen/RISCV/rv64p.ll
+++ b/llvm/test/CodeGen/RISCV/rv64p.ll
@@ -1897,3 +1897,21 @@ define i32 @test_asubu_u32(i32 %a, i32 %b) {
   %res = call i32 @llvm.riscv.asubu.u32(i32 %a, i32 %b)
   ret i32 %res
 }
+
+define i32 @test_mulq_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: test_mulq_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pmulq.w a0, a0, a1
+; CHECK-NEXT:    ret
+  %res = call i32 @llvm.riscv.mulq.i32(i32 %a, i32 %b)
+  ret i32 %res
+}
+
+define i32 @test_mulqr_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: test_mulqr_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pmulqr.w a0, a0, a1
+; CHECK-NEXT:    ret
+  %res = call i32 @llvm.riscv.mulqr.i32(i32 %a, i32 %b)
+  ret i32 %res
+}



More information about the cfe-commits mailing list