[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