[llvm] [X86][APX] Combine and/or with freeze to ccmp/ctest (PR #192362)

Feng Zou via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 16 19:14:34 PDT 2026


https://github.com/fzou1 updated https://github.com/llvm/llvm-project/pull/192362

>From 4f8803f4a2d99d8cbdcc6a8b2953b78be15c66d8 Mon Sep 17 00:00:00 2001
From: "Zou, Feng" <feng.zou at intel.com>
Date: Sat, 11 Apr 2026 19:52:43 +0530
Subject: [PATCH 1/2] [X86][APX] Combine and/or with freeze to ccmp/ctest

Resolve https://github.com/llvm/llvm-project/issues/191716.
---
 llvm/lib/Target/X86/X86ISelLowering.cpp | 30 +++++++++++++++----
 llvm/test/CodeGen/X86/apx/ccmp.ll       | 38 +++++++++++++++++++++++++
 llvm/test/CodeGen/X86/apx/ctest.ll      | 37 ++++++++++++++++++++++++
 3 files changed, 100 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index e647286301d6b..5b537a0489e5d 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -52341,21 +52341,41 @@ static SDValue combineX86SubCmpForFlags(SDNode *N, SDValue Flag,
 static SDValue combineAndOrForCcmpCtest(SDNode *N, SelectionDAG &DAG,
                                         TargetLowering::DAGCombinerInfo &DCI,
                                         const X86Subtarget &ST) {
-  // and/or(setcc(cc0, flag0), setcc(cc1, sub (X, Y)))
+  // Combine AND/OR of two SETCC nodes into CCMP/CTEST:
+  //
+  // and/or(setcc(cc0, flag0), setcc(cc1, sub(X, Y)))
   //  ->
   //    setcc(cc1, ccmp(X, Y, ~cflags/cflags, cc0/~cc0, flag0))
-
-  // and/or(setcc(cc0, flag0), setcc(cc1, cmp (X, 0)))
+  //
+  // and/or(setcc(cc0, flag0), setcc(cc1, cmp(X, 0)))
   //  ->
   //    setcc(cc1, ctest(X, X, ~cflags/cflags, cc0/~cc0, flag0))
   //
   // where cflags is determined by cc1.
+  //
+  // Fast-math canonicalization can introduce FREEZE nodes around SETCC
+  // operations to prevent poison propagation from unordered FP comparisons.
+  // We peek through single-use FREEZE nodes when matching:
+  //
+  // and/or(freeze(setcc(cc0, flag0)), freeze(setcc(cc1, sub(X, Y))))
+  //  ->
+  //    setcc(cc1, ccmp(X, Y, ~cflags/cflags, cc0/~cc0, flag0))
+  //
+  // and/or(freeze(setcc(cc0, flag0)), freeze(setcc(cc1, cmp(X, 0))))
+  //  ->
+  //    setcc(cc1, ctest(X, X, ~cflags/cflags, cc0/~cc0, flag0))
 
   if (!ST.hasCCMP())
     return SDValue();
 
-  SDValue SetCC0 = N->getOperand(0);
-  SDValue SetCC1 = N->getOperand(1);
+  auto PeekThroughFreeze = [](SDValue N) -> SDValue {
+    if (N.getOpcode() == ISD::FREEZE && N.hasOneUse())
+      return N.getOperand(0);
+    return N;
+  };
+
+  SDValue SetCC0 = PeekThroughFreeze(N->getOperand(0));
+  SDValue SetCC1 = PeekThroughFreeze(N->getOperand(1));
   if (SetCC0.getOpcode() != X86ISD::SETCC ||
       SetCC1.getOpcode() != X86ISD::SETCC)
     return SDValue();
diff --git a/llvm/test/CodeGen/X86/apx/ccmp.ll b/llvm/test/CodeGen/X86/apx/ccmp.ll
index a9cf0c060e729..af4ef1b19189f 100644
--- a/llvm/test/CodeGen/X86/apx/ccmp.ll
+++ b/llvm/test/CodeGen/X86/apx/ccmp.ll
@@ -1816,5 +1816,43 @@ if.end:                                           ; preds = %entry, %if.then
   ret void
 }
 
+; Fast-math patterns with CCMP instructions.
+; Fast-math canonicalization can introduce FREEZE nodes around SETCC operations,
+; and these tests verify that CCMP pattern matching works through those FREEZE
+; nodes.
+define i32 @test_or_fp_int(double %a, double %b, i32 %c, i32 %d) {
+; CHECK-LABEL: test_or_fp_int:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    ucomisd %xmm1, %xmm0 # encoding: [0x66,0x0f,0x2e,0xc1]
+; CHECK-NEXT:    ccmpal {dfv=} %esi, %edi # encoding: [0x62,0xf4,0x04,0x07,0x39,0xf7]
+; CHECK-NEXT:    setne %al # encoding: [0x0f,0x95,0xc0]
+; CHECK-NEXT:    movzbl %al, %eax # encoding: [0x0f,0xb6,0xc0]
+; CHECK-NEXT:    retq # encoding: [0xc3]
+;
+; NDD-LABEL: test_or_fp_int:
+; NDD:       # %bb.0:
+; NDD-NEXT:    ucomisd %xmm1, %xmm0 # encoding: [0x66,0x0f,0x2e,0xc1]
+; NDD-NEXT:    ccmpal {dfv=} %esi, %edi # encoding: [0x62,0xf4,0x04,0x07,0x39,0xf7]
+; NDD-NEXT:    setne %al # encoding: [0x0f,0x95,0xc0]
+; NDD-NEXT:    movzbl %al, %eax # encoding: [0x0f,0xb6,0xc0]
+; NDD-NEXT:    retq # encoding: [0xc3]
+;
+; SETZUCC-LABEL: test_or_fp_int:
+; SETZUCC:       # %bb.0:
+; SETZUCC-NEXT:    cmpl %esi, %edi # encoding: [0x39,0xf7]
+; SETZUCC-NEXT:    setzune %al # encoding: [0x62,0xf4,0x7f,0x18,0x45,0xc0]
+; SETZUCC-NEXT:    ucomisd %xmm1, %xmm0 # encoding: [0x66,0x0f,0x2e,0xc1]
+; SETZUCC-NEXT:    setzube %cl # encoding: [0x62,0xf4,0x7f,0x18,0x46,0xc1]
+; SETZUCC-NEXT:    orb %al, %cl # encoding: [0x08,0xc1]
+; SETZUCC-NEXT:    movzbl %cl, %eax # encoding: [0x0f,0xb6,0xc1]
+; SETZUCC-NEXT:    andl $1, %eax # encoding: [0x83,0xe0,0x01]
+; SETZUCC-NEXT:    retq # encoding: [0xc3]
+  %cmp = icmp ne i32 %c, %d
+  %cmp1 = fcmp fast ole double %a, %b
+  %sel = select i1 %cmp, i1 true, i1 %cmp1
+  %ext = zext i1 %sel to i32
+  ret i32 %ext
+}
+
 declare dso_local void @foo(...)
 declare {i64, i1} @llvm.ssub.with.overflow.i64(i64, i64) nounwind readnone
diff --git a/llvm/test/CodeGen/X86/apx/ctest.ll b/llvm/test/CodeGen/X86/apx/ctest.ll
index 0bd4ac1862ca1..11dffe4049f7d 100644
--- a/llvm/test/CodeGen/X86/apx/ctest.ll
+++ b/llvm/test/CodeGen/X86/apx/ctest.ll
@@ -1427,4 +1427,41 @@ bb:
   ret void
 }
 
+; Fast-math patterns with CTEST instructions.
+; Fast-math canonicalization can introduce FREEZE nodes around SETCC operations,
+; and these tests verify that CTEST pattern matching works through those FREEZE
+; nodes.
+define i32 @test_and_fp_int(double %a, i32 %b) {
+; CHECK-LABEL: test_and_fp_int:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    ucomisd {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm0
+; CHECK-NEXT:    ctestbl {dfv=zf} %edi, %edi
+; CHECK-NEXT:    setne %al
+; CHECK-NEXT:    movzbl %al, %eax
+; CHECK-NEXT:    retq
+;
+; NDD-LABEL: test_and_fp_int:
+; NDD:       # %bb.0:
+; NDD-NEXT:    ucomisd {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm0
+; NDD-NEXT:    ctestbl {dfv=zf} %edi, %edi
+; NDD-NEXT:    setne %al
+; NDD-NEXT:    movzbl %al, %eax
+; NDD-NEXT:    retq
+;
+; SETZUCC-LABEL: test_and_fp_int:
+; SETZUCC:       # %bb.0:
+; SETZUCC-NEXT:    testl %edi, %edi # encoding: [0x85,0xff]
+; SETZUCC-NEXT:    setzune %al # encoding: [0x62,0xf4,0x7f,0x18,0x45,0xc0]
+; SETZUCC-NEXT:    ucomisd {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm0 # encoding: [0x66,0x0f,0x2e,0x05,A,A,A,A]
+; SETZUCC-NEXT:    # fixup A - offset: 4, value: {{\.?LCPI[0-9]+_[0-9]+}}, kind: reloc_riprel_4byte
+; SETZUCC-NEXT:    setzub %cl # encoding: [0x62,0xf4,0x7f,0x18,0x42,0xc1]
+; SETZUCC-NEXT:    andb %al, %cl # encoding: [0x20,0xc1]
+; SETZUCC-NEXT:    movzbl %cl, %eax # encoding: [0x0f,0xb6,0xc1]
+; SETZUCC-NEXT:    retq # encoding: [0xc3]
+  %cmp = icmp ne i32 %b, 0
+  %cmp1 = fcmp fast olt double %a, 1.500000e+00
+  %sel = select i1 %cmp, i1 %cmp1, i1 false
+  %ext = zext i1 %sel to i32
+  ret i32 %ext
+}
 declare dso_local void @foo(...)

>From c9833cf8179ba56b24cfae6e4e4e7cb359e47009 Mon Sep 17 00:00:00 2001
From: "Zou, Feng" <feng.zou at intel.com>
Date: Wed, 15 Apr 2026 10:41:56 +0530
Subject: [PATCH 2/2] [X86] Add SETCC to
 isGuaranteedNotToBeUndefOrPoisonForTargetNode

X86ISD::SETCC always produces a well-defined result (0 or 1) based on
EFLAGS and cannot produce undef or poison values. Adding it to
isGuaranteedNotToBeUndefOrPoisonForTargetNode allows the optimizer to
eliminate FREEZE nodes around SETCC operations. With that, CCMP/CTEST
pattern matching works naturally with fastmath comparisons.
---
 llvm/lib/Target/X86/X86ISelLowering.cpp | 32 ++++++-------------------
 llvm/test/CodeGen/X86/apx/ccmp.ll       |  7 +++---
 llvm/test/CodeGen/X86/apx/ctest.ll      |  6 ++---
 llvm/test/CodeGen/X86/freeze-binary.ll  | 20 ++++++++--------
 4 files changed, 23 insertions(+), 42 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 5b537a0489e5d..a08c332b9102e 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -45755,6 +45755,8 @@ bool X86TargetLowering::isGuaranteedNotToBeUndefOrPoisonForTargetNode(
   case X86ISD::GlobalBaseReg:
   case X86ISD::Wrapper:
   case X86ISD::WrapperRIP:
+  // SETCC always produces a well-defined result (0 or 1) based on EFLAGS.
+  case X86ISD::SETCC:
     return true;
   case X86ISD::PACKSS:
   case X86ISD::PACKUS: {
@@ -52341,41 +52343,21 @@ static SDValue combineX86SubCmpForFlags(SDNode *N, SDValue Flag,
 static SDValue combineAndOrForCcmpCtest(SDNode *N, SelectionDAG &DAG,
                                         TargetLowering::DAGCombinerInfo &DCI,
                                         const X86Subtarget &ST) {
-  // Combine AND/OR of two SETCC nodes into CCMP/CTEST:
-  //
-  // and/or(setcc(cc0, flag0), setcc(cc1, sub(X, Y)))
+  // and/or(setcc(cc0, flag0), setcc(cc1, sub (X, Y)))
   //  ->
   //    setcc(cc1, ccmp(X, Y, ~cflags/cflags, cc0/~cc0, flag0))
-  //
-  // and/or(setcc(cc0, flag0), setcc(cc1, cmp(X, 0)))
+
+  // and/or(setcc(cc0, flag0), setcc(cc1, cmp (X, 0)))
   //  ->
   //    setcc(cc1, ctest(X, X, ~cflags/cflags, cc0/~cc0, flag0))
   //
   // where cflags is determined by cc1.
-  //
-  // Fast-math canonicalization can introduce FREEZE nodes around SETCC
-  // operations to prevent poison propagation from unordered FP comparisons.
-  // We peek through single-use FREEZE nodes when matching:
-  //
-  // and/or(freeze(setcc(cc0, flag0)), freeze(setcc(cc1, sub(X, Y))))
-  //  ->
-  //    setcc(cc1, ccmp(X, Y, ~cflags/cflags, cc0/~cc0, flag0))
-  //
-  // and/or(freeze(setcc(cc0, flag0)), freeze(setcc(cc1, cmp(X, 0))))
-  //  ->
-  //    setcc(cc1, ctest(X, X, ~cflags/cflags, cc0/~cc0, flag0))
 
   if (!ST.hasCCMP())
     return SDValue();
 
-  auto PeekThroughFreeze = [](SDValue N) -> SDValue {
-    if (N.getOpcode() == ISD::FREEZE && N.hasOneUse())
-      return N.getOperand(0);
-    return N;
-  };
-
-  SDValue SetCC0 = PeekThroughFreeze(N->getOperand(0));
-  SDValue SetCC1 = PeekThroughFreeze(N->getOperand(1));
+  SDValue SetCC0 = N->getOperand(0);
+  SDValue SetCC1 = N->getOperand(1);
   if (SetCC0.getOpcode() != X86ISD::SETCC ||
       SetCC1.getOpcode() != X86ISD::SETCC)
     return SDValue();
diff --git a/llvm/test/CodeGen/X86/apx/ccmp.ll b/llvm/test/CodeGen/X86/apx/ccmp.ll
index af4ef1b19189f..2c62e608223bc 100644
--- a/llvm/test/CodeGen/X86/apx/ccmp.ll
+++ b/llvm/test/CodeGen/X86/apx/ccmp.ll
@@ -1817,9 +1817,9 @@ if.end:                                           ; preds = %entry, %if.then
 }
 
 ; Fast-math patterns with CCMP instructions.
-; Fast-math canonicalization can introduce FREEZE nodes around SETCC operations,
-; and these tests verify that CCMP pattern matching works through those FREEZE
-; nodes.
+; Fast-math canonicalization can introduce FREEZE nodes around SETCC operations.
+; With SETCC in isGuaranteedNotToBeUndefOrPoisonForTargetNode, these FREEZE
+; nodes are eliminated early, allowing CCMP pattern matching to work naturally.
 define i32 @test_or_fp_int(double %a, double %b, i32 %c, i32 %d) {
 ; CHECK-LABEL: test_or_fp_int:
 ; CHECK:       # %bb.0:
@@ -1845,7 +1845,6 @@ define i32 @test_or_fp_int(double %a, double %b, i32 %c, i32 %d) {
 ; SETZUCC-NEXT:    setzube %cl # encoding: [0x62,0xf4,0x7f,0x18,0x46,0xc1]
 ; SETZUCC-NEXT:    orb %al, %cl # encoding: [0x08,0xc1]
 ; SETZUCC-NEXT:    movzbl %cl, %eax # encoding: [0x0f,0xb6,0xc1]
-; SETZUCC-NEXT:    andl $1, %eax # encoding: [0x83,0xe0,0x01]
 ; SETZUCC-NEXT:    retq # encoding: [0xc3]
   %cmp = icmp ne i32 %c, %d
   %cmp1 = fcmp fast ole double %a, %b
diff --git a/llvm/test/CodeGen/X86/apx/ctest.ll b/llvm/test/CodeGen/X86/apx/ctest.ll
index 11dffe4049f7d..8d5e36ed513f5 100644
--- a/llvm/test/CodeGen/X86/apx/ctest.ll
+++ b/llvm/test/CodeGen/X86/apx/ctest.ll
@@ -1428,9 +1428,9 @@ bb:
 }
 
 ; Fast-math patterns with CTEST instructions.
-; Fast-math canonicalization can introduce FREEZE nodes around SETCC operations,
-; and these tests verify that CTEST pattern matching works through those FREEZE
-; nodes.
+; Fast-math canonicalization can introduce FREEZE nodes around SETCC operations.
+; With SETCC in isGuaranteedNotToBeUndefOrPoisonForTargetNode, these FREEZE
+; nodes are eliminated early, allowing CTEST pattern matching to work naturally.
 define i32 @test_and_fp_int(double %a, i32 %b) {
 ; CHECK-LABEL: test_and_fp_int:
 ; CHECK:       # %bb.0:
diff --git a/llvm/test/CodeGen/X86/freeze-binary.ll b/llvm/test/CodeGen/X86/freeze-binary.ll
index 46b2571e196bb..15e9409a46eaa 100644
--- a/llvm/test/CodeGen/X86/freeze-binary.ll
+++ b/llvm/test/CodeGen/X86/freeze-binary.ll
@@ -866,11 +866,11 @@ define i32 @freeze_ssubo(i32 %a0, i32 %a1, i8 %a2, i8 %a3) nounwind {
 ; X86-LABEL: freeze_ssubo:
 ; X86:       # %bb.0:
 ; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-NEXT:    xorl %ecx, %ecx
-; X86-NEXT:    addb {{[0-9]+}}(%esp), %dl
-; X86-NEXT:    setb %cl
-; X86-NEXT:    andl $1, %ecx
+; X86-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT:    xorl %edx, %edx
+; X86-NEXT:    addb {{[0-9]+}}(%esp), %cl
+; X86-NEXT:    setb %dl
+; X86-NEXT:    movzbl %dl, %ecx
 ; X86-NEXT:    subl %ecx, %eax
 ; X86-NEXT:    subl {{[0-9]+}}(%esp), %eax
 ; X86-NEXT:    retl
@@ -898,11 +898,11 @@ define i32 @freeze_usubo(i32 %a0, i32 %a1, i8 %a2, i8 %a3) nounwind {
 ; X86-LABEL: freeze_usubo:
 ; X86:       # %bb.0:
 ; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-NEXT:    xorl %ecx, %ecx
-; X86-NEXT:    addb {{[0-9]+}}(%esp), %dl
-; X86-NEXT:    setb %cl
-; X86-NEXT:    andl $1, %ecx
+; X86-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT:    xorl %edx, %edx
+; X86-NEXT:    addb {{[0-9]+}}(%esp), %cl
+; X86-NEXT:    setb %dl
+; X86-NEXT:    movzbl %dl, %ecx
 ; X86-NEXT:    subl %ecx, %eax
 ; X86-NEXT:    subl {{[0-9]+}}(%esp), %eax
 ; X86-NEXT:    retl



More information about the llvm-commits mailing list