[llvm] [X86][CCMP] Reorder Comparison Trees to Facilitate CSE (PR #211476)
Phoebe Wang via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 23 00:08:52 PDT 2026
https://github.com/phoebewang created https://github.com/llvm/llvm-project/pull/211476
This patch ports AArch64's CMP CSE optimization #168064
Assisted-by: Claude Opus 4.8
>From d81fbb9bb777d363cd1b02e3b1be33d3a55dac3f Mon Sep 17 00:00:00 2001
From: Phoebe Wang <phoebe.wang at intel.com>
Date: Thu, 23 Jul 2026 14:48:02 +0800
Subject: [PATCH 1/2] [NFC] Add test case
---
llvm/test/CodeGen/X86/apx/ccmp-cse.ll | 128 ++++++++++++++++++++++++++
1 file changed, 128 insertions(+)
create mode 100644 llvm/test/CodeGen/X86/apx/ccmp-cse.ll
diff --git a/llvm/test/CodeGen/X86/apx/ccmp-cse.ll b/llvm/test/CodeGen/X86/apx/ccmp-cse.ll
new file mode 100644
index 0000000000000..fbefbabae1440
--- /dev/null
+++ b/llvm/test/CodeGen/X86/apx/ccmp-cse.ll
@@ -0,0 +1,128 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+ccmp | FileCheck %s
+
+; When one of the comparisons in a conjunction/disjunction tree has a
+; corresponding SUB node in the DAG, that comparison should be reordered to
+; become the root CMP so its EFLAGS can be reused by the SUB. This avoids an
+; extra copy and a separate compare instruction.
+
+; The comparison matching the SUB is on the left of the OR and would, without
+; reordering, be emitted as a CCMP (which cannot share flags with the SUB).
+define i64 @test_or_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
+; CHECK-LABEL: test_or_sub_on_lhs:
+; CHECK: # %bb.0:
+; CHECK-NEXT: movq %rdx, %r8
+; CHECK-NEXT: subq %rcx, %r8
+; CHECK-NEXT: xorl %eax, %eax
+; CHECK-NEXT: cmpq %rsi, %rdi
+; CHECK-NEXT: ccmpaeq {dfv=} %rcx, %rdx
+; CHECK-NEXT: cmovbeq %r8, %rax
+; CHECK-NEXT: retq
+ %cmp.match = icmp ugt i64 %c, %d
+ %cmp.other = icmp ult i64 %a, %b
+ %or.cond = or i1 %cmp.match, %cmp.other
+ %sub.reuse = sub nuw i64 %c, %d
+ %res = select i1 %or.cond, i64 0, i64 %sub.reuse
+ ret i64 %res
+}
+
+; Same for AND.
+define i64 @test_and_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
+; CHECK-LABEL: test_and_sub_on_lhs:
+; CHECK: # %bb.0:
+; CHECK-NEXT: movq %rdx, %r8
+; CHECK-NEXT: subq %rcx, %r8
+; CHECK-NEXT: xorl %eax, %eax
+; CHECK-NEXT: cmpq %rsi, %rdi
+; CHECK-NEXT: ccmpbq {dfv=cf} %rcx, %rdx
+; CHECK-NEXT: cmovbeq %r8, %rax
+; CHECK-NEXT: retq
+ %cmp.match = icmp ugt i64 %c, %d
+ %cmp.other = icmp ult i64 %a, %b
+ %and.cond = and i1 %cmp.match, %cmp.other
+ %sub.reuse = sub nuw i64 %c, %d
+ %res = select i1 %and.cond, i64 0, i64 %sub.reuse
+ ret i64 %res
+}
+
+; The SUB-matching comparison is nested deeper on the left; it should still be
+; hoisted to the root CMP.
+define i64 @test_two_ors_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d, i64 %e) nounwind {
+; CHECK-LABEL: test_two_ors_sub_on_lhs:
+; CHECK: # %bb.0:
+; CHECK-NEXT: movq %rdx, %rcx
+; CHECK-NEXT: subq %r8, %rcx
+; CHECK-NEXT: xorl %eax, %eax
+; CHECK-NEXT: cmpq %rdx, %rsi
+; CHECK-NEXT: ccmpaeq {dfv=cf} %rsi, %rdi
+; CHECK-NEXT: ccmpaeq {dfv=} %r8, %rdx
+; CHECK-NEXT: cmovbeq %rcx, %rax
+; CHECK-NEXT: retq
+ %cmp.match = icmp ugt i64 %c, %e
+ %cmp.other1 = icmp ult i64 %a, %b
+ %cmp.other2 = icmp ult i64 %b, %c
+ %or.other = or i1 %cmp.other1, %cmp.other2
+ %or.cond = or i1 %cmp.match, %or.other
+ %sub.reuse = sub nuw i64 %c, %e
+ %res = select i1 %or.cond, i64 0, i64 %sub.reuse
+ ret i64 %res
+}
+
+; Negative test: the SUB-matching comparison is already on the right (the side
+; that becomes the root CMP), so no reordering is needed.
+define i64 @test_or_sub_on_rhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
+; CHECK-LABEL: test_or_sub_on_rhs:
+; CHECK: # %bb.0:
+; CHECK-NEXT: xorl %eax, %eax
+; CHECK-NEXT: subq %rcx, %rdx
+; CHECK-NEXT: ccmpbeq {dfv=cf} %rsi, %rdi
+; CHECK-NEXT: cmovaeq %rdx, %rax
+; CHECK-NEXT: retq
+ %cmp.other = icmp ult i64 %a, %b
+ %cmp.match = icmp ugt i64 %c, %d
+ %or.cond = or i1 %cmp.other, %cmp.match
+ %sub.reuse = sub nuw i64 %c, %d
+ %res = select i1 %or.cond, i64 0, i64 %sub.reuse
+ ret i64 %res
+}
+
+; Negative test: there is no analogue of a SUB updating EFLAGS for floating
+; point, so nothing should be reordered.
+define float @test_negative_float(float %unrelated, float %x, float %y) nounwind {
+; CHECK-LABEL: test_negative_float:
+; CHECK: # %bb.0:
+; CHECK-NEXT: movaps %xmm2, %xmm3
+; CHECK-NEXT: subss %xmm1, %xmm3
+; CHECK-NEXT: cmpltss %xmm2, %xmm0
+; CHECK-NEXT: andnps %xmm3, %xmm0
+; CHECK-NEXT: cmpltss %xmm1, %xmm2
+; CHECK-NEXT: andnps %xmm0, %xmm2
+; CHECK-NEXT: movaps %xmm2, %xmm0
+; CHECK-NEXT: retq
+ %cmp.nomatch1 = fcmp olt float %y, %x
+ %cmp.nomatch2 = fcmp ogt float %y, %unrelated
+ %or.cond = or i1 %cmp.nomatch1, %cmp.nomatch2
+ %sub.noreuse = fsub float %y, %x
+ %res = select i1 %or.cond, float 0.0, float %sub.noreuse
+ ret float %res
+}
+
+; Negative test: if both operands match a sub, do not reorder them.
+define i64 @test_prefer_right_negative(i64 %x, i64 %y, i64 %z) nounwind {
+; CHECK-LABEL: test_prefer_right_negative:
+; CHECK: # %bb.0:
+; CHECK-NEXT: movq %rdx, %rcx
+; CHECK-NEXT: subq %rdi, %rcx
+; CHECK-NEXT: movq %rdx, %rax
+; CHECK-NEXT: subq %rsi, %rax
+; CHECK-NEXT: ccmpaeq {dfv=} %rdi, %rdx
+; CHECK-NEXT: cmovaq %rcx, %rax
+; CHECK-NEXT: retq
+ %cmp.match1 = icmp ult i64 %z, %y
+ %cmp.match2 = icmp ugt i64 %z, %x
+ %or.cond = or i1 %cmp.match1, %cmp.match2
+ %sub.reuse1 = sub nuw i64 %z, %y
+ %sub.reuse2 = sub nuw i64 %z, %x
+ %res = select i1 %or.cond, i64 %sub.reuse2, i64 %sub.reuse1
+ ret i64 %res
+}
>From 692e236ff5279d5db1ef7780ff8341bf77f05a2b Mon Sep 17 00:00:00 2001
From: Phoebe Wang <phoebe.wang at intel.com>
Date: Thu, 23 Jul 2026 15:00:27 +0800
Subject: [PATCH 2/2] [X86][CCMP] Reorder Comparison Trees to Facilitate CSE
This patch ports AArch64's CMP CSE optimization #168064
Assisted-by: Claude Opus 4.8
---
llvm/lib/Target/X86/X86ISelLowering.cpp | 52 +++++++++++++++++--------
llvm/test/CodeGen/X86/apx/ccmp-cse.ll | 23 +++++------
2 files changed, 44 insertions(+), 31 deletions(-)
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 76cc421c36ed8..32002b5ac877b 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -25516,19 +25516,29 @@ static SDValue LowerSELECTWithCmpZero(SDValue CmpVal, SDValue LHS, SDValue RHS,
/// inverting the conditions on the SETCC leaves.
/// \param MustBeFirst Set to true if this sub-tree needs to be negated but
/// cannot be negated naturally, so it must be emitted first.
+/// \param PreferFirst Set to true if processing this sub-tree first may result
+/// in more efficient code (e.g. because the flags of a
+/// corresponding SUB node can be reused).
/// \param WillNegate True when the result of this sub-expression must be
/// negated (i.e. the outer expression is an OR).
-static bool canEmitConjunctionForCCMP(SDValue Val, bool &CanNegate,
- bool &MustBeFirst, bool WillNegate,
+static bool canEmitConjunctionForCCMP(SelectionDAG &DAG, SDValue Val,
+ bool &CanNegate, bool &MustBeFirst,
+ bool &PreferFirst, bool WillNegate,
unsigned Depth = 0) {
if (!Val.hasOneUse())
return false;
unsigned Opcode = Val.getOpcode();
if (Opcode == ISD::SETCC) {
- if (!Val.getOperand(0).getSimpleValueType().isInteger())
+ EVT VT = Val.getOperand(0).getValueType();
+ if (!VT.isInteger())
return false;
CanNegate = true;
MustBeFirst = false;
+ // Designate this operation as a preferred first operation if the flags of a
+ // corresponding SUB node can be reused. The root comparison is emitted as a
+ // plain CMP, which can share EFLAGS with an existing SUB; a CCMP cannot.
+ PreferFirst = DAG.doesNodeExist(ISD::SUB, DAG.getVTList(VT),
+ {Val.getOperand(0), Val.getOperand(1)});
return true;
}
// Protect against exponential runtime and stack overflow.
@@ -25538,13 +25548,13 @@ static bool canEmitConjunctionForCCMP(SDValue Val, bool &CanNegate,
bool IsOR = Opcode == ISD::OR;
SDValue O0 = Val.getOperand(0);
SDValue O1 = Val.getOperand(1);
- bool CanNegateL, MustBeFirstL;
- if (!canEmitConjunctionForCCMP(O0, CanNegateL, MustBeFirstL, IsOR,
- Depth + 1))
+ bool CanNegateL, MustBeFirstL, PreferFirstL;
+ if (!canEmitConjunctionForCCMP(DAG, O0, CanNegateL, MustBeFirstL,
+ PreferFirstL, IsOR, Depth + 1))
return false;
- bool CanNegateR, MustBeFirstR;
- if (!canEmitConjunctionForCCMP(O1, CanNegateR, MustBeFirstR, IsOR,
- Depth + 1))
+ bool CanNegateR, MustBeFirstR, PreferFirstR;
+ if (!canEmitConjunctionForCCMP(DAG, O1, CanNegateR, MustBeFirstR,
+ PreferFirstR, IsOR, Depth + 1))
return false;
if (MustBeFirstL && MustBeFirstR)
@@ -25565,6 +25575,7 @@ static bool canEmitConjunctionForCCMP(SDValue Val, bool &CanNegate,
CanNegate = false;
MustBeFirst = MustBeFirstL || MustBeFirstR;
}
+ PreferFirst = PreferFirstL || PreferFirstR;
return true;
}
return false;
@@ -25618,19 +25629,25 @@ static SDValue emitConjunctionForCCMPRec(SDValue Val, X86::CondCode &OutCC,
bool IsOR = Opcode == ISD::OR;
SDValue LHS = Val.getOperand(0);
- bool CanNegateL, MustBeFirstL;
- bool ValidL = canEmitConjunctionForCCMP(LHS, CanNegateL, MustBeFirstL, IsOR);
+ bool CanNegateL, MustBeFirstL, PreferFirstL;
+ bool ValidL = canEmitConjunctionForCCMP(DAG, LHS, CanNegateL, MustBeFirstL,
+ PreferFirstL, IsOR);
assert(ValidL && "Valid conjunction/disjunction tree");
(void)ValidL;
SDValue RHS = Val.getOperand(1);
- bool CanNegateR, MustBeFirstR;
- bool ValidR = canEmitConjunctionForCCMP(RHS, CanNegateR, MustBeFirstR, IsOR);
+ bool CanNegateR, MustBeFirstR, PreferFirstR;
+ bool ValidR = canEmitConjunctionForCCMP(DAG, RHS, CanNegateR, MustBeFirstR,
+ PreferFirstR, IsOR);
assert(ValidR && "Valid conjunction/disjunction tree");
(void)ValidR;
- // Swap the sub-tree that must come first to the right side.
- if (MustBeFirstL) {
+ bool ShouldFirstL = PreferFirstL && !PreferFirstR && !MustBeFirstR;
+
+ // Swap the sub-tree that must or should come first to the right side. The
+ // right sub-tree is emitted first below, and the deepest right-most SETCC
+ // becomes the root plain CMP whose flags a matching SUB node can reuse.
+ if (MustBeFirstL || ShouldFirstL) {
assert(!MustBeFirstR && "Valid conjunction/disjunction tree");
std::swap(LHS, RHS);
std::swap(CanNegateL, CanNegateR);
@@ -25680,8 +25697,9 @@ static SDValue emitConjunctionForCCMPRec(SDValue Val, X86::CondCode &OutCC,
static SDValue emitConjunctionForCCMP(SDValue Val, X86::CondCode &OutCC,
SelectionDAG &DAG,
const X86Subtarget &Subtarget) {
- bool DummyCanNegate, DummyMustBeFirst;
- if (!canEmitConjunctionForCCMP(Val, DummyCanNegate, DummyMustBeFirst, false))
+ bool DummyCanNegate, DummyMustBeFirst, DummyPreferFirst;
+ if (!canEmitConjunctionForCCMP(DAG, Val, DummyCanNegate, DummyMustBeFirst,
+ DummyPreferFirst, false))
return SDValue();
return emitConjunctionForCCMPRec(Val, OutCC, /*Negate=*/false, SDValue(),
X86::COND_INVALID, DAG, Subtarget);
diff --git a/llvm/test/CodeGen/X86/apx/ccmp-cse.ll b/llvm/test/CodeGen/X86/apx/ccmp-cse.ll
index fbefbabae1440..05c22463413ea 100644
--- a/llvm/test/CodeGen/X86/apx/ccmp-cse.ll
+++ b/llvm/test/CodeGen/X86/apx/ccmp-cse.ll
@@ -11,12 +11,10 @@
define i64 @test_or_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
; CHECK-LABEL: test_or_sub_on_lhs:
; CHECK: # %bb.0:
-; CHECK-NEXT: movq %rdx, %r8
-; CHECK-NEXT: subq %rcx, %r8
; CHECK-NEXT: xorl %eax, %eax
-; CHECK-NEXT: cmpq %rsi, %rdi
-; CHECK-NEXT: ccmpaeq {dfv=} %rcx, %rdx
-; CHECK-NEXT: cmovbeq %r8, %rax
+; CHECK-NEXT: subq %rcx, %rdx
+; CHECK-NEXT: ccmpbeq {dfv=cf} %rsi, %rdi
+; CHECK-NEXT: cmovaeq %rdx, %rax
; CHECK-NEXT: retq
%cmp.match = icmp ugt i64 %c, %d
%cmp.other = icmp ult i64 %a, %b
@@ -30,12 +28,10 @@ define i64 @test_or_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
define i64 @test_and_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
; CHECK-LABEL: test_and_sub_on_lhs:
; CHECK: # %bb.0:
-; CHECK-NEXT: movq %rdx, %r8
-; CHECK-NEXT: subq %rcx, %r8
; CHECK-NEXT: xorl %eax, %eax
-; CHECK-NEXT: cmpq %rsi, %rdi
-; CHECK-NEXT: ccmpbq {dfv=cf} %rcx, %rdx
-; CHECK-NEXT: cmovbeq %r8, %rax
+; CHECK-NEXT: subq %rcx, %rdx
+; CHECK-NEXT: ccmpaq {dfv=} %rsi, %rdi
+; CHECK-NEXT: cmovaeq %rdx, %rax
; CHECK-NEXT: retq
%cmp.match = icmp ugt i64 %c, %d
%cmp.other = icmp ult i64 %a, %b
@@ -50,13 +46,12 @@ define i64 @test_and_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d) nounwind {
define i64 @test_two_ors_sub_on_lhs(i64 %a, i64 %b, i64 %c, i64 %d, i64 %e) nounwind {
; CHECK-LABEL: test_two_ors_sub_on_lhs:
; CHECK: # %bb.0:
+; CHECK-NEXT: xorl %eax, %eax
; CHECK-NEXT: movq %rdx, %rcx
; CHECK-NEXT: subq %r8, %rcx
-; CHECK-NEXT: xorl %eax, %eax
-; CHECK-NEXT: cmpq %rdx, %rsi
+; CHECK-NEXT: ccmpbeq {dfv=cf} %rdx, %rsi
; CHECK-NEXT: ccmpaeq {dfv=cf} %rsi, %rdi
-; CHECK-NEXT: ccmpaeq {dfv=} %r8, %rdx
-; CHECK-NEXT: cmovbeq %rcx, %rax
+; CHECK-NEXT: cmovaeq %rcx, %rax
; CHECK-NEXT: retq
%cmp.match = icmp ugt i64 %c, %e
%cmp.other1 = icmp ult i64 %a, %b
More information about the llvm-commits
mailing list