[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 07:55:57 PDT 2026


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

>From 56024034d4e8022404fce637561b4f480983cf46 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/3] [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 94c73bbe3bb4a5e47336655c5a29fdc7ef4c0132 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/3] [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

>From 2f878ac1c3706db2645b458c459b76d3307c065e Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 28 Aug 2026 15:55:10 +0100
Subject: [PATCH 3/3] !fixup also preserve NSW

---
 llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp | 2 +-
 llvm/test/Transforms/InstCombine/trunc.ll            | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index a0f9428866458..77492f3852a1c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1241,7 +1241,7 @@ Instruction *InstCombinerImpl::visitTrunc(TruncInst &Trunc) {
         bool NUW = Trunc.hasNoUnsignedWrap() && WideShl->hasNoUnsignedWrap();
         bool NSW = Trunc.hasNoSignedWrap() && WideShl->hasNoSignedWrap();
         Value *NewTrunc = Builder.CreateTrunc(A, DestTy, A->getName() + ".tr",
-                                              /*IsNUW=*/NUW);
+                                              /*IsNUW=*/NUW, /*IsNSW=*/NSW);
         auto *NewShl = BinaryOperator::Create(
             Instruction::Shl, NewTrunc, ConstantExpr::getTrunc(C, DestTy));
         NewShl->setHasNoUnsignedWrap(NUW);
diff --git a/llvm/test/Transforms/InstCombine/trunc.ll b/llvm/test/Transforms/InstCombine/trunc.ll
index 42b1420e11ed2..672c427773541 100644
--- a/llvm/test/Transforms/InstCombine/trunc.ll
+++ b/llvm/test/Transforms/InstCombine/trunc.ll
@@ -630,7 +630,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:    [[VAL_TR:%.*]] = trunc nsw i64 [[VAL:%.*]] to i32
 ; CHECK-NEXT:    [[TRUNC:%.*]] = shl nsw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
@@ -641,7 +641,7 @@ 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 nuw i64 [[VAL:%.*]] to i32
+; CHECK-NEXT:    [[VAL_TR:%.*]] = trunc nuw nsw i64 [[VAL:%.*]] to i32
 ; CHECK-NEXT:    [[TRUNC:%.*]] = shl nuw nsw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
@@ -663,7 +663,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:    [[VAL_TR:%.*]] = trunc nsw i64 [[VAL:%.*]] to i32
 ; CHECK-NEXT:    [[TRUNC:%.*]] = shl nsw i32 [[VAL_TR]], 3
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;



More information about the llvm-commits mailing list