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
