[llvm] [InstCombine] Preserve IR flags when reordering icmp/fcmp/cast through a shuffle (PR #208627)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 9 23:07:32 PDT 2026
https://github.com/ayrai-gb created https://github.com/llvm/llvm-project/pull/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
cc @jlebar
>From 507105685618f7c57c8d3ca608d1b8ef0d28b497 Mon Sep 17 00:00:00 2001
From: Ayush Rai <your-email at amd.com>
Date: Fri, 10 Jul 2026 11:34:03 +0530
Subject: [PATCH] [InstCombine] Preserve flags when reordering icmp/fcmp/cast
through a shuffle
---
.../InstCombine/InstCombineVectorOps.cpp | 27 +++++---
.../shuffle-cmp-cast-preserve-flags.ll | 61 +++++++++++++++++++
2 files changed, 80 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/Transforms/InstCombine/shuffle-cmp-cast-preserve-flags.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp b/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
index 86f64363d4186..9b3d3471a1878 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
@@ -2007,14 +2007,22 @@ 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]);
- case Instruction::FCmp:
+ 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]);
+ Value *New = Builder.CreateFCmp(cast<FCmpInst>(I)->getPredicate(),
+ NewOps[0], NewOps[1]);
+ if (auto *NewI = dyn_cast<Instruction>(New))
+ NewI->copyIRFlags(I);
+ return New;
+ }
case Instruction::Trunc:
case Instruction::ZExt:
case Instruction::SExt:
@@ -2030,8 +2038,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