[llvm] bbedf74 - [InstCombine] Fold scmp/ucmp of extended operands (#218871)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 26 12:06:33 PDT 2026


Author: Aayush Shrivastava
Date: 2026-08-27T03:06:28+08:00
New Revision: bbedf748783f904529a702c45dba6e8e56d0abac

URL: https://github.com/llvm/llvm-project/commit/bbedf748783f904529a702c45dba6e8e56d0abac
DIFF: https://github.com/llvm/llvm-project/commit/bbedf748783f904529a702c45dba6e8e56d0abac.diff

LOG: [InstCombine] Fold scmp/ucmp of extended operands (#218871)

Fixes #202467

`scmp`/`ucmp` compare their operands, and both `sext` and `zext` are
monotonic with respect to the unsigned order (`sext` additionally
preserves the signed order). So a common extension on both operands can
be dropped and the compare done in the narrower type:

scmp (sext X), (sext Y) --> scmp X, Y
scmp (zext X), (zext Y) --> ucmp X, Y
ucmp (ext X),  (ext Y)  --> ucmp X, Y

Zero-extended values are non-negative, which is why a signed compare of
them becomes an unsigned compare of the narrow operands.

Both operands must use the same extend opcode and source type. Since
these intrinsics are not commutative, the extend is looked for on either
side, and a constant operand is narrowed instead when truncating and
re-extending it gives back the same constant (`getLosslessInvCast`). No
one-use restriction is needed since no new cast is created.

This also adds the previously missing `Intrinsic::ucmp` case to
`visitCallInst`.

Added: 
    

Modified: 
    llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
    llvm/test/Transforms/InstCombine/scmp.ll
    llvm/test/Transforms/InstCombine/ucmp.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 8a9c99eb36f66..1402c12f2025f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -1984,6 +1984,60 @@ static Value *foldSinAndCosToSinCos(IntrinsicInst *II, IRBuilderBase &B,
   return IsSin ? Sin : Cos;
 }
 
+/// Fold an scmp/ucmp intrinsic whose operands are extended from a narrower
+/// type:
+///   scmp (sext X), (sext Y) --> scmp X, Y
+///   scmp (zext X), (zext Y) --> ucmp X, Y
+///   ucmp (ext X), (ext Y)   --> ucmp X, Y
+/// Both operands must use the same extend opcode and source type. A constant
+/// operand is narrowed instead, if truncating and re-extending it gives back
+/// the same constant.
+static Value *foldCmpIntrinsicOfExtended(IntrinsicInst *II,
+                                         InstCombiner::BuilderTy &Builder,
+                                         const DataLayout &DL) {
+  // scmp/ucmp are not commutative, so the extend may be on either side.
+  unsigned ExtIdx = 0;
+  Value *X;
+  if (!match(II->getArgOperand(0), m_ZExtOrSExt(m_Value(X)))) {
+    ExtIdx = 1;
+    if (!match(II->getArgOperand(1), m_ZExtOrSExt(m_Value(X))))
+      return nullptr;
+  }
+
+  auto CastOpc = static_cast<Instruction::CastOps>(
+      cast<Operator>(II->getArgOperand(ExtIdx))->getOpcode());
+  Type *NarrowTy = X->getType();
+
+  // The other operand must be the same kind of extend from the same type, or a
+  // constant that can be narrowed losslessly.
+  Value *OtherOp = II->getArgOperand(1 - ExtIdx);
+  Value *Y;
+  Constant *WideC;
+  if (match(OtherOp, m_ZExtOrSExt(m_Value(Y)))) {
+    if (cast<Operator>(OtherOp)->getOpcode() != CastOpc ||
+        Y->getType() != NarrowTy)
+      return nullptr;
+  } else if (match(OtherOp, m_ImmConstant(WideC))) {
+    Y = getLosslessInvCast(WideC, NarrowTy, CastOpc, DL);
+    if (!Y)
+      return nullptr;
+  } else {
+    return nullptr;
+  }
+
+  // Both extends preserve the unsigned order, so an unsigned compare of the
+  // narrow operands is always equivalent. The signed order is only preserved by
+  // sext; zero extended values are non-negative, so a signed compare of those
+  // is an unsigned compare of the narrow operands.
+  Intrinsic::ID NewIID =
+      II->getIntrinsicID() == Intrinsic::scmp && CastOpc == Instruction::SExt
+          ? Intrinsic::scmp
+          : Intrinsic::ucmp;
+  if (ExtIdx != 0)
+    std::swap(X, Y);
+  return Builder.CreateIntrinsic(II->getType(), NewIID, {X, Y});
+}
+
 /// CallInst simplification. This mostly only handles folding of intrinsic
 /// instructions. For normal calls, it allows visitCallBase to do the heavy
 /// lifting.
@@ -2482,7 +2536,14 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
 
     break;
   }
-  case Intrinsic::scmp: {
+  case Intrinsic::scmp:
+  case Intrinsic::ucmp: {
+    if (Value *V = foldCmpIntrinsicOfExtended(II, Builder, DL))
+      return replaceInstUsesWith(CI, V);
+
+    if (IID == Intrinsic::ucmp)
+      break;
+
     Value *I0 = II->getArgOperand(0), *I1 = II->getArgOperand(1);
 
     // scmp(X, 0) -> sext_or_trunc(X) if X is known to be one of -1, 0, 1.

diff  --git a/llvm/test/Transforms/InstCombine/scmp.ll b/llvm/test/Transforms/InstCombine/scmp.ll
index 4352834fa6e00..9d55843e34815 100644
--- a/llvm/test/Transforms/InstCombine/scmp.ll
+++ b/llvm/test/Transforms/InstCombine/scmp.ll
@@ -1019,3 +1019,181 @@ define i8 @scmp_zero_of_ucmp(i32 %x, i32 %y) {
   %r = call i8 @llvm.scmp.i8.i64(i64 %cmp, i64 0)
   ret i8 %r
 }
+
+; scmp(sext(X), sext(Y)) -> scmp(X, Y): sign extension preserves the signed
+; order, so the compare can be done in the narrower type.
+define i8 @scmp_of_sexts(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @scmp_of_sexts(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %sy = sext i32 %y to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %sx, i64 %sy)
+  ret i8 %r
+}
+
+; scmp(zext(X), zext(Y)) -> ucmp(X, Y): both operands are non-negative, so the
+; signed compare of the wide values is an unsigned compare of the narrow ones.
+define i8 @scmp_of_zexts(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @scmp_of_zexts(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %zy = zext i32 %y to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %zx, i64 %zy)
+  ret i8 %r
+}
+
+define <4 x i8> @scmp_of_sexts_vec(<4 x i16> %x, <4 x i16> %y) {
+; CHECK-LABEL: define <4 x i8> @scmp_of_sexts_vec(
+; CHECK-SAME: <4 x i16> [[X:%.*]], <4 x i16> [[Y:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call <4 x i8> @llvm.scmp.v4i8.v4i16(<4 x i16> [[X]], <4 x i16> [[Y]])
+; CHECK-NEXT:    ret <4 x i8> [[R]]
+;
+  %sx = sext <4 x i16> %x to <4 x i32>
+  %sy = sext <4 x i16> %y to <4 x i32>
+  %r = call <4 x i8> @llvm.scmp.v4i8.v4i32(<4 x i32> %sx, <4 x i32> %sy)
+  ret <4 x i8> %r
+}
+
+; The extends may have other uses: no new cast is created and the compare only
+; gets narrower.
+define i8 @scmp_of_sexts_multiuse(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @scmp_of_sexts_multiuse(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[SX:%.*]] = sext i32 [[X]] to i64
+; CHECK-NEXT:    [[SY:%.*]] = sext i32 [[Y]] to i64
+; CHECK-NEXT:    call void @use64(i64 [[SX]])
+; CHECK-NEXT:    call void @use64(i64 [[SY]])
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %sy = sext i32 %y to i64
+  call void @use64(i64 %sx)
+  call void @use64(i64 %sy)
+  %r = call i8 @llvm.scmp.i8.i64(i64 %sx, i64 %sy)
+  ret i8 %r
+}
+
+; Negative test: the extends use 
diff erent opcodes.
+define i8 @scmp_of_sext_zext(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @scmp_of_sext_zext(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[SX:%.*]] = sext i32 [[X]] to i64
+; CHECK-NEXT:    [[ZY:%.*]] = zext i32 [[Y]] to i64
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i64(i64 [[SX]], i64 [[ZY]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %zy = zext i32 %y to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %sx, i64 %zy)
+  ret i8 %r
+}
+
+; Negative test: the extends have 
diff erent source types.
+define i8 @scmp_of_sexts_
diff erent_src_ty(i32 %x, i16 %y) {
+; CHECK-LABEL: define i8 @scmp_of_sexts_
diff erent_src_ty(
+; CHECK-SAME: i32 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[SX:%.*]] = sext i32 [[X]] to i64
+; CHECK-NEXT:    [[SY:%.*]] = sext i16 [[Y]] to i64
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i64(i64 [[SX]], i64 [[SY]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %sy = sext i16 %y to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %sx, i64 %sy)
+  ret i8 %r
+}
+
+define i8 @scmp_sext_const(i32 %x) {
+; CHECK-LABEL: define i8 @scmp_sext_const(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 [[X]], i32 -42)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %sx, i64 -42)
+  ret i8 %r
+}
+
+define i8 @scmp_const_sext(i32 %x) {
+; CHECK-LABEL: define i8 @scmp_const_sext(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 -42, i32 [[X]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 -42, i64 %sx)
+  ret i8 %r
+}
+
+define i8 @scmp_zext_const(i32 %x) {
+; CHECK-LABEL: define i8 @scmp_zext_const(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 42)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %zx, i64 42)
+  ret i8 %r
+}
+
+define <2 x i8> @scmp_sext_const_vec(<2 x i16> %x) {
+; CHECK-LABEL: define <2 x i8> @scmp_sext_const_vec(
+; CHECK-SAME: <2 x i16> [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call <2 x i8> @llvm.scmp.v2i8.v2i16(<2 x i16> [[X]], <2 x i16> <i16 -7, i16 3>)
+; CHECK-NEXT:    ret <2 x i8> [[R]]
+;
+  %sx = sext <2 x i16> %x to <2 x i32>
+  %r = call <2 x i8> @llvm.scmp.v2i8.v2i32(<2 x i32> %sx, <2 x i32> <i32 -7, i32 3>)
+  ret <2 x i8> %r
+}
+
+; The constant is not representable in the narrow type, so the extend is not
+; dropped; the compare is resolved by range analysis instead.
+define i8 @scmp_sext_const_not_narrowable(i8 %x) {
+; CHECK-LABEL: define i8 @scmp_sext_const_not_narrowable(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    ret i8 -1
+;
+  %sx = sext i8 %x to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %sx, i64 1000)
+  ret i8 %r
+}
+
+; Negative test: one of the constant lanes is not representable in the narrow
+; type, so the whole constant cannot be narrowed.
+define <2 x i8> @scmp_sext_const_vec_not_narrowable(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i8> @scmp_sext_const_vec_not_narrowable(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[SX:%.*]] = sext <2 x i8> [[X]] to <2 x i16>
+; CHECK-NEXT:    [[R:%.*]] = call <2 x i8> @llvm.scmp.v2i8.v2i16(<2 x i16> [[SX]], <2 x i16> <i16 5, i16 300>)
+; CHECK-NEXT:    ret <2 x i8> [[R]]
+;
+  %sx = sext <2 x i8> %x to <2 x i16>
+  %r = call <2 x i8> @llvm.scmp.v2i8.v2i16(<2 x i16> %sx, <2 x i16> <i16 5, i16 300>)
+  ret <2 x i8> %r
+}
+
+; Real-world motivating case (Rust `<[u8; 32]>::cmp`): the memcmp result is
+; sign extended only to feed scmp against zero.
+define i8 @scmp_zero_of_sext_memcmp(ptr %x, ptr %y) {
+; CHECK-LABEL: define i8 @scmp_zero_of_sext_memcmp(
+; CHECK-SAME: ptr [[X:%.*]], ptr [[Y:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = call i32 @memcmp(ptr noundef nonnull dereferenceable(32) [[X]], ptr noundef nonnull dereferenceable(32) [[Y]], i64 32)
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.scmp.i8.i32(i32 [[CMP]], i32 0)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %cmp = call i32 @memcmp(ptr %x, ptr %y, i64 32)
+  %s = sext i32 %cmp to i64
+  %r = call i8 @llvm.scmp.i8.i64(i64 %s, i64 0)
+  ret i8 %r
+}
+
+declare void @use64(i64 %value)
+declare i32 @memcmp(ptr, ptr, i64)

diff  --git a/llvm/test/Transforms/InstCombine/ucmp.ll b/llvm/test/Transforms/InstCombine/ucmp.ll
index 3ccfb4f6fd6e6..1f53617d9cf45 100644
--- a/llvm/test/Transforms/InstCombine/ucmp.ll
+++ b/llvm/test/Transforms/InstCombine/ucmp.ll
@@ -622,3 +622,152 @@ define i8 @trunc_ucmp_multiuse(i32 %x, i32 %y) {
   %tr = trunc i32 %cmp to i8
   ret i8 %tr
 }
+
+; ucmp(zext(X), zext(Y)) -> ucmp(X, Y): zero extension preserves the unsigned
+; order, so the compare can be done in the narrower type.
+define i8 @ucmp_of_zexts(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @ucmp_of_zexts(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %zy = zext i32 %y to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %zx, i64 %zy)
+  ret i8 %r
+}
+
+; ucmp(sext(X), sext(Y)) -> ucmp(X, Y): sign extension is monotonic with
+; respect to the unsigned order too.
+define i8 @ucmp_of_sexts(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @ucmp_of_sexts(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %sy = sext i32 %y to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %sx, i64 %sy)
+  ret i8 %r
+}
+
+define <4 x i8> @ucmp_of_zexts_vec(<4 x i16> %x, <4 x i16> %y) {
+; CHECK-LABEL: define <4 x i8> @ucmp_of_zexts_vec(
+; CHECK-SAME: <4 x i16> [[X:%.*]], <4 x i16> [[Y:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call <4 x i8> @llvm.ucmp.v4i8.v4i16(<4 x i16> [[X]], <4 x i16> [[Y]])
+; CHECK-NEXT:    ret <4 x i8> [[R]]
+;
+  %zx = zext <4 x i16> %x to <4 x i32>
+  %zy = zext <4 x i16> %y to <4 x i32>
+  %r = call <4 x i8> @llvm.ucmp.v4i8.v4i32(<4 x i32> %zx, <4 x i32> %zy)
+  ret <4 x i8> %r
+}
+
+define i8 @ucmp_of_zexts_multiuse(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @ucmp_of_zexts_multiuse(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[ZX:%.*]] = zext i32 [[X]] to i64
+; CHECK-NEXT:    [[ZY:%.*]] = zext i32 [[Y]] to i64
+; CHECK-NEXT:    call void @use64(i64 [[ZX]])
+; CHECK-NEXT:    call void @use64(i64 [[ZY]])
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 [[Y]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %zy = zext i32 %y to i64
+  call void @use64(i64 %zx)
+  call void @use64(i64 %zy)
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %zx, i64 %zy)
+  ret i8 %r
+}
+
+; Negative test: the extends use 
diff erent opcodes.
+define i8 @ucmp_of_zext_sext(i32 %x, i32 %y) {
+; CHECK-LABEL: define i8 @ucmp_of_zext_sext(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[ZX:%.*]] = zext i32 [[X]] to i64
+; CHECK-NEXT:    [[SY:%.*]] = sext i32 [[Y]] to i64
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i64(i64 [[ZX]], i64 [[SY]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %sy = sext i32 %y to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %zx, i64 %sy)
+  ret i8 %r
+}
+
+; Negative test: the extends have 
diff erent source types.
+define i8 @ucmp_of_zexts_
diff erent_src_ty(i32 %x, i16 %y) {
+; CHECK-LABEL: define i8 @ucmp_of_zexts_
diff erent_src_ty(
+; CHECK-SAME: i32 [[X:%.*]], i16 [[Y:%.*]]) {
+; CHECK-NEXT:    [[ZX:%.*]] = zext i32 [[X]] to i64
+; CHECK-NEXT:    [[ZY:%.*]] = zext i16 [[Y]] to i64
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i64(i64 [[ZX]], i64 [[ZY]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %zy = zext i16 %y to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %zx, i64 %zy)
+  ret i8 %r
+}
+
+define i8 @ucmp_zext_const(i32 %x) {
+; CHECK-LABEL: define i8 @ucmp_zext_const(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 42)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %zx, i64 42)
+  ret i8 %r
+}
+
+define i8 @ucmp_const_zext(i32 %x) {
+; CHECK-LABEL: define i8 @ucmp_const_zext(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 42, i32 [[X]])
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %zx = zext i32 %x to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 42, i64 %zx)
+  ret i8 %r
+}
+
+define i8 @ucmp_sext_const(i32 %x) {
+; CHECK-LABEL: define i8 @ucmp_sext_const(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call i8 @llvm.ucmp.i8.i32(i32 [[X]], i32 -42)
+; CHECK-NEXT:    ret i8 [[R]]
+;
+  %sx = sext i32 %x to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %sx, i64 -42)
+  ret i8 %r
+}
+
+; The constant is not representable in the narrow type, so the extend is not
+; dropped; the compare is resolved by range analysis instead.
+define i8 @ucmp_zext_const_not_narrowable(i8 %x) {
+; CHECK-LABEL: define i8 @ucmp_zext_const_not_narrowable(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT:    ret i8 -1
+;
+  %zx = zext i8 %x to i64
+  %r = call i8 @llvm.ucmp.i8.i64(i64 %zx, i64 1000)
+  ret i8 %r
+}
+
+; Negative test: one of the constant lanes is not representable in the narrow
+; type, so the whole constant cannot be narrowed.
+define <2 x i8> @ucmp_zext_const_vec_not_narrowable(<2 x i8> %x) {
+; CHECK-LABEL: define <2 x i8> @ucmp_zext_const_vec_not_narrowable(
+; CHECK-SAME: <2 x i8> [[X:%.*]]) {
+; CHECK-NEXT:    [[ZX:%.*]] = zext <2 x i8> [[X]] to <2 x i16>
+; CHECK-NEXT:    [[R:%.*]] = call <2 x i8> @llvm.ucmp.v2i8.v2i16(<2 x i16> [[ZX]], <2 x i16> <i16 5, i16 300>)
+; CHECK-NEXT:    ret <2 x i8> [[R]]
+;
+  %zx = zext <2 x i8> %x to <2 x i16>
+  %r = call <2 x i8> @llvm.ucmp.v2i8.v2i16(<2 x i16> %zx, <2 x i16> <i16 5, i16 300>)
+  ret <2 x i8> %r
+}
+
+declare void @use64(i64 %value)


        


More information about the llvm-commits mailing list