[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