[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
Wed Apr 15 17:49:50 PDT 2026


https://github.com/tclin914 created https://github.com/llvm/llvm-project/pull/192366

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.

>From a55cc25b34e2e9c34b2ea70300db0db3b110f237 Mon Sep 17 00:00:00 2001
From: Jim Lin <jim at andestech.com>
Date: Wed, 15 Apr 2026 05:51:48 +0000
Subject: [PATCH 1/2] [RISCV] Pre-commit test for (mul x, 2^c) -> (shl x, c)
 flag preservation

Baseline test showing that DAGCombiner currently drops the nsw flag when
folding (mul nsw x, 2^c) to (shl x, c). 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 | 40 +++++++++++++++++++++++
 1 file changed, 40 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..1a0a792c32628
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
@@ -0,0 +1,40 @@
+; REQUIRES: asserts
+; RUN: llc -mtriple=riscv64 -debug-only=isel -o /dev/null %s 2>&1 | FileCheck %s
+
+; Verify that DAGCombiner's (mul x, 2^c) -> (shl x, c) fold preserves the nsw
+; flag when the shift amount is strictly less than BitWidth - 1, and does not
+; preserve it when the multiplier is the signed minimum (2^(BitWidth - 1)).
+; RISC-V has no unified IMUL-with-flags instruction, so this inspects the
+; SelectionDAG flags directly via the isel debug dump.
+
+define i32 @mul_nsw_pow2(i32 %x) {
+; CHECK-LABEL: === mul_nsw_pow2
+; CHECK: Optimized lowered selection DAG
+; CHECK: i32 = shl {{[^n].*}}, Constant:i64<2>
+  %mul = mul nsw i32 %x, 4
+  ret i32 %mul
+}
+
+define i32 @mul_nsw_pow2_signmin(i32 %x) {
+; CHECK-LABEL: === mul_nsw_pow2_signmin
+; CHECK: Optimized lowered selection DAG
+; CHECK: i32 = shl {{[^n].*}}, Constant:i64<31>
+  %mul = mul nsw i32 %x, -2147483648
+  ret i32 %mul
+}
+
+define i32 @mul_no_flags_pow2(i32 %x) {
+; CHECK-LABEL: === mul_no_flags_pow2
+; CHECK: Optimized lowered selection DAG
+; CHECK: i32 = shl {{[^n].*}}, Constant:i64<2>
+  %mul = mul i32 %x, 4
+  ret i32 %mul
+}
+
+define i32 @mul_nuw_pow2(i32 %x) {
+; CHECK-LABEL: === mul_nuw_pow2
+; CHECK: Optimized lowered selection DAG
+; CHECK: i32 = shl nuw {{.*}}, Constant:i64<2>
+  %mul = mul nuw i32 %x, 4
+  ret i32 %mul
+}

>From 0e261a96335287aab2f7d0537e4269dc572a7487 Mon Sep 17 00:00:00 2001
From: Jim Lin <jim at andestech.com>
Date: Wed, 15 Apr 2026 05:52:31 +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 1a0a792c32628..8f926443b2a5c 100644
--- a/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
+++ b/llvm/test/CodeGen/RISCV/mul-to-shl-nsw.ll
@@ -10,7 +10,7 @@
 define i32 @mul_nsw_pow2(i32 %x) {
 ; CHECK-LABEL: === mul_nsw_pow2
 ; CHECK: Optimized lowered selection DAG
-; CHECK: i32 = shl {{[^n].*}}, Constant:i64<2>
+; CHECK: i32 = shl nsw {{.*}}, Constant:i64<2>
   %mul = mul nsw i32 %x, 4
   ret i32 %mul
 }



More information about the llvm-commits mailing list