https://gcc.gnu.org/g:a6b623a8eb41dd485c8b3b9fb92c50f551a486b7
commit r17-4986-ga6b623a8eb41dd485c8b3b9fb92c50f551a486b7 Author: Kyrylo Tkachov <[email protected]> Date: Tue Sep 29 16:58:49 2026 +0200 cprop: Do not queue dead CC setters when bypassing jumps [PR127661] The testcase in the PR is a long chain of if (v == 0) c->hit (0); if (v == 1) c->hit (1); ... At -O2 almost all of its compile time goes to jump bypassing in cprop1. The GIMPLE threaders move the edge after each call only a few tests forward. bypass_block then redirects each such edge to the next conditional block, which the forward sweep of bypass_conditional_jumps visits later, so a chain of N tests gives about N*N/2 redirections. Each redirection queues a copy of the flags setter of the bypassed block on the edge. The copies are dead, because the next block sets the flags again, but reg_killed_on_edge and insert_insn_on_edge walk the whole queue of the edge at each step, and commit_edge_insertions adds about N*N/2 dead compares. In gcc.target/aarch64/pr127661.c the block of the first call ends up as: (call_insn 10 ... (call (mem:DI (symbol_ref:DI ("f")) ...) ...)) (insn 76 ... (set (reg:CC 66 cc) (compare:CC (reg/v:SI 101 [ v ]) (const_int 1)))) (insn 78 ... (set (reg:CC 66 cc) (compare:CC (reg/v:SI 101 [ v ]) (const_int 2)))) ... five more compares, with the constants 3 to 7 ... This patch copies the setter only if it is needed on entry to the new destination, that is if it has side effects, can throw, or sets a register that is live there or is not a condition-code register. Setters of other registers are always copied, because debug insns and REG_EQUAL notes, which the liveness information ignores, can refer to them. The comment of bypass_setcc_needed_p explains why the liveness information, which predates the global propagation and the redirections, is safe to use. With the patch the block above contains only the call. gcc.dg/pr127661.c and gcc.target/aarch64/pr127661-2.c check that the copy stays when the flags are live at the new destination. For the testcase at N=2048 on Grace the compile time drops by 95% and the peak memory use by 78%. For the variant with the calls inlined the compile time at N=1024 drops by 92%. Dead copies no longer stop the bypass of an outgoing edge, so the patch can add redirections, but it adds none for the testcase, and the assembly output does not change for N from 64 to 2048. Bootstrapped and tested as a series on aarch64-linux-gnu and x86_64-linux-gnu. gcc/ChangeLog: PR rtl-optimization/127661 * cprop.cc (bypass_setcc_needed_p): New function. (bypass_block): Use it to decide whether to copy SETCC onto a redirected edge. (bypass_conditional_jumps): Update comment. gcc/testsuite/ChangeLog: PR rtl-optimization/127661 * gcc.dg/pr127661.c: New test. * gcc.target/aarch64/pr127661.c: New test. * gcc.target/aarch64/pr127661-2.c: New test. * gcc.target/i386/pr127661.c: New test. Signed-off-by: Kyrylo Tkachov <[email protected]> Diff: --- gcc/cprop.cc | 42 ++++++++++++++++++++++--- gcc/testsuite/gcc.dg/pr127661.c | 44 +++++++++++++++++++++++++++ gcc/testsuite/gcc.target/aarch64/pr127661-2.c | 30 ++++++++++++++++++ gcc/testsuite/gcc.target/aarch64/pr127661.c | 20 ++++++++++++ gcc/testsuite/gcc.target/i386/pr127661.c | 17 +++++++++++ 5 files changed, 149 insertions(+), 4 deletions(-) diff --git a/gcc/cprop.cc b/gcc/cprop.cc index 26c69f070ce3..7250554c75cc 100644 --- a/gcc/cprop.cc +++ b/gcc/cprop.cc @@ -1511,6 +1511,39 @@ reg_killed_on_edge (const_rtx reg, const_edge e) return false; } +/* Subroutine of bypass_block. Return true if a copy of SETCC, the insn + before the jump of a bypassed block, must be placed on an edge that has + been redirected to DEST. + + The copy is not needed if SETCC has no side effects, cannot throw, and + sets only condition-code registers that are dead on entry to DEST. + Setters of other registers are always copied, because debug insns and + REG_EQUAL notes, which the liveness information ignores, can refer to + them. The liveness information predates the global propagation and + the edge redirections, but neither can make a register set by SETCC + live on entry to DEST: hard registers are never propagated, SETCC kills + every copy from a pseudo that it sets, and a redirection omits a copy + of a setter only if its registers are dead on entry to the new + destination. */ + +static bool +bypass_setcc_needed_p (rtx_insn *setcc, basic_block dest) +{ + if (side_effects_p (PATTERN (setcc)) || !insn_nothrow_p (setcc)) + return true; + + /* DEST existed when the DF information was computed. */ + gcc_checking_assert (dest->index < bypass_last_basic_block); + bitmap live = df_get_live_in (dest); + df_ref def; + FOR_EACH_INSN_DEF (def, setcc) + if (GET_MODE_CLASS (GET_MODE (*DF_REF_REAL_LOC (def))) != MODE_CC + || REGNO_REG_SET_P (live, DF_REF_REGNO (def))) + return true; + + return false; +} + /* Subroutine of bypass_conditional_jumps that attempts to bypass the given basic block BB which has more than one predecessor. If not NULL, SETCC is the first instruction of BB, which is immediately followed by JUMP_INSN @@ -1661,8 +1694,9 @@ bypass_block (basic_block bb, rtx_insn *setcc, rtx_insn *jump) { redirect_edge_and_branch_force (e, dest); - /* Copy the register setter to the redirected edge. */ - if (setcc) + /* Copy the register setter to the redirected edge if it is + needed on entry to DEST. */ + if (setcc && bypass_setcc_needed_p (setcc, dest)) { rtx pat = PATTERN (setcc); insert_insn_on_edge (copy_insn (pat), e); @@ -1750,8 +1784,8 @@ bypass_conditional_jumps (void) } } - /* If we bypassed any register setting insns, we inserted a - copy on the redirected edge. These need to be committed. */ + /* If we bypassed any register setting insns, we may have inserted + copies of them on the redirected edges. These need to be committed. */ if (changed) commit_edge_insertions (); diff --git a/gcc/testsuite/gcc.dg/pr127661.c b/gcc/testsuite/gcc.dg/pr127661.c new file mode 100644 index 000000000000..017d65ffb33a --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr127661.c @@ -0,0 +1,44 @@ +/* PR rtl-optimization/127661 */ +/* { dg-do run } */ +/* { dg-options "-O2 -fno-thread-jumps" } */ + +int r; + +__attribute__ ((noipa)) void a (void) { r |= 1; } +__attribute__ ((noipa)) void b (void) { r |= 2; } +__attribute__ ((noipa)) void c (void) { r |= 4; } +__attribute__ ((noipa)) void d (void) { r |= 8; } + +/* Some targets use one compare for both tests of X. The edge out of the + call to A then skips that compare but still needs its result, so jump + bypassing must put a copy of the compare on the edge. S > 100 leaves + flags that give the wrong result for X == 5 if the copy is missing. */ + +__attribute__ ((noipa)) void +foo (int x, int s) +{ + if (s > 100) + { + x = 5; + a (); + } + if (x == 13) + b (); + else if (x > 13) + c (); + else + d (); +} + +int +main () +{ + foo (20, 200); + if (r != 9) + __builtin_abort (); + r = 0; + foo (20, 0); + if (r != 4) + __builtin_abort (); + return 0; +} diff --git a/gcc/testsuite/gcc.target/aarch64/pr127661-2.c b/gcc/testsuite/gcc.target/aarch64/pr127661-2.c new file mode 100644 index 000000000000..d5f413869f79 --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/pr127661-2.c @@ -0,0 +1,30 @@ +/* PR rtl-optimization/127661 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-thread-jumps -fdump-rtl-cprop1" } */ + +#pragma GCC target "+nocmpbr" + +void a (void), b (void), c (void), d (void); + +/* Both tests of X use the flags of one compare. Jump bypassing moves the + edge out of the call to A past that compare to the second test, so it + must put a copy of the compare on that edge. */ + +void +foo (int x, int s) +{ + if (s) + { + x = 5; + a (); + } + if (x == 13) + b (); + else if (x > 13) + c (); + else + d (); +} + +/* { dg-final { scan-rtl-dump "JUMP-BYPASS" "cprop1" } } */ +/* { dg-final { scan-rtl-dump-times "scanning new insn" 1 "cprop1" } } */ diff --git a/gcc/testsuite/gcc.target/aarch64/pr127661.c b/gcc/testsuite/gcc.target/aarch64/pr127661.c new file mode 100644 index 000000000000..66a120722833 --- /dev/null +++ b/gcc/testsuite/gcc.target/aarch64/pr127661.c @@ -0,0 +1,20 @@ +/* PR rtl-optimization/127661 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-thread-jumps -fdump-rtl-cprop1" } */ + +/* Use a CMP and a B.cond for each test of V. */ +#pragma GCC target "+nocmpbr" + +void f (int); + +#define T(n) if (v == n) f (n); + +void +foo (int v) +{ + T (0) T (1) T (2) T (3) T (4) T (5) T (6) T (7) +} + +/* Jump bypassing must not add copies of the compares that it skips. */ +/* { dg-final { scan-rtl-dump "JUMP-BYPASS" "cprop1" } } */ +/* { dg-final { scan-rtl-dump-times {\(compare:} 8 "cprop1" } } */ diff --git a/gcc/testsuite/gcc.target/i386/pr127661.c b/gcc/testsuite/gcc.target/i386/pr127661.c new file mode 100644 index 000000000000..2e4ae2871a31 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/pr127661.c @@ -0,0 +1,17 @@ +/* PR rtl-optimization/127661 */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-thread-jumps -fdump-rtl-cprop1" } */ + +void f (int); + +#define T(n) if (v == n) f (n); + +void +foo (int v) +{ + T (0) T (1) T (2) T (3) T (4) T (5) T (6) T (7) +} + +/* Jump bypassing must not add copies of the compares that it skips. */ +/* { dg-final { scan-rtl-dump "JUMP-BYPASS" "cprop1" } } */ +/* { dg-final { scan-rtl-dump-times {\(compare:} 8 "cprop1" } } */
