[llvm] [X86] Remove redundant TESTs before unsigned flag users (PR #202942)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 10 04:48:23 PDT 2026
https://github.com/Pppp1116 created https://github.com/llvm/llvm-project/pull/202942
Logical integer ops clear CF/OF like TEST and produce the same zero/sign/parity facts for their result. `optimizeCompareInstr` already reused those producers for compare-with-zero users that did not read CF; this patch tracks carry-clearing separately so unsigned flag users can reuse the original logical-op EFLAGS too.
The arithmetic path remains conservative: producers such as ADD still keep the TEST when the replacement condition reads CF. The existing AND-through-subreg redundant TEST path is taught the same carry-clearing fact.
Tests:
- `build-x86-patch/bin/llvm-lit -sv llvm/test/CodeGen/X86/optimize-compare.mir`
- `ninja -C build-x86-patch check-llvm-codegen-x86`
>From a7dbaaef93f57070e77aef1c9bcdfbb5ea75fc52 Mon Sep 17 00:00:00 2001
From: Pppp1116 <pcaiadoguerreiro at gmail.com>
Date: Wed, 10 Jun 2026 12:46:50 +0100
Subject: [PATCH] [X86] Remove redundant tests before unsigned flag users
---
llvm/lib/Target/X86/X86InstrInfo.cpp | 24 ++++++++---
llvm/test/CodeGen/X86/optimize-compare.mir | 47 ++++++++++++++++++++++
2 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Target/X86/X86InstrInfo.cpp b/llvm/lib/Target/X86/X86InstrInfo.cpp
index 15d2e10aa0f08..a301f9f9f122c 100644
--- a/llvm/lib/Target/X86/X86InstrInfo.cpp
+++ b/llvm/lib/Target/X86/X86InstrInfo.cpp
@@ -1031,7 +1031,8 @@ static bool
findRedundantFlagInstr(MachineInstr &CmpInstr, MachineInstr &CmpValDefInstr,
const MachineRegisterInfo *MRI, MachineInstr **AndInstr,
const TargetRegisterInfo *TRI, const X86Subtarget &ST,
- bool &NoSignFlag, bool &ClearsOverflowFlag) {
+ bool &NoSignFlag, bool &ClearsOverflowFlag,
+ bool &ClearsCarryFlag) {
if (!(CmpValDefInstr.getOpcode() == X86::SUBREG_TO_REG &&
CmpInstr.getOpcode() == X86::TEST64rr) &&
!(CmpValDefInstr.getOpcode() == X86::COPY &&
@@ -1136,6 +1137,7 @@ findRedundantFlagInstr(MachineInstr &CmpInstr, MachineInstr &CmpValDefInstr,
NoSignFlag = true;
// ClearsOverflowFlag is true for AND operation (no surprise).
ClearsOverflowFlag = true;
+ ClearsCarryFlag = true;
return true;
}
return false;
@@ -4989,9 +4991,11 @@ bool X86InstrInfo::isRedundantFlagInstr(const MachineInstr &FlagI,
/// Check whether the definition can be converted
/// to remove a comparison against zero.
inline static bool isDefConvertible(const MachineInstr &MI, bool &NoSignFlag,
- bool &ClearsOverflowFlag) {
+ bool &ClearsOverflowFlag,
+ bool &ClearsCarryFlag) {
NoSignFlag = false;
ClearsOverflowFlag = false;
+ ClearsCarryFlag = false;
// "ELF Handling for Thread-Local Storage" specifies that x86-64 GOTTPOFF, and
// i386 GOTNTPOFF/INDNTPOFF relocations can convert an ADD to a LEA during
@@ -5155,6 +5159,9 @@ inline static bool isDefConvertible(const MachineInstr &MI, bool &NoSignFlag,
CASE_ND(OR32rm)
CASE_ND(OR16rm)
CASE_ND(OR8rm)
+ ClearsOverflowFlag = true;
+ ClearsCarryFlag = true;
+ return true;
case X86::ANDN32rr:
case X86::ANDN32rm:
case X86::ANDN64rr:
@@ -5348,6 +5355,7 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
SmallVector<std::pair<MachineInstr *, unsigned>, 4> InstsToUpdate;
bool NoSignFlag = false;
bool ClearsOverflowFlag = false;
+ bool ClearsCarryFlag = false;
bool ShouldUpdateCC = false;
bool IsSwapped = false;
bool HasNF = Subtarget.hasNF();
@@ -5368,7 +5376,8 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
// testl %eax, %eax // <-- can be removed
if (&Inst == SrcRegDef) {
if (IsCmpZero &&
- isDefConvertible(Inst, NoSignFlag, ClearsOverflowFlag)) {
+ isDefConvertible(Inst, NoSignFlag, ClearsOverflowFlag,
+ ClearsCarryFlag)) {
MI = &Inst;
break;
}
@@ -5389,7 +5398,8 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
MachineInstr *AndInstr = nullptr;
if (IsCmpZero &&
findRedundantFlagInstr(CmpInstr, Inst, MRI, &AndInstr, TRI,
- Subtarget, NoSignFlag, ClearsOverflowFlag)) {
+ Subtarget, NoSignFlag, ClearsOverflowFlag,
+ ClearsCarryFlag)) {
assert(AndInstr != nullptr && X86::isAND(AndInstr->getOpcode()));
MI = AndInstr;
break;
@@ -5504,8 +5514,10 @@ bool X86InstrInfo::optimizeCompareInstr(MachineInstr &CmpInstr, Register SrcReg,
case X86::COND_AE:
case X86::COND_B:
case X86::COND_BE:
- // CF is used, we can't perform this optimization.
- return false;
+ // If CF is used, the instruction needs to clear it like CmpZero does.
+ if (!ClearsCarryFlag)
+ return false;
+ break;
case X86::COND_G:
case X86::COND_GE:
case X86::COND_L:
diff --git a/llvm/test/CodeGen/X86/optimize-compare.mir b/llvm/test/CodeGen/X86/optimize-compare.mir
index 36ab851a85153..165ca406df6c5 100644
--- a/llvm/test/CodeGen/X86/optimize-compare.mir
+++ b/llvm/test/CodeGen/X86/optimize-compare.mir
@@ -16,6 +16,53 @@ body: |
$al = SETCCr 5, implicit $eflags
...
---
+name: opt_zerocmp_logical_carry_cc
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: opt_zerocmp_logical_carry_cc
+ ; CHECK: [[COPY:%[0-9]+]]:gr32 = COPY $esi
+ ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr32 = COPY $edi
+ ; CHECK-NEXT: [[OR32rr:%[0-9]+]]:gr32 = OR32rr [[COPY]], [[COPY1]], implicit-def $eflags
+ ; CHECK-NEXT: $al = SETCCr 7, implicit $eflags
+ %0:gr32 = COPY $esi
+ %1:gr32 = COPY $edi
+ %2:gr32 = OR32rr %0, %1, implicit-def dead $eflags
+ ; TEST should be removed. OR clears CF like TEST and sets the same ZF.
+ TEST32rr %2, %2, implicit-def $eflags
+ $al = SETCCr 7, implicit $eflags
+...
+---
+name: opt_zerocmp_logical_carry_cc_subreg
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: opt_zerocmp_logical_carry_cc_subreg
+ ; CHECK: [[COPY:%[0-9]+]]:gr32 = COPY $esi
+ ; CHECK-NEXT: [[AND32ri:%[0-9]+]]:gr32 = AND32ri [[COPY]], i32 255, implicit-def $eflags
+ ; CHECK-NEXT: [[SUBREG_TO_REG:%[0-9]+]]:gr64 = SUBREG_TO_REG [[AND32ri]], %subreg.sub_32bit
+ ; CHECK-NEXT: $al = SETCCr 7, implicit $eflags
+ %0:gr32 = COPY $esi
+ %1:gr32 = AND32ri %0, i32 255, implicit-def dead $eflags
+ %2:gr64 = SUBREG_TO_REG %1, %subreg.sub_32bit
+ ; TEST should be removed. AND clears CF like TEST and sets the same ZF.
+ TEST64rr %2, %2, implicit-def $eflags
+ $al = SETCCr 7, implicit $eflags
+...
+---
+name: opt_zerocmp_arith_carry_cc_noopt
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: opt_zerocmp_arith_carry_cc_noopt
+ ; CHECK: [[COPY:%[0-9]+]]:gr32 = COPY $esi
+ ; CHECK-NEXT: [[ADD32ri:%[0-9]+]]:gr32 = ADD32ri [[COPY]], i32 1, implicit-def dead $eflags
+ ; CHECK-NEXT: TEST32rr [[ADD32ri]], [[ADD32ri]], implicit-def $eflags
+ ; CHECK-NEXT: $al = SETCCr 7, implicit $eflags
+ %0:gr32 = COPY $esi
+ %1:gr32 = ADD32ri %0, i32 1, implicit-def dead $eflags
+ ; TEST should not be removed. ADD does not clear CF like TEST.
+ TEST32rr %1, %1, implicit-def $eflags
+ $al = SETCCr 7, implicit $eflags
+...
+---
name: opt_zerocmp_1
body: |
bb.0:
More information about the llvm-commits
mailing list