https://gcc.gnu.org/g:31c8e6c019106d3e816e1bae77c4c9a70adf6957

commit r17-1950-g31c8e6c019106d3e816e1bae77c4c9a70adf6957
Author: liuhongt <[email protected]>
Date:   Sun Jun 21 23:29:49 2026 -0700

    i386: Allow immediate cmov arm in *cmov_bt<mode> [PR125907]
    
    The "bt followed by cmov" splitter only matched when both cmov arms
    were nonimmediate_operand, so "(a & (1 << b)) ? c : imm" could not be
    folded into bt + cmov and instead emitted a sal/shr + and + cmov
    sequence.
    
    Keep the existing define_split for the two-register case unchanged, and
    add a separate define_insn_and_split for the immediate false-arm case.
    The immediate arm must be materialized into a register, and that extra
    move cannot be expressed in a combine define_split (which is limited to
    two insns), so the new pattern is a define_insn_and_split that combine
    recognizes as a single insn.  Because it is a real insn whose split sets
    the flags register, it carries a (clobber (reg:CC FLAGS_REG)), matching
    the sibling bt helpers.
    
    gcc/ChangeLog:
    
            PR target/125907
            * config/i386/i386.md (*cmov_bt<SWI248:mode>): New
            define_insn_and_split handling a const_int false arm, forced into
            a register by the splitter.  Leave the existing define_split for
            the nonimmediate case unchanged.
    
    gcc/testsuite/ChangeLog:
    
            PR target/125907
            * gcc.target/i386/bt-cmov-1.c: New test.

Diff:
---
 gcc/config/i386/i386.md                   | 35 +++++++++++++++++++++++++++++++
 gcc/testsuite/gcc.target/i386/bt-cmov-1.c | 33 +++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 00bc37b1cb03..8fb70eccd249 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -20248,6 +20248,41 @@
     std::swap (operands[3], operands[4]);
 })
 
+;; The same as above, but with operands[4] as const_int_operand.  A combine
+;; define_split cannot express the extra move needed to materialize the
+;; immediate into a register (it is limited to two insns), so use a
+;; define_insn_and_split that combine recognizes as a single insn.
+(define_insn_and_split "*cmov_bt<SWI248:mode>"
+  [(set (match_operand:SWI248 0 "register_operand")
+       (if_then_else:SWI248
+        (match_operator 5 "bt_comparison_operator"
+         [(zero_extract:SWI48
+           (match_operand:SWI48 1 "nonimmediate_operand")
+           (const_int 1)
+           (match_operand:QI 2 "register_operand"))
+          (const_int 0)])
+        (match_operand:SWI248 3 "nonimmediate_operand")
+        (match_operand:SWI248 4 "const_int_operand")))
+   (clobber (reg:CC FLAGS_REG))]
+  "TARGET_USE_BT && TARGET_CMOVE
+   && ix86_pre_reload_split ()"
+  "#"
+  "&& 1"
+  [(set (reg:CCC FLAGS_REG)
+       (compare:CCC
+        (const_int 0)
+        (zero_extract:SWI48 (match_dup 1) (const_int 1) (match_dup 2))))
+   (set (match_dup 0)
+       (if_then_else:SWI248 (ltu (reg:CCC FLAGS_REG) (const_int 0))
+                            (match_dup 3)
+                            (match_dup 4)))]
+{
+  operands[4] = force_reg (<SWI248:MODE>mode, operands[4]);
+
+  if (GET_CODE (operands[5]) == EQ)
+    std::swap (operands[3], operands[4]);
+})
+
 ;; Help combine recognize bt followed by setc
 (define_insn_and_split "*bt<mode>_setcqi"
   [(set (subreg:SWI48 (match_operand:QI 0 "register_operand") 0)
diff --git a/gcc/testsuite/gcc.target/i386/bt-cmov-1.c 
b/gcc/testsuite/gcc.target/i386/bt-cmov-1.c
new file mode 100644
index 000000000000..07234c2a9a02
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/bt-cmov-1.c
@@ -0,0 +1,33 @@
+/* PR target/125907 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic" } */
+
+/* Verify that "(a & (1 << b)) ? c : imm" and the equivalent
+   "((a >> b) & 1) ? c : imm" forms use bt + cmov even when one of the
+   cmov arms is an immediate.  The immediate arm is materialized into a
+   register by the *cmov_bt<mode> splitter.  */
+
+unsigned f0 (unsigned a, unsigned b, unsigned c)
+{
+  return (a & (1 << b)) ? c : 2;
+}
+
+unsigned f1 (unsigned a, unsigned b, unsigned c)
+{
+  return ((a >> b) & 1) ? c : 2;
+}
+
+unsigned f2 (unsigned a, unsigned b, unsigned c)
+{
+  return (a & (1 << b)) ? 2 : c;
+}
+
+unsigned f3 (unsigned a, unsigned b, unsigned c)
+{
+  return ((a >> b) & 1) ? 2 : c;
+}
+
+/* { dg-final { scan-assembler-times "bt\[l\]?\[ \\t\]+" 4 } } */
+/* { dg-final { scan-assembler-times "cmov\[n\]*c" 4 } } */
+/* { dg-final { scan-assembler-not "sal" } } */
+/* { dg-final { scan-assembler-not "shr" } } */

Reply via email to