[llvm] [InstCombine] Evaluate zext nneg as sext where possible (PR #212230)
John Brawn via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 06:47:33 PDT 2026
https://github.com/john-brawn-arm updated https://github.com/llvm/llvm-project/pull/212230
>From 873ac901b63e0fcfde2cbf551f8539dda5d2934d Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Mon, 20 Jul 2026 11:14:27 +0100
Subject: [PATCH 1/7] [InstCombine] Evaluate zext nneg as sext where possible
We can convert zext nneg to sext, and doing so when evaluating an
expression in a wider type means any negative constant operands are
sign-extended instead of zero-extended, meaning the high bits of the
result will be correct without needing to mask them. We do however
need to be careful of signed wrapping, as that can result in a
positive result in the narrower type which becomes negative when
sign-extended.
---
.../InstCombine/InstCombineCasts.cpp | 72 ++++--
llvm/test/Transforms/InstCombine/zext-nneg.ll | 233 ++++++++++++++++++
2 files changed, 285 insertions(+), 20 deletions(-)
create mode 100644 llvm/test/Transforms/InstCombine/zext-nneg.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 2db6396b9d661..fb40c656dc4ae 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -318,8 +318,10 @@ class TypeEvaluationHelper {
/// Return true if we can take the specified value and return it as type Ty
/// without inserting any new casts and without changing the value of the
- /// common low bits.
- [[nodiscard]] static bool canEvaluateSExtd(Value *V, Type *Ty);
+ /// common low bits. If NoSignedWrap is true then the calculation of V must
+ /// not involve arithmetic that could performed signed wrapping.
+ [[nodiscard]] static bool canEvaluateSExtd(Value *V, Type *Ty,
+ bool NoSignedWrap = false);
private:
/// Constants and extensions/truncates from the destination type are always
@@ -458,8 +460,10 @@ class TypeEvaluationHelper {
unsigned &BitsToClear,
InstCombinerImpl &IC,
Instruction *CxtI);
- [[nodiscard]] bool canEvaluateSExtdImpl(Value *V, Type *Ty);
- [[nodiscard]] bool canEvaluateSExtdPred(Value *V, Type *Ty);
+ [[nodiscard]] bool canEvaluateSExtdImpl(Value *V, Type *Ty,
+ bool NoSignedWrap);
+ [[nodiscard]] bool canEvaluateSExtdPred(Value *V, Type *Ty,
+ bool NoSignedWrap);
/// A bookkeeping map to memorize an already made decision for a traversed
/// value.
@@ -1615,7 +1619,18 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
dbgs() << "ICE: EvaluateInDifferentType converting expression type"
" to avoid zero extend: "
<< Zext << '\n');
- Value *Res = EvaluateInDifferentType(Src, DestTy, false);
+
+ // zext nneg means Src is non-negative and we can treat this as an sext, and
+ // if no signed wrapping occurs we know that Src evaluated as a signed
+ // DestTy will also be non-negative. Evaluating as a signed type means that
+ // any constant operands will be sign-extended instead of zero-extended,
+ // meaning they remain in the range of a signed SrcTy so we won't need to
+ // clear any high bits.
+ bool EvaluateAsSigned =
+ Zext.hasNonNeg() &&
+ TypeEvaluationHelper::canEvaluateSExtd(Src, DestTy, true);
+
+ Value *Res = EvaluateInDifferentType(Src, DestTy, EvaluateAsSigned);
assert(Res->getType() == DestTy);
// Preserve debug values referring to Src if the zext is its last use.
@@ -1627,10 +1642,14 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
uint32_t DestBitSize = DestTy->getScalarSizeInBits();
// If the high bits are already filled with zeros, just replace this
- // cast with the result.
- if (MaskedValueIsZero(
- Res, APInt::getHighBitsSet(DestBitSize, DestBitSize - SrcBitsKept),
- &Zext))
+ // cast with the result. If we've evaluated as a signed expressions then
+ // instead check that the high bits are the sign bit, which we know is zero.
+ if (EvaluateAsSigned
+ ? (ComputeNumSignBits(Res, &Zext) > DestBitSize - SrcBitsKept)
+ : MaskedValueIsZero(
+ Res,
+ APInt::getHighBitsSet(DestBitSize, DestBitSize - SrcBitsKept),
+ &Zext))
return replaceInstUsesWith(Zext, Res);
// We need to emit an AND to clear the high bits.
@@ -1834,22 +1853,35 @@ Instruction *InstCombinerImpl::transformSExtICmp(ICmpInst *Cmp,
///
/// This function works on both vectors and scalars.
///
-bool TypeEvaluationHelper::canEvaluateSExtd(Value *V, Type *Ty) {
+bool TypeEvaluationHelper::canEvaluateSExtd(Value *V, Type *Ty,
+ bool NoSignedWrap) {
TypeEvaluationHelper TYH;
- return TYH.canEvaluateSExtdImpl(V, Ty) && TYH.allPendingVisited();
+ return TYH.canEvaluateSExtdImpl(V, Ty, NoSignedWrap) &&
+ TYH.allPendingVisited();
}
-bool TypeEvaluationHelper::canEvaluateSExtdImpl(Value *V, Type *Ty) {
- return canEvaluate(V, Ty, [this](Value *V, Type *Ty) {
- return canEvaluateSExtdPred(V, Ty);
+bool TypeEvaluationHelper::canEvaluateSExtdImpl(Value *V, Type *Ty,
+ bool NoSignedWrap) {
+ return canEvaluate(V, Ty, [this, NoSignedWrap](Value *V, Type *Ty) {
+ return canEvaluateSExtdPred(V, Ty, NoSignedWrap);
});
}
-bool TypeEvaluationHelper::canEvaluateSExtdPred(Value *V, Type *Ty) {
+bool TypeEvaluationHelper::canEvaluateSExtdPred(Value *V, Type *Ty,
+ bool NoSignedWrap) {
assert(V->getType()->getScalarSizeInBits() < Ty->getScalarSizeInBits() &&
"Can't sign extend type to a smaller type");
auto *I = cast<Instruction>(V);
+
+ // If signed wrapping is forbidden then check that all instructions that could
+ // potentially wrap have the nsw flag.
+ if (NoSignedWrap &&
+ (isa<OverflowingBinaryOperator>(I) ||
+ I->getOpcode() == Instruction::Trunc) &&
+ !I->hasNoSignedWrap())
+ return false;
+
switch (I->getOpcode()) {
case Instruction::SExt: // sext(sext(x)) -> sext(x)
case Instruction::ZExt: // sext(zext(x)) -> zext(x)
@@ -1862,15 +1894,15 @@ bool TypeEvaluationHelper::canEvaluateSExtdPred(Value *V, Type *Ty) {
case Instruction::Sub:
case Instruction::Mul:
// These operators can all arbitrarily be extended if their inputs can.
- return canEvaluateSExtdImpl(I->getOperand(0), Ty) &&
- canEvaluateSExtdImpl(I->getOperand(1), Ty);
+ return canEvaluateSExtdImpl(I->getOperand(0), Ty, NoSignedWrap) &&
+ canEvaluateSExtdImpl(I->getOperand(1), Ty, NoSignedWrap);
// case Instruction::Shl: TODO
// case Instruction::LShr: TODO
case Instruction::Select:
- return canEvaluateSExtdImpl(I->getOperand(1), Ty) &&
- canEvaluateSExtdImpl(I->getOperand(2), Ty);
+ return canEvaluateSExtdImpl(I->getOperand(1), Ty, NoSignedWrap) &&
+ canEvaluateSExtdImpl(I->getOperand(2), Ty, NoSignedWrap);
case Instruction::PHI: {
// We can change a phi if we can change all operands. Note that we never
@@ -1878,7 +1910,7 @@ bool TypeEvaluationHelper::canEvaluateSExtdPred(Value *V, Type *Ty) {
// chain loops.
PHINode *PN = cast<PHINode>(I);
for (Value *IncValue : PN->incoming_values())
- if (!canEvaluateSExtdImpl(IncValue, Ty))
+ if (!canEvaluateSExtdImpl(IncValue, Ty, NoSignedWrap))
return false;
return true;
}
diff --git a/llvm/test/Transforms/InstCombine/zext-nneg.ll b/llvm/test/Transforms/InstCombine/zext-nneg.ll
new file mode 100644
index 0000000000000..bb75d37a7824f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/zext-nneg.ll
@@ -0,0 +1,233 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+target datalayout = "n64"
+
+; When evaluated as i64 we need to restrict the range of the output to that of
+; i32 using an and, as negative values of the i32 add become positive values
+; when zero-extended.
+define i64 @zext_of_sext_nsw(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_of_sext_nsw(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], 4294967295
+; CHECK-NEXT: [[ZEXT:%.*]] = and i64 [[ADD]], 4294967295
+; CHECK-NEXT: ret i64 [[ZEXT]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %add = add nsw i32 %sext, -1
+ %zext = zext i32 %add to i64
+ ret i64 %zext
+}
+
+define i64 @zext_of_zext_nsw(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_of_zext_nsw(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nuw nsw i64 [[SEXT]], 4294967295
+; CHECK-NEXT: [[ZEXT:%.*]] = and i64 [[ADD]], 4294967295
+; CHECK-NEXT: ret i64 [[ZEXT]]
+;
+entry:
+ %sext = zext i16 %arg to i32
+ %add = add nsw i32 %sext, -1
+ %zext = zext i32 %add to i64
+ ret i64 %zext
+}
+
+; When the zext is nneg we know the add result can never be negative, and nsw on
+; the add means evaluating the add at a wider width can't result in the sign of
+; the result changing, so we don't need to restrict to the range of an i32.
+define i64 @zext_nneg_of_sext_nsw(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_sext_nsw(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], -1
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %add = add nsw i32 %sext, -1
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+define i64 @zext_nneg_of_zext_nsw(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_zext_nsw(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], -1
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = zext i16 %arg to i32
+ %add = add nsw i32 %sext, -1
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+; We can still avoid an and even without nsw on the add as -1 is small enough
+; that we know there are no sign or zero extended values that could result in
+; the output wrapping.
+define i64 @zext_nneg_of_sext(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_sext(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], -1
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %add = add i32 %sext, -1
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+define i64 @zext_nneg_of_zext(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_zext(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], -1
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = zext i16 %arg to i32
+ %add = add i32 %sext, -1
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+; -2147450881 is (INT32_MIN - INT16_MIN - 1), so if %arg is INT16_MIN the result
+; is INT32_MIN-1, so when done in i32 precision will wrap around to INT32_MAX.
+; We therefore need to use an and to restrict the result to the range of an i32.
+; Theoretically we could optimize this to just return INT32_MIN-1, as all other
+; %add values are negative and thus result in poison when used as an argument to
+; zext nneg.
+define i64 @zext_nneg_of_sext_large_negative(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_sext_large_negative(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], 2147516415
+; CHECK-NEXT: [[ZEXT:%.*]] = and i64 [[ADD]], 4294967295
+; CHECK-NEXT: ret i64 [[ZEXT]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %add = add i32 %sext, -2147450881
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+; We don't need to worry about wrapping, and so never need to restrict the range
+; of the output, for zext of zext, as the result of the zext is, when
+; interpreted as a signed integer, always positive.
+; Theoretically here we could optimize this to return poison, as the result of
+; the %add is always negative ans thus gives poison when passed to zext nneg.
+define i64 @zext_nneg_of_zext_large_negative(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_zext_large_negative(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nuw nsw i64 [[SEXT]], 2147516415
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = zext i16 %arg to i32
+ %add = add i32 %sext, -2147450881
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+; 4294934529 is (UINT32_MAX - INT16_MAX + 1), so if %arg is INT16_MAX the result
+; is UINT32_MAX+1, causing the result to wrap around to zero. However 4294934529
+; interpreted as a signed i32 is -32767, so adding that at i64 width will give
+; the same result without needing to care about unsigned wrapping.
+define i64 @zext_nneg_of_sext_large_positive(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_sext_large_positive(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], -32767
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %add = add i32 %sext, 4294934529
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+define i64 @zext_nneg_of_zext_large_positive(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_nneg_of_zext_large_positive(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[SEXT]], -32767
+; CHECK-NEXT: ret i64 [[ADD]]
+;
+entry:
+ %sext = zext i16 %arg to i32
+ %add = add i32 %sext, 4294934529
+ %zext = zext nneg i32 %add to i64
+ ret i64 %zext
+}
+
+; 2271560481 is 0x87654321. Sign-extending it would be a problem if the zext
+; weren't nneg, but because it is we know that the sign-extended bits of arg
+; must have the same value and cancel out to zero.
+define i64 @zext_of_sext_xor(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_of_sext_xor(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[XOR:%.*]] = xor i64 [[SEXT]], -2023406815
+; CHECK-NEXT: ret i64 [[XOR]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %xor = xor i32 %sext, 2271560481
+ %zext = zext nneg i32 %xor to i64
+ ret i64 %zext
+}
+
+; Theoretically this can be replaced with ret poison, as the or means that it's
+; guaranteed that the sign bit is set which isn't permitted by the zext nneg.
+define i64 @zext_of_sext_or(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_of_sext_or(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[OR:%.*]] = or i64 [[SEXT]], -2023406815
+; CHECK-NEXT: ret i64 [[OR]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %or = or i32 %sext, 2271560481
+ %zext = zext nneg i32 %or to i64
+ ret i64 %zext
+}
+
+; The and preserves the sign bit of the sext'd arg, so we know it must be zero
+; from the nneg, so it's safe to sign-extend the and immediate.
+define i64 @zext_of_sext_and(i16 %arg) {
+; CHECK-LABEL: define i64 @zext_of_sext_and(
+; CHECK-SAME: i16 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[ARG]] to i64
+; CHECK-NEXT: [[AND:%.*]] = and i64 [[SEXT]], -2023406815
+; CHECK-NEXT: ret i64 [[AND]]
+;
+entry:
+ %sext = sext i16 %arg to i32
+ %and = and i32 %sext, 2271560481
+ %zext = zext nneg i32 %and to i64
+ ret i64 %zext
+}
>From 3ced1f69cb0502239950e4549f9d629f66932212 Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Thu, 6 Aug 2026 15:06:43 +0100
Subject: [PATCH 2/7] Don't call canEvaluateZExtd if CanEvaluateSigned is true
---
.../InstCombine/InstCombineCasts.cpp | 26 ++++++------
.../Transforms/InstCombine/cast-mul-select.ll | 40 ++++++++-----------
2 files changed, 30 insertions(+), 36 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index fb40c656dc4ae..2d134c8458882 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1606,11 +1606,21 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
if (SrcTy->isIntOrIntVectorTy(1) && Zext.hasNonNeg())
return replaceInstUsesWith(Zext, Constant::getNullValue(Zext.getType()));
+ // zext nneg means Src is non-negative and we can treat this as an sext, and
+ // if no signed wrapping occurs we know that Src evaluated as a signed
+ // DestTy will also be non-negative. Evaluating as a signed type means that
+ // any constant operands will be sign-extended instead of zero-extended,
+ // meaning they remain in the range of a signed SrcTy so we won't need to
+ // clear any high bits.
+ bool EvaluateAsSigned =
+ Zext.hasNonNeg() &&
+ TypeEvaluationHelper::canEvaluateSExtd(Src, DestTy, true);
+
// Try to extend the entire expression tree to the wide destination type.
- unsigned BitsToClear;
+ unsigned BitsToClear = 0;
if (shouldChangeType(SrcTy, DestTy) &&
- TypeEvaluationHelper::canEvaluateZExtd(Src, DestTy, BitsToClear, *this,
- &Zext)) {
+ (EvaluateAsSigned || TypeEvaluationHelper::canEvaluateZExtd(
+ Src, DestTy, BitsToClear, *this, &Zext))) {
assert(BitsToClear <= SrcTy->getScalarSizeInBits() &&
"Can't clear more bits than in SrcTy");
@@ -1620,16 +1630,6 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
" to avoid zero extend: "
<< Zext << '\n');
- // zext nneg means Src is non-negative and we can treat this as an sext, and
- // if no signed wrapping occurs we know that Src evaluated as a signed
- // DestTy will also be non-negative. Evaluating as a signed type means that
- // any constant operands will be sign-extended instead of zero-extended,
- // meaning they remain in the range of a signed SrcTy so we won't need to
- // clear any high bits.
- bool EvaluateAsSigned =
- Zext.hasNonNeg() &&
- TypeEvaluationHelper::canEvaluateSExtd(Src, DestTy, true);
-
Value *Res = EvaluateInDifferentType(Src, DestTy, EvaluateAsSigned);
assert(Res->getType() == DestTy);
diff --git a/llvm/test/Transforms/InstCombine/cast-mul-select.ll b/llvm/test/Transforms/InstCombine/cast-mul-select.ll
index 29c5bb57a4667..1a0728778cc34 100644
--- a/llvm/test/Transforms/InstCombine/cast-mul-select.ll
+++ b/llvm/test/Transforms/InstCombine/cast-mul-select.ll
@@ -113,22 +113,18 @@ define i32 @eval_trunc_multi_use_in_one_inst(i32 %x) {
define i32 @eval_zext_multi_use_in_one_inst(i32 %x) {
; CHECK-LABEL: @eval_zext_multi_use_in_one_inst(
-; CHECK-NEXT: [[T:%.*]] = trunc i32 [[X:%.*]] to i16
-; CHECK-NEXT: [[A:%.*]] = and i16 [[T]], 5
-; CHECK-NEXT: [[M:%.*]] = mul nuw nsw i16 [[A]], [[A]]
-; CHECK-NEXT: [[R:%.*]] = zext nneg i16 [[M]] to i32
+; CHECK-NEXT: [[A:%.*]] = and i32 [[X:%.*]], 5
+; CHECK-NEXT: [[R:%.*]] = mul nuw nsw i32 [[A]], [[A]]
; CHECK-NEXT: ret i32 [[R]]
;
; DBGINFO-LABEL: @eval_zext_multi_use_in_one_inst(
-; DBGINFO-NEXT: [[T:%.*]] = trunc i32 [[X:%.*]] to i16, !dbg [[DBG69:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i16 [[T]], [[META64:![0-9]+]], !DIExpression(), [[DBG69]])
-; DBGINFO-NEXT: [[A:%.*]] = and i16 [[T]], 5, !dbg [[DBG70:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i16 [[A]], [[META66:![0-9]+]], !DIExpression(), [[DBG70]])
-; DBGINFO-NEXT: [[M:%.*]] = mul nuw nsw i16 [[A]], [[A]], !dbg [[DBG71:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i16 [[M]], [[META67:![0-9]+]], !DIExpression(), [[DBG71]])
-; DBGINFO-NEXT: [[R:%.*]] = zext nneg i16 [[M]] to i32, !dbg [[DBG72:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i32 [[R]], [[META68:![0-9]+]], !DIExpression(), [[DBG72]])
-; DBGINFO-NEXT: ret i32 [[R]], !dbg [[DBG73:![0-9]+]]
+; DBGINFO-NEXT: #dbg_value(i32 [[X:%.*]], [[META64:![0-9]+]], !DIExpression(DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_LLVM_convert, 16, DW_ATE_unsigned, DW_OP_stack_value), [[META69:![0-9]+]])
+; DBGINFO-NEXT: [[A:%.*]] = and i32 [[X]], 5, !dbg [[DBG70:![0-9]+]]
+; DBGINFO-NEXT: #dbg_value(i32 [[X]], [[META66:![0-9]+]], !DIExpression(DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_LLVM_convert, 16, DW_ATE_unsigned, DW_OP_constu, 5, DW_OP_and, DW_OP_stack_value), [[DBG70]])
+; DBGINFO-NEXT: [[M:%.*]] = mul nuw nsw i32 [[A]], [[A]], !dbg [[DBG71:![0-9]+]]
+; DBGINFO-NEXT: #dbg_value(i32 [[M]], [[META67:![0-9]+]], !DIExpression(), [[DBG71]])
+; DBGINFO-NEXT: #dbg_value(i32 [[M]], [[META68:![0-9]+]], !DIExpression(), [[META72:![0-9]+]])
+; DBGINFO-NEXT: ret i32 [[M]], !dbg [[DBG73:![0-9]+]]
;
%t = trunc i32 %x to i16
%a = and i16 %t, 5
@@ -174,7 +170,7 @@ define void @PR36225(i32 %a, i32 %b, i1 %c1, i3 %v1, i3 %v2) {
; CHECK-NEXT: br i1 [[C1:%.*]], label [[FOR_BODY3_US:%.*]], label [[FOR_BODY3:%.*]]
; CHECK: for.body3.us:
; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i32 [[B:%.*]], 0
-; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[TOBOOL]], i8 0, i8 4
+; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[TOBOOL]], i32 0, i32 4
; CHECK-NEXT: switch i3 [[V1:%.*]], label [[EXIT:%.*]] [
; CHECK-NEXT: i3 0, label [[FOR_END:%.*]]
; CHECK-NEXT: i3 -1, label [[FOR_END]]
@@ -185,8 +181,7 @@ define void @PR36225(i32 %a, i32 %b, i1 %c1, i3 %v1, i3 %v2) {
; CHECK-NEXT: i3 -1, label [[FOR_END]]
; CHECK-NEXT: ]
; CHECK: for.end:
-; CHECK-NEXT: [[H:%.*]] = phi i8 [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ 0, [[FOR_BODY3]] ], [ 0, [[FOR_BODY3]] ]
-; CHECK-NEXT: [[CONV:%.*]] = zext nneg i8 [[H]] to i32
+; CHECK-NEXT: [[CONV:%.*]] = phi i32 [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ 0, [[FOR_BODY3]] ], [ 0, [[FOR_BODY3]] ]
; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[A:%.*]], [[CONV]]
; CHECK-NEXT: br i1 [[CMP]], label [[EXIT]], label [[EXIT2:%.*]]
; CHECK: exit2:
@@ -203,8 +198,8 @@ define void @PR36225(i32 %a, i32 %b, i1 %c1, i3 %v1, i3 %v2) {
; DBGINFO: for.body3.us:
; DBGINFO-NEXT: [[TOBOOL:%.*]] = icmp eq i32 [[B]], 0, !dbg [[META95]]
; DBGINFO-NEXT: #dbg_value(i1 [[TOBOOL]], [[META89]], !DIExpression(), [[META95]])
-; DBGINFO-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[TOBOOL]], i8 0, i8 4, !dbg [[DBG97:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i8 [[SPEC_SELECT]], [[META90:![0-9]+]], !DIExpression(), [[DBG97]])
+; DBGINFO-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[TOBOOL]], i32 0, i32 4, !dbg [[DBG97:![0-9]+]]
+; DBGINFO-NEXT: #dbg_value(i8 poison, [[META90:![0-9]+]], !DIExpression(), [[DBG97]])
; DBGINFO-NEXT: switch i3 [[V1:%.*]], label [[EXIT:%.*]] [
; DBGINFO-NEXT: i3 0, label [[FOR_END:%.*]]
; DBGINFO-NEXT: i3 -1, label [[FOR_END]]
@@ -215,11 +210,10 @@ define void @PR36225(i32 %a, i32 %b, i1 %c1, i3 %v1, i3 %v2) {
; DBGINFO-NEXT: i3 -1, label [[FOR_END]]
; DBGINFO-NEXT: ], !dbg [[DBG99:![0-9]+]]
; DBGINFO: for.end:
-; DBGINFO-NEXT: [[H:%.*]] = phi i8 [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ 0, [[FOR_BODY3]] ], [ 0, [[FOR_BODY3]] ], !dbg [[DBG100:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i8 [[H]], [[META91:![0-9]+]], !DIExpression(), [[DBG100]])
-; DBGINFO-NEXT: [[CONV:%.*]] = zext nneg i8 [[H]] to i32, !dbg [[DBG101:![0-9]+]]
-; DBGINFO-NEXT: #dbg_value(i32 [[CONV]], [[META92:![0-9]+]], !DIExpression(), [[DBG101]])
-; DBGINFO-NEXT: [[CMP:%.*]] = icmp sgt i32 [[A:%.*]], [[CONV]], !dbg [[DBG102:![0-9]+]]
+; DBGINFO-NEXT: [[H:%.*]] = phi i32 [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ [[SPEC_SELECT]], [[FOR_BODY3_US]] ], [ 0, [[FOR_BODY3]] ], [ 0, [[FOR_BODY3]] ], !dbg [[DBG100:![0-9]+]]
+; DBGINFO-NEXT: #dbg_value(i32 [[H]], [[META91:![0-9]+]], !DIExpression(), [[DBG100]])
+; DBGINFO-NEXT: #dbg_value(i32 [[H]], [[META92:![0-9]+]], !DIExpression(), [[META101:![0-9]+]])
+; DBGINFO-NEXT: [[CMP:%.*]] = icmp sgt i32 [[A:%.*]], [[H]], !dbg [[DBG102:![0-9]+]]
; DBGINFO-NEXT: #dbg_value(i1 [[CMP]], [[META93:![0-9]+]], !DIExpression(), [[DBG102]])
; DBGINFO-NEXT: br i1 [[CMP]], label [[EXIT]], label [[EXIT2:%.*]], !dbg [[DBG103:![0-9]+]]
; DBGINFO: exit2:
>From 2a4ddf6967486ef6ec00a20540adf72457296178 Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Thu, 6 Aug 2026 15:48:42 +0100
Subject: [PATCH 3/7] Add NoSignedWrap comment
---
llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 2d134c8458882..f77c0ba15ebfe 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1613,8 +1613,8 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
// meaning they remain in the range of a signed SrcTy so we won't need to
// clear any high bits.
bool EvaluateAsSigned =
- Zext.hasNonNeg() &&
- TypeEvaluationHelper::canEvaluateSExtd(Src, DestTy, true);
+ Zext.hasNonNeg() && TypeEvaluationHelper::canEvaluateSExtd(
+ Src, DestTy, /*NoSignedWrap=*/true);
// Try to extend the entire expression tree to the wide destination type.
unsigned BitsToClear = 0;
>From 49424e869037d8267450de708197d39d05a2eb57 Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Mon, 10 Aug 2026 17:45:42 +0100
Subject: [PATCH 4/7] Don't need to check ComputeNumSignBits
---
.../Transforms/InstCombine/InstCombineCasts.cpp | 15 +++++++--------
llvm/test/Transforms/InstCombine/zext-nneg.ll | 2 +-
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index f77c0ba15ebfe..52e1d54589963 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1642,14 +1642,13 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
uint32_t DestBitSize = DestTy->getScalarSizeInBits();
// If the high bits are already filled with zeros, just replace this
- // cast with the result. If we've evaluated as a signed expressions then
- // instead check that the high bits are the sign bit, which we know is zero.
- if (EvaluateAsSigned
- ? (ComputeNumSignBits(Res, &Zext) > DestBitSize - SrcBitsKept)
- : MaskedValueIsZero(
- Res,
- APInt::getHighBitsSet(DestBitSize, DestBitSize - SrcBitsKept),
- &Zext))
+ // cast with the result. If we've evaluated as a signed expressions then we
+ // know the high bits are all zero, as canEvaluateSExtd doesn't permit
+ // anything that would set the high bits.
+ if (EvaluateAsSigned ||
+ MaskedValueIsZero(
+ Res, APInt::getHighBitsSet(DestBitSize, DestBitSize - SrcBitsKept),
+ &Zext))
return replaceInstUsesWith(Zext, Res);
// We need to emit an AND to clear the high bits.
diff --git a/llvm/test/Transforms/InstCombine/zext-nneg.ll b/llvm/test/Transforms/InstCombine/zext-nneg.ll
index bb75d37a7824f..deda3922467a8 100644
--- a/llvm/test/Transforms/InstCombine/zext-nneg.ll
+++ b/llvm/test/Transforms/InstCombine/zext-nneg.ll
@@ -136,7 +136,7 @@ define i64 @zext_nneg_of_zext_large_negative(i16 %arg) {
; CHECK-SAME: i16 [[ARG:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
-; CHECK-NEXT: [[ADD:%.*]] = add nuw nsw i64 [[SEXT]], 2147516415
+; CHECK-NEXT: [[ADD:%.*]] = add nuw nsw i64 [[SEXT]], -2147450881
; CHECK-NEXT: ret i64 [[ADD]]
;
entry:
>From a7e0a2b7f1a80d951e0d1175c34ae4577a868f2c Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Tue, 11 Aug 2026 11:51:49 +0100
Subject: [PATCH 5/7] Add zext_of_trunc test
---
llvm/test/Transforms/InstCombine/zext-nneg.ll | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/zext-nneg.ll b/llvm/test/Transforms/InstCombine/zext-nneg.ll
index deda3922467a8..4dc093d6a06dd 100644
--- a/llvm/test/Transforms/InstCombine/zext-nneg.ll
+++ b/llvm/test/Transforms/InstCombine/zext-nneg.ll
@@ -231,3 +231,18 @@ entry:
%zext = zext nneg i32 %and to i64
ret i64 %zext
}
+
+; FIXME: The and isn't needed here, as the trunc nsw means we know the high bits
+; are all equal to the sign bit, and zext nneg means we know the sign bit is
+; zero.
+define i32 @zext_of_trunc(i64 %arg) {
+; CHECK-LABEL: define i32 @zext_of_trunc(
+; CHECK-SAME: i64 [[ARG:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[ARG]] to i32
+; CHECK-NEXT: [[ZEXT:%.*]] = and i32 [[TMP1]], 65535
+; CHECK-NEXT: ret i32 [[ZEXT]]
+;
+ %trunc = trunc nsw i64 %arg to i16
+ %zext = zext nneg i16 %trunc to i32
+ ret i32 %zext
+}
>From e6e9ddbe0561ae1352b5b73fd0a99795ca53005f Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Tue, 11 Aug 2026 19:25:42 +0100
Subject: [PATCH 6/7] Revert "Don't need to check ComputeNumSignBits"
This reverts commit 49424e869037d8267450de708197d39d05a2eb57.
---
.../Transforms/InstCombine/InstCombineCasts.cpp | 15 ++++++++-------
llvm/test/Transforms/InstCombine/zext-nneg.ll | 2 +-
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 52e1d54589963..f77c0ba15ebfe 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1642,13 +1642,14 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
uint32_t DestBitSize = DestTy->getScalarSizeInBits();
// If the high bits are already filled with zeros, just replace this
- // cast with the result. If we've evaluated as a signed expressions then we
- // know the high bits are all zero, as canEvaluateSExtd doesn't permit
- // anything that would set the high bits.
- if (EvaluateAsSigned ||
- MaskedValueIsZero(
- Res, APInt::getHighBitsSet(DestBitSize, DestBitSize - SrcBitsKept),
- &Zext))
+ // cast with the result. If we've evaluated as a signed expressions then
+ // instead check that the high bits are the sign bit, which we know is zero.
+ if (EvaluateAsSigned
+ ? (ComputeNumSignBits(Res, &Zext) > DestBitSize - SrcBitsKept)
+ : MaskedValueIsZero(
+ Res,
+ APInt::getHighBitsSet(DestBitSize, DestBitSize - SrcBitsKept),
+ &Zext))
return replaceInstUsesWith(Zext, Res);
// We need to emit an AND to clear the high bits.
diff --git a/llvm/test/Transforms/InstCombine/zext-nneg.ll b/llvm/test/Transforms/InstCombine/zext-nneg.ll
index 4dc093d6a06dd..fd8490fdb3b32 100644
--- a/llvm/test/Transforms/InstCombine/zext-nneg.ll
+++ b/llvm/test/Transforms/InstCombine/zext-nneg.ll
@@ -136,7 +136,7 @@ define i64 @zext_nneg_of_zext_large_negative(i16 %arg) {
; CHECK-SAME: i16 [[ARG:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[SEXT:%.*]] = zext i16 [[ARG]] to i64
-; CHECK-NEXT: [[ADD:%.*]] = add nuw nsw i64 [[SEXT]], -2147450881
+; CHECK-NEXT: [[ADD:%.*]] = add nuw nsw i64 [[SEXT]], 2147516415
; CHECK-NEXT: ret i64 [[ADD]]
;
entry:
>From fee28c1c4d7b03f046436e78c6f4d77576c1836b Mon Sep 17 00:00:00 2001
From: John Brawn <john.brawn at arm.com>
Date: Wed, 12 Aug 2026 14:45:59 +0100
Subject: [PATCH 7/7] Add zext_of_trunc_same_size test
This is an example where not checking ComputeNumSignBits gives the wrong result
---
llvm/test/Transforms/InstCombine/zext-nneg.ll | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/zext-nneg.ll b/llvm/test/Transforms/InstCombine/zext-nneg.ll
index fd8490fdb3b32..20188f605652e 100644
--- a/llvm/test/Transforms/InstCombine/zext-nneg.ll
+++ b/llvm/test/Transforms/InstCombine/zext-nneg.ll
@@ -232,11 +232,26 @@ entry:
ret i64 %zext
}
+; trunc then zext nneg to the same size as before isn't a no-op, and we need to
+; have an and.
+define i64 @zext_of_trunc_same_size(i64 %arg) {
+; CHECK-LABEL: define i64 @zext_of_trunc_same_size(
+; CHECK-SAME: i64 [[ARG:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[ZEXT:%.*]] = and i64 [[ARG]], 4294967295
+; CHECK-NEXT: ret i64 [[ZEXT]]
+;
+entry:
+ %trunc = trunc i64 %arg to i32
+ %zext = zext nneg i32 %trunc to i64
+ ret i64 %zext
+}
+
; FIXME: The and isn't needed here, as the trunc nsw means we know the high bits
; are all equal to the sign bit, and zext nneg means we know the sign bit is
; zero.
-define i32 @zext_of_trunc(i64 %arg) {
-; CHECK-LABEL: define i32 @zext_of_trunc(
+define i32 @zext_of_trunc_nsw(i64 %arg) {
+; CHECK-LABEL: define i32 @zext_of_trunc_nsw(
; CHECK-SAME: i64 [[ARG:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = trunc i64 [[ARG]] to i32
; CHECK-NEXT: [[ZEXT:%.*]] = and i32 [[TMP1]], 65535
More information about the llvm-commits
mailing list