[llvm] [DAGCombiner] Preserve nsw when folding (mul x, 2^c) to (shl x, c) (PR #192366)
Jim Lin via llvm-commits
llvm-commits at lists.llvm.org
Sun Apr 19 22:49:33 PDT 2026
https://github.com/tclin914 updated https://github.com/llvm/llvm-project/pull/192366
>From 583fa976cf56f85abc215c524b14dd602bc0218f Mon Sep 17 00:00:00 2001
From: Jim Lin <jim at andestech.com>
Date: Mon, 20 Apr 2026 02:11:29 +0000
Subject: [PATCH 1/2] [RISCV] Pre-commit test for (mul x, 2^c) -> (shl x, c)
flag preservation
Baseline MIR test showing that DAGCombiner folds mul-by-power-of-two
into SLLI. Currently nsw is not preserved on the shift. Preparatory for
a follow-up that preserves nsw when the shift amount is less than
BitWidth - 1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll | 58 +++++++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
diff --git a/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll b/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
new file mode 100644
index 0000000000000..7dd94654a79f4
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
@@ -0,0 +1,58 @@
+; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+; RUN: llc -mtriple=riscv64 -stop-after=finalize-isel -o - %s | FileCheck %s
+
+; Verifies that (mul x, 2^c) -> (shl x, c) fold fires
+; for nsw, nuw, and plain mul by power-of-two, including the signed-minimum
+; boundary case.
+
+define i64 @mul_nsw_pow2(i64 %x) {
+ ; CHECK-LABEL: name: mul_nsw_pow2
+ ; CHECK: bb.0 (%ir-block.0):
+ ; CHECK-NEXT: liveins: $x10
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-NEXT: [[SLLI:%[0-9]+]]:gpr = SLLI [[COPY]], 2
+ ; CHECK-NEXT: $x10 = COPY [[SLLI]]
+ ; CHECK-NEXT: PseudoRET implicit $x10
+ %mul = mul nsw i64 %x, 4
+ ret i64 %mul
+}
+
+define i64 @mul_nsw_pow2_signmin(i64 %x) {
+ ; CHECK-LABEL: name: mul_nsw_pow2_signmin
+ ; CHECK: bb.0 (%ir-block.0):
+ ; CHECK-NEXT: liveins: $x10
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-NEXT: [[SLLI:%[0-9]+]]:gpr = SLLI [[COPY]], 63
+ ; CHECK-NEXT: $x10 = COPY [[SLLI]]
+ ; CHECK-NEXT: PseudoRET implicit $x10
+ %mul = mul nsw i64 %x, -9223372036854775808
+ ret i64 %mul
+}
+
+define i64 @mul_no_flags_pow2(i64 %x) {
+ ; CHECK-LABEL: name: mul_no_flags_pow2
+ ; CHECK: bb.0 (%ir-block.0):
+ ; CHECK-NEXT: liveins: $x10
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-NEXT: [[SLLI:%[0-9]+]]:gpr = SLLI [[COPY]], 2
+ ; CHECK-NEXT: $x10 = COPY [[SLLI]]
+ ; CHECK-NEXT: PseudoRET implicit $x10
+ %mul = mul i64 %x, 4
+ ret i64 %mul
+}
+
+define i64 @mul_nuw_pow2(i64 %x) {
+ ; CHECK-LABEL: name: mul_nuw_pow2
+ ; CHECK: bb.0 (%ir-block.0):
+ ; CHECK-NEXT: liveins: $x10
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-NEXT: [[SLLI:%[0-9]+]]:gpr = nuw SLLI [[COPY]], 2
+ ; CHECK-NEXT: $x10 = COPY [[SLLI]]
+ ; CHECK-NEXT: PseudoRET implicit $x10
+ %mul = mul nuw i64 %x, 4
+ ret i64 %mul
+}
>From a561e354ec6abbe1aec424496a69af74929639dd Mon Sep 17 00:00:00 2001
From: Jim Lin <jim at andestech.com>
Date: Mon, 20 Apr 2026 02:11:37 +0000
Subject: [PATCH 2/2] [DAGCombiner] Preserve nsw when folding (mul x, 2^c) to
(shl x, c)
When the multiplier is a power of two strictly less than 2^(BitWidth-1),
the signed multiplication cannot overflow any more than the equivalent
shift, so nsw transfers from the mul to the shl. At 2^(BitWidth-1) the
multiplier is the signed minimum and the equivalence breaks, so keep
the existing behaviour of dropping nsw in that case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply at anthropic.com>
---
llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 6 +++++-
llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index aee5aabdc9e02..0625aa3f324f8 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -4832,7 +4832,11 @@ template <class MatchContextClass> SDValue DAGCombiner::visitMUL(SDNode *N) {
SDValue Trunc = DAG.getZExtOrTrunc(LogBase2, DL, ShiftVT);
SDNodeFlags Flags;
Flags.setNoUnsignedWrap(N->getFlags().hasNoUnsignedWrap());
- // TODO: Preserve setNoSignedWrap if LogBase2 isn't BitWidth - 1.
+ // Preserve nsw when the shift amount is strictly less than BitWidth - 1,
+ // i.e. the multiplier is not the signed minimum value.
+ if (N->getFlags().hasNoSignedWrap() && N1IsConst &&
+ ConstValue1.logBase2() < BitWidth - 1)
+ Flags.setNoSignedWrap(true);
return Matcher.getNode(ISD::SHL, DL, VT, N0, Trunc, Flags);
}
}
diff --git a/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll b/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
index 7dd94654a79f4..a81a3698a41e1 100644
--- a/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
+++ b/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
@@ -11,7 +11,7 @@ define i64 @mul_nsw_pow2(i64 %x) {
; CHECK-NEXT: liveins: $x10
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
- ; CHECK-NEXT: [[SLLI:%[0-9]+]]:gpr = SLLI [[COPY]], 2
+ ; CHECK-NEXT: [[SLLI:%[0-9]+]]:gpr = nsw SLLI [[COPY]], 2
; CHECK-NEXT: $x10 = COPY [[SLLI]]
; CHECK-NEXT: PseudoRET implicit $x10
%mul = mul nsw i64 %x, 4
More information about the llvm-commits
mailing list