[llvm] [RISCV] Fix crash in combinePExtTruncate for truncate(srl) without MUL/SUB (PR #186141)

Kito Cheng via llvm-commits llvm-commits at lists.llvm.org
Thu Mar 12 08:14:31 PDT 2026


https://github.com/kito-cheng created https://github.com/llvm/llvm-project/pull/186141

combinePExtTruncate is called from performTRUNCATECombine when the P-extension is enabled. It attempts to match patterns like truncate(srl(mul/sub(...), shamt)) and combine them into P-extension narrowing shift instructions (e.g. PNSRLI, PNSRAI).

However, after extracting the shift input operand `Op` from the SRL node, the function unconditionally accessed Op.getOperand(0) and Op.getOperand(1) without first verifying that Op has at least two operands. For example, when combining:

```
  truncate(v2i16
    srl(v2i32
      bitcast(v2i32 i64),   <-- Op = bitcast, a unary op with 1 operand
      BUILD_VECTOR <8, 8>))
```

Op is a BITCAST node (unary, only 1 operand), so accessing Op.getOperand(1) triggers an out-of-bounds assertion:

```
  Assertion `Num < NumOperands && "Invalid child # of SDNode!"' failed.
```

Add an early return when Op has fewer than two operands.

>From 15f0069f7d03133c1d42aa60118ab575127585f1 Mon Sep 17 00:00:00 2001
From: Kito Cheng <kito.cheng at sifive.com>
Date: Thu, 12 Mar 2026 22:50:26 +0800
Subject: [PATCH] [RISCV] Fix crash in combinePExtTruncate for truncate(srl)
 without MUL/SUB

combinePExtTruncate is called from performTRUNCATECombine when the
P-extension is enabled. It attempts to match patterns like
truncate(srl(mul/sub(...), shamt)) and combine them into P-extension
narrowing shift instructions (e.g. PNSRLI, PNSRAI).

However, after extracting the shift input operand `Op` from the SRL
node, the function unconditionally accessed Op.getOperand(0) and
Op.getOperand(1) without first verifying that Op has at least two
operands. For example, when combining:

  truncate(v2i16
    srl(v2i32
      bitcast(v2i32 i64),   <-- Op = bitcast, a unary op with 1 operand
      BUILD_VECTOR <8, 8>))

Op is a BITCAST node (unary, only 1 operand), so accessing
Op.getOperand(1) triggers an out-of-bounds assertion:

  Assertion `Num < NumOperands && "Invalid child # of SDNode!"' failed.

Add an early return when Op has fewer than two operands.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  4 +
 .../RISCV/rvp-narrowing-shift-trunc.ll        | 80 +++++++++++++++++++
 2 files changed, 84 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/rvp-narrowing-shift-trunc.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 510034f96ca19..9146ef7249777 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -16965,6 +16965,10 @@ static SDValue combinePExtTruncate(SDNode *N, SelectionDAG &DAG,
     }
   }
 
+  // Ensure Op is a binary operation before accessing its operands.
+  if (Op.getNumOperands() < 2)
+    return SDValue();
+
   SDValue LHS = Op.getOperand(0);
   SDValue RHS = Op.getOperand(1);
 
