[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