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

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 28 08:54:04 PDT 2026


Author: Evgenii Kudriashov
Date: 2026-09-28T17:53:56+02:00
New Revision: d9495236da19d3f11d6e00ad13f15281e879c61f

URL: https://github.com/llvm/llvm-project/commit/d9495236da19d3f11d6e00ad13f15281e879c61f
DIFF: https://github.com/llvm/llvm-project/commit/d9495236da19d3f11d6e00ad13f15281e879c61f.diff

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

Tests for #217682

Assisted-by: Claude Code

Added: 
    llvm/test/CodeGen/X86/apx/ccmp-flag-source.ll

Modified: 
    

Removed: 
    


################################################################################
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