diff --git a/llvm/test/CodeGen/RISCV/rvp-narrowing-shift-trunc.ll b/llvm/test/CodeGen/RISCV/rvp-narrowing-shift-trunc.ll
new file mode 100644
index 0000000000000..4fe4ed66fa842
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rvp-narrowing-shift-trunc.ll
@@ -0,0 +1,80 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+experimental-p,+m,+zbb \
+; RUN:   -verify-machineinstrs < %s | \
+; RUN:   FileCheck --check-prefixes=CHECK-RV32 %s
+; RUN: llc -mtriple=riscv64 -mattr=+experimental-p,+m,+zbb \
+; RUN:   -verify-machineinstrs < %s | \
+; RUN:   FileCheck --check-prefixes=CHECK-RV64 %s
+
+; Regression test: combinePExtTruncate must not crash when the operand of a
+; narrowing shift is not a MUL/SUB (e.g. a plain function argument).
+
+define i32 @trunc_lshr_v2i32_to_v2i16(i64 %a.coerce) {
+; CHECK-RV32-LABEL: trunc_lshr_v2i32_to_v2i16:
+; CHECK-RV32:       # %bb.0:
+; CHECK-RV32-NEXT:    srli a1, a1, 8
+; CHECK-RV32-NEXT:    srli a0, a0, 8
+; CHECK-RV32-NEXT:    pack a0, a0, a1
+; CHECK-RV32-NEXT:    ret
+;
+; CHECK-RV64-LABEL: trunc_lshr_v2i32_to_v2i16:
+; CHECK-RV64:       # %bb.0:
+; CHECK-RV64-NEXT:    psrli.w a0, a0, 8
+; CHECK-RV64-NEXT:    srli a1, a0, 32
+; CHECK-RV64-NEXT:    ppaire.h a0, a0, a1
+; CHECK-RV64-NEXT:    ret
+  %v = bitcast i64 %a.coerce to <2 x i32>
+  %shr = lshr <2 x i32> %v, splat (i32 8)
+  %conv = trunc <2 x i32> %shr to <2 x i16>
+  %ret = bitcast <2 x i16> %conv to i32
+  ret i32 %ret
+}
+
+define i32 @trunc_ashr_v2i32_to_v2i16(i64 %a.coerce) {
+; CHECK-RV32-LABEL: trunc_ashr_v2i32_to_v2i16:
+; CHECK-RV32:       # %bb.0:
+; CHECK-RV32-NEXT:    srli a1, a1, 8
+; CHECK-RV32-NEXT:    srli a0, a0, 8
+; CHECK-RV32-NEXT:    pack a0, a0, a1
+; CHECK-RV32-NEXT:    ret
+;
+; CHECK-RV64-LABEL: trunc_ashr_v2i32_to_v2i16:
+; CHECK-RV64:       # %bb.0:
+; CHECK-RV64-NEXT:    psrli.w a0, a0, 8
+; CHECK-RV64-NEXT:    srli a1, a0, 32
+; CHECK-RV64-NEXT:    ppaire.h a0, a0, a1
+; CHECK-RV64-NEXT:    ret
+  %v = bitcast i64 %a.coerce to <2 x i32>
+  %shr = ashr <2 x i32> %v, splat (i32 8)
+  %conv = trunc <2 x i32> %shr to <2 x i16>
+  %ret = bitcast <2 x i16> %conv to i32
+  ret i32 %ret
+}
+
+define i32 @trunc_lshr_v4i16_to_v4i8(i64 %a.coerce) {
+; CHECK-RV32-LABEL: trunc_lshr_v4i16_to_v4i8:
+; CHECK-RV32:       # %bb.0:
+; CHECK-RV32-NEXT:    psrli.h a1, a1, 4
+; CHECK-RV32-NEXT:    psrli.h a0, a0, 4
+; CHECK-RV32-NEXT:    srli a3, a1, 16
+; CHECK-RV32-NEXT:    srli a2, a0, 16
+; CHECK-RV32-NEXT:    ppaire.db a0, a0, a2
+; CHECK-RV32-NEXT:    pack a0, a0, a1
+; CHECK-RV32-NEXT:    ret
+;
+; CHECK-RV64-LABEL: trunc_lshr_v4i16_to_v4i8:
+; CHECK-RV64:       # %bb.0:
+; CHECK-RV64-NEXT:    psrli.h a0, a0, 4
+; CHECK-RV64-NEXT:    srli a1, a0, 48
+; CHECK-RV64-NEXT:    srli a2, a0, 32
+; CHECK-RV64-NEXT:    ppaire.b a1, a2, a1
+; CHECK-RV64-NEXT:    srli a2, a0, 16
+; CHECK-RV64-NEXT:    ppaire.b a0, a0, a2
+; CHECK-RV64-NEXT:    ppaire.h a0, a0, a1
+; CHECK-RV64-NEXT:    ret
+  %v = bitcast i64 %a.coerce to <4 x i16>
+  %shr = lshr <4 x i16> %v, splat (i16 4)
+  %conv = trunc <4 x i16> %shr to <4 x i8>
+  %ret = bitcast <4 x i8> %conv to i32
+  ret i32 %ret
+}



More information about the llvm-commits mailing list