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.

NOTE:the testcase in the PR still can't be optimized into bt + cmov since
uncprop1  substitutes the constant 0 in the PHI with _3 (the and result,
known == 0 on that edge), which keeps _3 live and blocks the bt form.
See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125907#c4

Bootstrapped and regtested on x86_64-pc-linux-gnu{-m32,}.
Ok for trunk?

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.
---
 gcc/config/i386/i386.md                   | 35 +++++++++++++++++++++++
 gcc/testsuite/gcc.target/i386/bt-cmov-1.c | 33 +++++++++++++++++++++
 2 files changed, 68 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/i386/bt-cmov-1.c

diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 8db5d3b0152..3a8fff294f1 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -20247,6 +20247,41 @@ (define_split
     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 00000000000..07234c2a9a0
--- /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" } } */
-- 
2.34.1

Reply via email to