[llvm] [InstCombine] Preserve no-wrap flags in trunc (shl X, C) fold. (PR #219443)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 04:29:34 PDT 2026


https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/219443

Preserve flags when folding trunc (shl X, C) into shl (trunc X), C. If
both ops have NUW/NSW, they can be carried over to the new shl/trunc.

No major changes on llvm-opt-benchmark-nightly besides a number of
preserved flags and a few minor fold changes:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1083

Alive2 Proof: https://alive2.llvm.org/ce/z/EYivzg



>From 4918ad2d8ee882398bd4df8a2acf3f98b821a766 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 27 Aug 2026 21:57:28 +0100
Subject: [PATCH 1/2] [InstCombine] Add shl (trunc) flag preservation tests
 (NFC).

---
 llvm/test/Transforms/InstCombine/trunc.ll | 91 +++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/trunc.ll b/llvm/test/Transforms/InstCombine/trunc.ll
index 507a4678a7a1f..207d3acdca960 100644
--- a/llvm/test/Transforms/InstCombine/trunc.ll
+++ b/llvm/test/Transforms/InstCombine/trunc.ll
@@ -617,6 +617,97 @@ define i32 @trunc_shl_nsw_nuw_31_i32_i64(i64 %val) {
   ret i32 %trunc
 }
 
+define i32 @trunc_nuw_shl_nuw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nuw_shl_nuw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nuw i64 %val, 3
+  %trunc = trunc nuw i64 %shl to i32
+  ret i32 %trunc
+}
+
+define i32 @trunc_nsw_shl_nsw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nsw_shl_nsw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nsw i64 %val, 3
+  %trunc = trunc nsw i64 %shl to i32
+  ret i32 %trunc
+}
+
+define i32 @trunc_nuw_nsw_shl_nuw_nsw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nuw_nsw_shl_nuw_nsw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nuw nsw i64 %val, 3
+  %trunc = trunc nuw nsw i64 %shl to i32
+  ret i32 %trunc
+}
+
+define i32 @trunc_nuw_shl_nuw_nsw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nuw_shl_nuw_nsw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nuw nsw i64 %val, 3
+  %trunc = trunc nuw i64 %shl to i32
+  ret i32 %trunc
+}
+
+define i32 @trunc_nuw_nsw_shl_nsw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nuw_nsw_shl_nsw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nsw i64 %val, 3
+  %trunc = trunc nuw nsw i64 %shl to i32
+  ret i32 %trunc
+}
+
+; Missing flags on shl.
+define i32 @trunc_nuw_shl_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nuw_shl_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl i64 %val, 3
+  %trunc = trunc nuw i64 %shl to i32
+  ret i32 %trunc
+}
+
+; Missing flags on shl.
+define i32 @trunc_shl_nuw_nsw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_shl_nuw_nsw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nuw nsw i64 %val, 3
+  %trunc = trunc i64 %shl to i32
+  ret i32 %trunc
+}
+
+; Mismatched flags on shl/trunc.
+define i32 @trunc_nuw_shl_nsw_3_i32_i64(i64 %val) {
+; CHECK-LABEL: @trunc_nuw_shl_nsw_3_i32_i64(
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %shl = shl nsw i64 %val, 3
+  %trunc = trunc nuw i64 %shl to i32
+  ret i32 %trunc
+}
+
 define i16 @trunc_shl_15_i16_i64(i64 %val) {
 ; CHECK-LABEL: @trunc_shl_15_i16_i64(
 ; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i16

>From 687507188eba1e7f18dc341f1a761db85759e38a Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 26 Aug 2026 10:05:54 +0100
Subject: [PATCH 2/2] [InstCombine] Preserve no-wrap flags in trunc (shl X, C)
 fold.

Preserve flags when folding trunc (shl X, C) into shl (trunc X), C. If
both ops have NUW/NSW, they can be carried over to the new shl/trunc.

Alive2 Proof: https://alive2.llvm.org/ce/z/EYivzg
---
 .../Transforms/InstCombine/InstCombineCasts.cpp  | 15 ++++++++++++---
 llvm/test/Transforms/InstCombine/trunc.ll        | 16 ++++++++--------
 2 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 2db6396b9d661..a0f9428866458 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1235,9 +1235,18 @@ Instruction *InstCombinerImpl::visitTrunc(TruncInst &Trunc) {
       // FoldShiftByConstant and is the extend in reg pattern.
       APInt Threshold = APInt(C->getType()->getScalarSizeInBits(), DestWidth);
       if (match(C, m_SpecificInt_ICMP(ICmpInst::ICMP_ULT, Threshold))) {
-        Value *NewTrunc = Builder.CreateTrunc(A, DestTy, A->getName() + ".tr");
-        return BinaryOperator::Create(Instruction::Shl, NewTrunc,
-                                      ConstantExpr::getTrunc(C, DestTy));
+        // If neither the wide shift nor the truncate wrap, preserve the wrap
+        // flags.
+        auto *WideShl = cast<OverflowingBinaryOperator>(Src);
+        bool NUW = Trunc.hasNoUnsignedWrap() && WideShl->hasNoUnsignedWrap();
+        bool NSW = Trunc.hasNoSignedWrap() && WideShl->hasNoSignedWrap();
+        Value *NewTrunc = Builder.CreateTrunc(A, DestTy, A->getName() + ".tr",
+                                              /*IsNUW=*/NUW);
+        auto *NewShl = BinaryOperator::Create(
+            Instruction::Shl, NewTrunc, ConstantExpr::getTrunc(C, DestTy));
+        NewShl->setHasNoUnsignedWrap(NUW);
+        NewShl->setHasNoSignedWrap(NSW);
+        return NewShl;
       }
     }
   }
diff --git a/llvm/test/Transforms/InstCombine/trunc.ll b/llvm/test/Transforms/InstCombine/trunc.ll
index 207d3acdca960..42b1420e11ed2 100644
--- a/llvm/test/Transforms/InstCombine/trunc.ll
+++ b/llvm/test/Transforms/InstCombine/trunc.ll
@@ -619,8 +619,8 @@ define i32 @trunc_shl_nsw_nuw_31_i32_i64(i64 %val) {
 
 define i32 @trunc_nuw_shl_nuw_3_i32_i64(i64 %val) {
 ; CHECK-LABEL: @trunc_nuw_shl_nuw_3_i32_i64(
-; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc nuw i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl nuw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
   %shl = shl nuw i64 %val, 3
@@ -631,7 +631,7 @@ define i32 @trunc_nuw_shl_nuw_3_i32_i64(i64 %val) {
 define i32 @trunc_nsw_shl_nsw_3_i32_i64(i64 %val) {
 ; CHECK-LABEL: @trunc_nsw_shl_nsw_3_i32_i64(
 ; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl nsw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
   %shl = shl nsw i64 %val, 3
@@ -641,8 +641,8 @@ define i32 @trunc_nsw_shl_nsw_3_i32_i64(i64 %val) {
 
 define i32 @trunc_nuw_nsw_shl_nuw_nsw_3_i32_i64(i64 %val) {
 ; CHECK-LABEL: @trunc_nuw_nsw_shl_nuw_nsw_3_i32_i64(
-; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc nuw i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl nuw nsw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
   %shl = shl nuw nsw i64 %val, 3
@@ -652,8 +652,8 @@ define i32 @trunc_nuw_nsw_shl_nuw_nsw_3_i32_i64(i64 %val) {
 
 define i32 @trunc_nuw_shl_nuw_nsw_3_i32_i64(i64 %val) {
 ; CHECK-LABEL: @trunc_nuw_shl_nuw_nsw_3_i32_i64(
-; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc nuw i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl nuw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
   %shl = shl nuw nsw i64 %val, 3
@@ -664,7 +664,7 @@ define i32 @trunc_nuw_shl_nuw_nsw_3_i32_i64(i64 %val) {
 define i32 @trunc_nuw_nsw_shl_nsw_3_i32_i64(i64 %val) {
 ; CHECK-LABEL: @trunc_nuw_nsw_shl_nsw_3_i32_i64(
 ; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc i64 [[VAL:%.*]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = shl i32 [[VAL_TR]], 3
+; CHECK-NEXT:    [[TRUNC:%.*]] = shl nsw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
   %shl = shl nsw i64 %val, 3



More information about the llvm-commits mailing list