[llvm] [InstCombine] Preserve no-wrap flags in BitwiseOp(trunc(A), trunc(B)) fold. (PR #219567)

Andreas Jonson via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 12:59:28 PDT 2026


https://github.com/andjo403 created https://github.com/llvm/llvm-project/pull/219567

proof: https://alive2.llvm.org/ce/z/BP5VSQ

>From 694f576f55bd6ab921948c2987422bf03ecb93ff Mon Sep 17 00:00:00 2001
From: Andreas Jonson <andjo403 at hotmail.com>
Date: Fri, 28 Aug 2026 21:45:45 +0200
Subject: [PATCH 1/2] [InstCombine] premerge test

---
 .../test/Transforms/InstCombine/and-xor-or.ll | 195 ++++++++++++++++++
 1 file changed, 195 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/and-xor-or.ll b/llvm/test/Transforms/InstCombine/and-xor-or.ll
index 3bb1056c72025..dfbf12e03ce76 100644
--- a/llvm/test/Transforms/InstCombine/and-xor-or.ll
+++ b/llvm/test/Transforms/InstCombine/and-xor-or.ll
@@ -4247,6 +4247,201 @@ define i32 @trunc_trunc_xor_uses(i65 %x, i65 %y) {
   ret i32 %r
 }
 
+define i8 @trunc_nuw_trunc_nuw_xor(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_nuw_xor
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nuw i16 %x to i8
+  %sy = trunc nuw i16 %y to i8
+  %r = xor i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nsw_trunc_nsw_xor(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nsw_xor
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc nsw i16 %y to i8
+  %r = xor i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nsw_trunc_nuw_xor(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nsw_trunc_nuw_xor
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc nuw i16 %y to i8
+  %r = xor i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nuw_trunc_xor(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nuw_trunc_xor
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nuw i16 %x to i8
+  %sy = trunc i16 %y to i8
+  %r = xor i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nsw_trunc_xor(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nsw_trunc_xor
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc i16 %y to i8
+  %r = xor i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nuw_trunc_nuw_or(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_nuw_or
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nuw i16 %x to i8
+  %sy = trunc nuw i16 %y to i8
+  %r = or i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nsw_trunc_nsw_or(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nsw_or
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc nsw i16 %y to i8
+  %r = or i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nsw_trunc_nuw_or(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nsw_trunc_nuw_or
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc nuw i16 %y to i8
+  %r = or i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nuw_trunc_or(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nuw_trunc_or
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nuw i16 %x to i8
+  %sy = trunc i16 %y to i8
+  %r = or i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nsw_trunc_or(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nsw_trunc_or
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc i16 %y to i8
+  %r = or i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nuw_trunc_nuw_and(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_nuw_and
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nuw i16 %x to i8
+  %sy = trunc nuw i16 %y to i8
+  %r = and i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nsw_trunc_nsw_and(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nsw_and
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc nsw i16 %y to i8
+  %r = and i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nsw_trunc_nuw_and(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nuw_and
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc nuw i16 %y to i8
+  %r = and i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @trunc_nuw_trunc_and(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_and
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nuw i16 %x to i8
+  %sy = trunc i16 %y to i8
+  %r = and i8 %sx, %sy
+  ret i8 %r
+}
+
+define i8 @neg_trunc_nsw_trunc_and(i16 %x, i16 %y) {
+; CHECK-LABEL: define {{[^@]+}}@neg_trunc_nsw_trunc_and
+; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
+; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = trunc nsw i16 %x to i8
+  %sy = trunc i16 %y to i8
+  %r = and i8 %sx, %sy
+  ret i8 %r
+}
+
 define i16 @and_zext_zext(i8 %x, i4 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@and_zext_zext
 ; CHECK-SAME: (i8 [[X:%.*]], i4 [[Y:%.*]]) {

>From 720661da20a57686831716c445e9b70dd4ffdae1 Mon Sep 17 00:00:00 2001
From: Andreas Jonson <andjo403 at hotmail.com>
Date: Fri, 28 Aug 2026 21:47:18 +0200
Subject: [PATCH 2/2] [InstCombine] Preserve no-wrap flags in
 BitwiseOp(trunc(A), trunc(B)) fold.

---
 .../InstCombine/InstCombineAndOrXor.cpp          | 13 ++++++++++++-
 llvm/test/Transforms/InstCombine/and-xor-or.ll   | 16 ++++++++--------
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 9e9ebfe5c5e90..054161d4481ff 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -1911,7 +1911,18 @@ Instruction *InstCombinerImpl::foldCastedBitwiseLogic(BinaryOperator &I) {
   if (shouldOptimizeCast(Cast0) && shouldOptimizeCast(Cast1)) {
     Value *NewOp = Builder.CreateBinOp(LogicOpc, Cast0Src, Cast1Src,
                                        I.getName());
-    return CastInst::Create(CastOpcode, NewOp, DestTy);
+    auto *NewCast = CastInst::Create(CastOpcode, NewOp, DestTy);
+    if (auto *NewTrunc = dyn_cast<TruncInst>(NewCast)) {
+      auto *Trunc0 = cast<TruncInst>(Cast0);
+      auto *Trunc1 = cast<TruncInst>(Cast1);
+      NewTrunc->setHasNoUnsignedWrap(
+          LogicOpc == Instruction::And
+              ? Trunc0->hasNoUnsignedWrap() || Trunc1->hasNoUnsignedWrap()
+              : Trunc0->hasNoUnsignedWrap() && Trunc1->hasNoUnsignedWrap());
+      NewTrunc->setHasNoSignedWrap(Trunc0->hasNoSignedWrap() &&
+                                   Trunc1->hasNoSignedWrap());
+    }
+    return NewCast;
   }
 
   return nullptr;
diff --git a/llvm/test/Transforms/InstCombine/and-xor-or.ll b/llvm/test/Transforms/InstCombine/and-xor-or.ll
index dfbf12e03ce76..1750aea8e1eaa 100644
--- a/llvm/test/Transforms/InstCombine/and-xor-or.ll
+++ b/llvm/test/Transforms/InstCombine/and-xor-or.ll
@@ -4251,7 +4251,7 @@ define i8 @trunc_nuw_trunc_nuw_xor(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_nuw_xor
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nuw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nuw i16 %x to i8
@@ -4264,7 +4264,7 @@ define i8 @trunc_nsw_trunc_nsw_xor(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nsw_xor
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = xor i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nsw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nsw i16 %x to i8
@@ -4316,7 +4316,7 @@ define i8 @trunc_nuw_trunc_nuw_or(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_nuw_or
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nuw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nuw i16 %x to i8
@@ -4329,7 +4329,7 @@ define i8 @trunc_nsw_trunc_nsw_or(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nsw_or
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = or i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nsw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nsw i16 %x to i8
@@ -4381,7 +4381,7 @@ define i8 @trunc_nuw_trunc_nuw_and(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_nuw_and
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nuw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nuw i16 %x to i8
@@ -4394,7 +4394,7 @@ define i8 @trunc_nsw_trunc_nsw_and(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nsw_and
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nsw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nsw i16 %x to i8
@@ -4407,7 +4407,7 @@ define i8 @trunc_nsw_trunc_nuw_and(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nsw_trunc_nuw_and
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nuw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nsw i16 %x to i8
@@ -4420,7 +4420,7 @@ define i8 @trunc_nuw_trunc_and(i16 %x, i16 %y) {
 ; CHECK-LABEL: define {{[^@]+}}@trunc_nuw_trunc_and
 ; CHECK-SAME: (i16 [[X:%.*]], i16 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[R1:%.*]] = and i16 [[X]], [[Y]]
-; CHECK-NEXT:    [[R:%.*]] = trunc i16 [[R1]] to i8
+; CHECK-NEXT:    [[R:%.*]] = trunc nuw i16 [[R1]] to i8
 ; CHECK-NEXT:    ret i8 [[R]]
 ;
   %sx = trunc nuw i16 %x to i8



More information about the llvm-commits mailing list