[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