[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