[llvm] [X86] Add tests for CCMP chain leaves with a folded flag source (PR #226797)

Evgenii Kudriashov via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 27 13:07:34 PDT 2026


https://github.com/e-kud updated https://github.com/llvm/llvm-project/pull/226797

>From 15caeb9d16784be3372406fc42fb75037abf2175 Mon Sep 17 00:00:00 2001
From: Evgenii Kudriashov <evgenii.kudriashov at intel.com>
Date: Thu, 17 Sep 2026 09:46:09 -0700
Subject: [PATCH] [X86] Add tests for CCMP chain leaves with a folded flag
 source

EmitCmp does not always emit a comparison: where it can it reuses the EFLAGS of
an instruction that is computed anyway. (X | Y) == 0 uses the OR's own flags,
(X & Y) == 0 an AND's, (0 - X) == Y is rewritten into (X + Y) == 0 and uses an
ADD's, and an equality can reuse an already existing XOR.

Assisted-by: Claude Code
---
 llvm/test/CodeGen/X86/apx/ccmp-flag-source.ll | 177 ++++++++++++++++++
 1 file changed, 177 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/apx/ccmp-flag-source.ll

diff --git a/llvm/test/CodeGen/X86/apx/ccmp-flag-source.ll b/llvm/test/CodeGen/X86/apx/ccmp-flag-source.ll
new file mode 100644
index 0000000000000..21e8d765ac7aa
--- /dev/null
+++ b/llvm/test/CodeGen/X86/apx/ccmp-flag-source.ll
@@ -0,0 +1,177 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+ccmp | FileCheck %s
+
+; EmitCmp does not always emit a comparison: where it can it reuses the EFLAGS
+; of an instruction that is computed anyway. (X | Y) == 0 uses the OR's own
+; flags, (X & Y) == 0 an AND's, (0 - X) == Y is rewritten into (X + Y) == 0 and
+; uses an ADD's, and an equality can reuse an already existing XOR.
+;
+; A non-root leaf of a CCMP chain rebuilds a conditional comparison out of that
+; flag source's operands. Only a subtract (CCMP) and an AND (CTEST) can be made
+; conditional, so a flag source that computes something else must not have its
+; operands reused.
+
+; A comparison of an OR with zero, in a non-root slot of the chain.
+;
+; FIXME: Miscompiled. The OR's result is never tested, the CCMP compares x with
+; y instead.
+define i32 @or_vs_zero_interior(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v) {
+; CHECK-LABEL: or_vs_zero_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=} %esi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovel %r8d, %eax
+; CHECK-NEXT:    retq
+  %or   = or i32 %x, %y
+  %c1   = icmp eq i32 %or, 0
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; Same, with a flag source that has a second use and a condition that reads SF
+; rather than ZF.
+;
+; FIXME: Miscompiled. The ADD's result is never tested, the CCMP compares x with
+; y instead.
+define i32 @stored_add_vs_zero_interior(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v, ptr %s) {
+; CHECK-LABEL: stored_add_vs_zero_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    # kill: def $esi killed $esi def $rsi
+; CHECK-NEXT:    # kill: def $edi killed $edi def $rdi
+; CHECK-NEXT:    leal (%rdi,%rsi), %eax
+; CHECK-NEXT:    movl %eax, (%r9)
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=sf} %esi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovnsl %r8d, %eax
+; CHECK-NEXT:    retq
+  %add  = add i32 %x, %y
+  store i32 %add, ptr %s
+  %c1   = icmp sgt i32 %add, -1
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; An AND compared with zero is a test of the AND's operands, so the AND itself
+; is not needed.
+define i32 @and_vs_zero_interior(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v) {
+; CHECK-LABEL: and_vs_zero_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    andl %esi, %edi
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=} $0, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovel %r8d, %eax
+; CHECK-NEXT:    retq
+  %and  = and i32 %x, %y
+  %c1   = icmp eq i32 %and, 0
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; An AND whose result is used elsewhere, so EmitCmp reuses the AND's own EFLAGS
+; rather than emitting a test.
+;
+; FIXME: Miscompiled. The CCMP subtracts x and y instead of testing them.
+define i32 @stored_and_vs_zero_interior(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v, ptr %s) {
+; CHECK-LABEL: stored_and_vs_zero_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl %edi, %eax
+; CHECK-NEXT:    andl %esi, %eax
+; CHECK-NEXT:    movl %eax, (%r9)
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=} %esi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovel %r8d, %eax
+; CHECK-NEXT:    retq
+  %and  = and i32 %x, %y
+  store i32 %and, ptr %s
+  %c1   = icmp eq i32 %and, 0
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; An equality comparison is a test of the operands' XOR, so an already live XOR
+; can be reused instead of keeping both operands live.
+define i32 @eq_reuses_live_xor_interior(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v, ptr %s) {
+; CHECK-LABEL: eq_reuses_live_xor_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl %edi, %eax
+; CHECK-NEXT:    xorl %esi, %eax
+; CHECK-NEXT:    movl %eax, (%r9)
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=} %esi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovel %r8d, %eax
+; CHECK-NEXT:    retq
+  %xor  = xor i32 %x, %y
+  store i32 %xor, ptr %s
+  %c1   = icmp eq i32 %x, %y
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; Without a live XOR the comparison stays a comparison of the two operands.
+define i32 @eq_without_live_xor_interior(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v) {
+; CHECK-LABEL: eq_without_live_xor_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=} %esi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovel %r8d, %eax
+; CHECK-NEXT:    retq
+  %c1   = icmp eq i32 %x, %y
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; (0 - b) == c is rewritten by EmitCmp into (b + c) == 0.
+;
+; FIXME: Miscompiled. Neither the negation nor an addition is tested, the CCMP
+; compares b with c instead.
+define i32 @neg_eq_interior(i32 %b, i32 %c, i32 %p, i32 %q, i32 %v) {
+; CHECK-LABEL: neg_eq_interior:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cmpl %ecx, %edx
+; CHECK-NEXT:    ccmpll {dfv=} %esi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovel %r8d, %eax
+; CHECK-NEXT:    retq
+  %neg  = sub i32 0, %b
+  %c1   = icmp eq i32 %neg, %c
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c1, %c2
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}
+
+; The root of the chain does not have this problem: unconditional flags are
+; exactly what it needs, so the OR provides the flags of the first comparison.
+define i32 @or_vs_zero_root(i32 %x, i32 %y, i32 %p, i32 %q, i32 %v) {
+; CHECK-LABEL: or_vs_zero_root:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    orl %esi, %edi
+; CHECK-NEXT:    ccmpel {dfv=} %ecx, %edx
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovll %r8d, %eax
+; CHECK-NEXT:    retq
+  %or   = or i32 %x, %y
+  %c1   = icmp eq i32 %or, 0
+  %c2   = icmp slt i32 %p, %q
+  %cond = and i1 %c2, %c1
+  %sel  = select i1 %cond, i32 %v, i32 -1
+  ret i32 %sel
+}



More information about the llvm-commits mailing list