[llvm] 0a5793b - [InstCombine] Preserve IR flags when reordering icmp/fcmp/cast through a shuffle (#208627)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 14 07:38:14 PDT 2026
Author: ayrai-gb
Date: 2026-07-14T22:38:08+08:00
New Revision: 0a5793ba557fb31ea1f847aa353397bb9b88cb18
URL: https://github.com/llvm/llvm-project/commit/0a5793ba557fb31ea1f847aa353397bb9b88cb18
DIFF: https://github.com/llvm/llvm-project/commit/0a5793ba557fb31ea1f847aa353397bb9b88cb18.diff
LOG: [InstCombine] Preserve IR flags when reordering icmp/fcmp/cast through a shuffle (#208627)
InstCombine's `evaluateInDifferentElementOrder` pushes a lane-reordering
`shufflevector` *through* the instruction that produced its input,
rebuilding that instruction on the reordered operands via the `buildNew`
helper — a `switch` with one case per opcode. The binary-operator case
carefully re-applies the original's poison-generating flags
(`nuw`/`nsw`, `exact`, FMF), and the `GetElementPtr` case passes through
its `getNoWrapFlags()`.
The `ICmp`, `FCmp`, and cast cases are the odd ones out. Each is a terse
`return Builder.CreateICmp/CreateFCmp/CreateCast(...)` that copies only
the predicate/opcode, returning the rebuilt instruction with default
flags. As a result a reordered `icmp samesign`, `fcmp nnan ninf`, `zext
nneg`, or `trunc nuw`/`nsw` silently loses its flag, even though the
flag held on the original.
Route each rebuilt instruction through `copyIRFlags(I)`, bringing these
cases in line with the binary-op and GEP cases. `copyIRFlags` transfers
only the flags the source instruction actually carried, so this can
never introduce an unsound promise. This is a missed-optimization fix,
not a miscompile — dropping these flags only makes later passes more
conservative.
Added
`llvm/test/Transforms/InstCombine/shuffle-cmp-cast-preserve-flags.ll`
(`icmp_samesign`, `fcmp_fmf`, `zext_nneg`), which feed the compare/cast
from `insertelement` chains so that `canEvaluateShuffled` permits the
reorder and `buildNew` actually runs, and check that the flag survives
on the rebuilt instruction after the reversing shuffle.
Found via jlebar's X86 LLVM bug-hunt / FuzzX effort:
https://github.com/SemiAnalysisAI/FuzzX/tree/master/x86/bugs/241-instcombine-buildNew-shuffle-reorder-drops-cmp-cast-flags
---------
Co-authored-by: Ayush Rai <ayrai at amd.com>
Added:
llvm/test/Transforms/InstCombine/shuffle-cmp-cast-preserve-flags.ll
Modified:
llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp b/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
index 86f64363d4186..226253a7de3f8 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
@@ -2007,14 +2007,18 @@ static Value *buildNew(Instruction *I, ArrayRef<Value*> NewOps,
}
return New;
}
- case Instruction::ICmp:
+ case Instruction::ICmp: {
assert(NewOps.size() == 2 && "icmp with #ops != 2");
- return Builder.CreateICmp(cast<ICmpInst>(I)->getPredicate(), NewOps[0],
- NewOps[1]);
+ Value *New = Builder.CreateICmp(cast<ICmpInst>(I)->getPredicate(),
+ NewOps[0], NewOps[1]);
+ if (auto *NewI = dyn_cast<Instruction>(New))
+ NewI->copyIRFlags(I);
+ return New;
+ }
case Instruction::FCmp:
assert(NewOps.size() == 2 && "fcmp with #ops != 2");
- return Builder.CreateFCmp(cast<FCmpInst>(I)->getPredicate(), NewOps[0],
- NewOps[1]);
+ return Builder.CreateFCmpFMF(cast<FCmpInst>(I)->getPredicate(), NewOps[0],
+ NewOps[1], I);
case Instruction::Trunc:
case Instruction::ZExt:
case Instruction::SExt:
@@ -2030,8 +2034,11 @@ static Value *buildNew(Instruction *I, ArrayRef<Value*> NewOps,
I->getType()->getScalarType(),
cast<VectorType>(NewOps[0]->getType())->getElementCount());
assert(NewOps.size() == 1 && "cast with #ops != 1");
- return Builder.CreateCast(cast<CastInst>(I)->getOpcode(), NewOps[0],
- DestTy);
+ Value *New =
+ Builder.CreateCast(cast<CastInst>(I)->getOpcode(), NewOps[0], DestTy);
+ if (auto *NewI = dyn_cast<Instruction>(New))
+ NewI->copyIRFlags(I);
+ return New;
}
case Instruction::GetElementPtr: {
Value *Ptr = NewOps[0];
diff --git a/llvm/test/Transforms/InstCombine/shuffle-cmp-cast-preserve-flags.ll b/llvm/test/Transforms/InstCombine/shuffle-cmp-cast-preserve-flags.ll
new file mode 100644
index 0000000000000..c385a107ca4d6
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/shuffle-cmp-cast-preserve-flags.ll
@@ -0,0 +1,61 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; When a shufflevector is pushed through the instruction that produced its
+; input (evaluateInDifferentElementOrder -> buildNew), the rebuilt icmp/fcmp/
+; cast must keep the poison-generating flags of the original (samesign, FMF,
+; nneg/nuw/nsw). The operands are fed from insertelement chains so that
+; canEvaluateShuffled permits the reorder and buildNew actually runs.
+
+define <2 x i1> @icmp_samesign(i32 %s0, i32 %s1, i32 %t0, i32 %t1) {
+; CHECK-LABEL: define <2 x i1> @icmp_samesign(
+; CHECK-SAME: i32 [[S0:%.*]], i32 [[S1:%.*]], i32 [[T0:%.*]], i32 [[T1:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = insertelement <2 x i32> poison, i32 [[S1]], i64 0
+; CHECK-NEXT: [[TMP2:%.*]] = insertelement <2 x i32> [[TMP1]], i32 [[S0]], i64 1
+; CHECK-NEXT: [[TMP3:%.*]] = insertelement <2 x i32> poison, i32 [[T1]], i64 0
+; CHECK-NEXT: [[TMP4:%.*]] = insertelement <2 x i32> [[TMP3]], i32 [[T0]], i64 1
+; CHECK-NEXT: [[R:%.*]] = icmp samesign slt <2 x i32> [[TMP2]], [[TMP4]]
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %a0 = insertelement <2 x i32> poison, i32 %s0, i32 0
+ %a1 = insertelement <2 x i32> %a0, i32 %s1, i32 1
+ %b0 = insertelement <2 x i32> poison, i32 %t0, i32 0
+ %b1 = insertelement <2 x i32> %b0, i32 %t1, i32 1
+ %c = icmp samesign slt <2 x i32> %a1, %b1
+ %r = shufflevector <2 x i1> %c, <2 x i1> poison, <2 x i32> <i32 1, i32 0>
+ ret <2 x i1> %r
+}
+
+define <2 x i1> @fcmp_fmf(float %s0, float %s1, float %t0, float %t1) {
+; CHECK-LABEL: define <2 x i1> @fcmp_fmf(
+; CHECK-SAME: float [[S0:%.*]], float [[S1:%.*]], float [[T0:%.*]], float [[T1:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = insertelement <2 x float> poison, float [[S1]], i64 0
+; CHECK-NEXT: [[TMP2:%.*]] = insertelement <2 x float> [[TMP1]], float [[S0]], i64 1
+; CHECK-NEXT: [[TMP3:%.*]] = insertelement <2 x float> poison, float [[T1]], i64 0
+; CHECK-NEXT: [[TMP4:%.*]] = insertelement <2 x float> [[TMP3]], float [[T0]], i64 1
+; CHECK-NEXT: [[R:%.*]] = fcmp nnan ninf olt <2 x float> [[TMP2]], [[TMP4]]
+; CHECK-NEXT: ret <2 x i1> [[R]]
+;
+ %a0 = insertelement <2 x float> poison, float %s0, i32 0
+ %a1 = insertelement <2 x float> %a0, float %s1, i32 1
+ %b0 = insertelement <2 x float> poison, float %t0, i32 0
+ %b1 = insertelement <2 x float> %b0, float %t1, i32 1
+ %c = fcmp nnan ninf olt <2 x float> %a1, %b1
+ %r = shufflevector <2 x i1> %c, <2 x i1> poison, <2 x i32> <i32 1, i32 0>
+ ret <2 x i1> %r
+}
+
+define <2 x i64> @zext_nneg(i32 %s0, i32 %s1) {
+; CHECK-LABEL: define <2 x i64> @zext_nneg(
+; CHECK-SAME: i32 [[S0:%.*]], i32 [[S1:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = insertelement <2 x i32> poison, i32 [[S1]], i64 0
+; CHECK-NEXT: [[TMP2:%.*]] = insertelement <2 x i32> [[TMP1]], i32 [[S0]], i64 1
+; CHECK-NEXT: [[R:%.*]] = zext nneg <2 x i32> [[TMP2]] to <2 x i64>
+; CHECK-NEXT: ret <2 x i64> [[R]]
+;
+ %a0 = insertelement <2 x i32> poison, i32 %s0, i32 0
+ %a1 = insertelement <2 x i32> %a0, i32 %s1, i32 1
+ %c = zext nneg <2 x i32> %a1 to <2 x i64>
+ %r = shufflevector <2 x i64> %c, <2 x i64> poison, <2 x i32> <i32 1, i32 0>
+ ret <2 x i64> %r
+}
More information about the llvm-commits
mailing list