From b56c6b62395d2cbeb2ad73b4d1fbd69eef963335 Mon Sep 17 00:00:00 2001 From: Richard Biener Date: Tue, 17 May 2022 17:18:06 +0200 Subject: [PATCH 01/10] [Backport] Do not clear bb->aux in duplicate_loop_body_to_header_edge Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=941efd87e22b1b88e3488b8e681d14d0d66e45ac duplicate_loop_body_to_header_edge clears bb->aux which is not wanted by a new use in loop unswitching. The clearing was introduced with r0-69110-g6580ee7781f903 and it seems accidentially so. 2022-05-17 Richard Biener * cfgloopmanip.cc (duplicate_loop_body_to_header_edge): Do not clear bb->aux of the copied blocks. (cherry picked from commit 941efd87e22b1b88e3488b8e681d14d0d66e45ac) Signed-off-by: Zhongjie Guo (cherry picked from commit bf5a9bd4886dd197880e65dc7af567b0d412e13c) --- gcc/cfgloopmanip.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/gcc/cfgloopmanip.cc b/gcc/cfgloopmanip.cc index b4357c03e86..7736e3ec709 100644 --- a/gcc/cfgloopmanip.cc +++ b/gcc/cfgloopmanip.cc @@ -1351,7 +1351,6 @@ duplicate_loop_body_to_header_edge (class loop *loop, edge e, unsigned j; bb = bbs[i]; - bb->aux = 0; auto_vec dom_bbs = get_dominated_by (CDI_DOMINATORS, bb); FOR_EACH_VEC_ELT (dom_bbs, j, dominated) -- Gitee From 5f1201c24e0bf46273cddb9e136f5fab17eddec4 Mon Sep 17 00:00:00 2001 From: Martin Liska Date: Mon, 22 Nov 2021 13:54:20 +0100 Subject: [PATCH 02/10] [Backport] Add GIMPLE switch support to loop unswitching Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=a1c9f779f75283427316b5c670c1e01ff8ce9ced This patch adds support to unswitch loops with switch statements based on invariant index. It furthermore reworks the cost model to allow an overall budget of statements to be created per original loop by all unswitching opportunities in the loop. Compared to the original all unswitching opportunities in a loop are pre-evaluated before the first transform which will allow future changes to select the most profitable candidates first. To efficiently support switch statements the pass now uses ranger to simplify switch statements and conditions in loop copies based on ranges extracted from the recorded set of predicates unswitched. gcc/ChangeLog: * dbgcnt.def (DEBUG_COUNTER): Add loop_unswitch counter. * params.opt (max-unswitch-level): Remove. * doc/invoke.texi (max-unswitch-level): Likewise. * tree-cfg.cc (gimple_lv_add_condition_to_bb): Support not gimplified expressions. * tree-ssa-loop-unswitch.cc (struct unswitch_predicate): New. (tree_may_unswitch_on): Rename to ... (find_unswitching_predicates_for_bb): ... this and handle switch statements. (get_predicates_for_bb): Likewise. (set_predicates_for_bb): Likewise. (init_loop_unswitch_info): Likewise. (tree_ssa_unswitch_loops): Prepare stuff before calling tree_unswitch_single_loop. (tree_unswitch_single_loop): Rework the function using pre-computed predicates and with a per original loop cost model. (merge_last): New. (add_predicate_to_path): Likewise. (find_range_for_lhs): Likewise. (simplify_using_entry_checks): Rename to ... (evaluate_control_stmt_using_entry_checks): ... this, handle switch statements and improve simplifications using ranger. (simplify_loop_version): Rework using evaluate_control_stmt_using_entry_checks. (evaluate_bbs): New. (evaluate_loop_insns_for_predicate): Likewise. (tree_unswitch_loop): Adjust to allow switch statements and pass in the edge to unswitch. (clean_up_after_unswitching): New. (pass_tree_unswitch::execute): Pass down fun. gcc/testsuite/ChangeLog: * gcc.dg/loop-unswitch-7.c: New test. * gcc.dg/loop-unswitch-8.c: New test. * gcc.dg/loop-unswitch-9.c: New test. * gcc.dg/loop-unswitch-10.c: New test. * gcc.dg/loop-unswitch-11.c: New test. * gcc.dg/loop-unswitch-12.c: New test. * gcc.dg/loop-unswitch-13.c: New test. * gcc.dg/loop-unswitch-14.c: New test. * gcc.dg/loop-unswitch-15.c: New test. * gcc.dg/loop-unswitch-16.c: New test. * gcc.dg/loop-unswitch-17.c: New test. * gcc.dg/torture/20220518-1.c: New test. * gcc.dg/torture/20220518-2.c: New test. * gcc.dg/torture/20220525-1.c: New test. * gcc.dg/alias-10.c: Adjust. * gcc.dg/tree-ssa/loop-6.c: Likewise. * gcc.dg/loop-unswitch-1.c: Likewise. Co-authored-by: Richard Biener (cherry picked from commit a1c9f779f75283427316b5c670c1e01ff8ce9ced) Signed-off-by: Zhongjie Guo (cherry picked from commit 4c367e138f2bfb67652b38dd24ea07b4def62436) --- gcc/dbgcnt.def | 1 + gcc/doc/invoke.texi | 3 - gcc/params.opt | 4 - gcc/testsuite/gcc.dg/alias-10.c | 2 +- gcc/testsuite/gcc.dg/loop-unswitch-1.c | 2 +- gcc/testsuite/gcc.dg/loop-unswitch-10.c | 56 ++ gcc/testsuite/gcc.dg/loop-unswitch-11.c | 45 + gcc/testsuite/gcc.dg/loop-unswitch-12.c | 28 + gcc/testsuite/gcc.dg/loop-unswitch-13.c | 35 + gcc/testsuite/gcc.dg/loop-unswitch-14.c | 60 ++ gcc/testsuite/gcc.dg/loop-unswitch-15.c | 15 + gcc/testsuite/gcc.dg/loop-unswitch-16.c | 22 + gcc/testsuite/gcc.dg/loop-unswitch-17.c | 24 + gcc/testsuite/gcc.dg/loop-unswitch-7.c | 28 + gcc/testsuite/gcc.dg/loop-unswitch-8.c | 31 + gcc/testsuite/gcc.dg/loop-unswitch-9.c | 27 + gcc/testsuite/gcc.dg/torture/20220518-1.c | 39 + gcc/testsuite/gcc.dg/torture/20220518-2.c | 14 + gcc/testsuite/gcc.dg/torture/20220525-1.c | 33 + gcc/testsuite/gcc.dg/tree-ssa/loop-6.c | 2 +- gcc/tree-cfg.cc | 7 +- gcc/tree-ssa-loop-unswitch.cc | 1062 ++++++++++++++++----- 22 files changed, 1288 insertions(+), 252 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-10.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-11.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-12.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-13.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-14.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-15.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-16.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-17.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-7.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-8.c create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-9.c create mode 100644 gcc/testsuite/gcc.dg/torture/20220518-1.c create mode 100644 gcc/testsuite/gcc.dg/torture/20220518-2.c create mode 100644 gcc/testsuite/gcc.dg/torture/20220525-1.c diff --git a/gcc/dbgcnt.def b/gcc/dbgcnt.def index 3aa18cd0cf6..365d5cbc6b8 100644 --- a/gcc/dbgcnt.def +++ b/gcc/dbgcnt.def @@ -187,6 +187,7 @@ DEBUG_COUNTER (ira_move) DEBUG_COUNTER (ivopts_loop) DEBUG_COUNTER (lim) DEBUG_COUNTER (local_alloc_for_sched) +DEBUG_COUNTER (loop_unswitch) DEBUG_COUNTER (match) DEBUG_COUNTER (merged_ipa_icf) DEBUG_COUNTER (phiopt_edge_range) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index baf066400da..3bc479a8185 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -14166,9 +14166,6 @@ The maximum depth of a loop nest suitable for complete peeling. @item max-unswitch-insns The maximum number of insns of an unswitched loop. -@item max-unswitch-level -The maximum number of branches unswitched in a single loop. - @item lim-expensive The minimum cost of an expensive expression in the loop invariant motion. diff --git a/gcc/params.opt b/gcc/params.opt index 0a287fb94cf..68e5bdbcf57 100644 --- a/gcc/params.opt +++ b/gcc/params.opt @@ -748,10 +748,6 @@ The maximum number of instructions to consider to unroll in a loop. Common Joined UInteger Var(param_max_unswitch_insns) Init(50) Param Optimization The maximum number of insns of an unswitched loop. --param=max-unswitch-level= -Common Joined UInteger Var(param_max_unswitch_level) Init(3) Param Optimization -The maximum number of unswitchings in a single loop. - -param=max-variable-expansions-in-unroller= Common Joined UInteger Var(param_max_variable_expansions) Init(1) Param Optimization If -fvariable-expansion-in-unroller is used, the maximum number of times that an individual variable will be expanded during loop unrolling. diff --git a/gcc/testsuite/gcc.dg/alias-10.c b/gcc/testsuite/gcc.dg/alias-10.c index 95d8b196cae..8472d30a6ae 100644 --- a/gcc/testsuite/gcc.dg/alias-10.c +++ b/gcc/testsuite/gcc.dg/alias-10.c @@ -28,4 +28,4 @@ void foo (bitmap head, bitmap_element *elt) } -/* { dg-final { scan-tree-dump-times "Unswitching" 1 "unswitch"} } */ +/* { dg-final { scan-tree-dump-times "unswitching" 1 "unswitch"} } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-1.c b/gcc/testsuite/gcc.dg/loop-unswitch-1.c index f9d628df510..196cb64735e 100644 --- a/gcc/testsuite/gcc.dg/loop-unswitch-1.c +++ b/gcc/testsuite/gcc.dg/loop-unswitch-1.c @@ -33,4 +33,4 @@ parse_tag: ; } /* Test that we actually unswitched something. */ -/* { dg-final { scan-tree-dump "Unswitching loop" "unswitch" } } */ +/* { dg-final { scan-tree-dump "unswitching loop" "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-10.c b/gcc/testsuite/gcc.dg/loop-unswitch-10.c new file mode 100644 index 00000000000..5e4f16e2935 --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-10.c @@ -0,0 +1,56 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +int +__attribute__((noipa)) +foo(double *a, double *b, double *c, double *d, double *r, int size, int order) +{ + for (int i = 0; i < size; i++) + { + double tmp, tmp2; + + switch(order) + { + case 0: + tmp = -8 * a[i]; + tmp2 = 2 * b[i]; + break; + case 1: + tmp = 3 * a[i] - 2 * b[i]; + tmp2 = 5 * b[i] - 2 * c[i]; + break; + case 2: + tmp = 9 * a[i] + 2 * b[i] + c[i]; + tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i]; + break; + case 3: + tmp = 3 * a[i] + 2 * b[i] - c[i]; + tmp2 = b[i] - 2 * c[i] + 8 * d[i]; + break; + defaut: + __builtin_unreachable (); + } + + double x = 3 * tmp + d[i] + tmp; + double y = 3.4f * tmp + d[i] + tmp2; + r[i] = x + y; + } + + return 0; +} + +#define N 16 * 1024 +double aa[N], bb[N], cc[N], dd[N], rr[N]; + +int main() +{ + for (int i = 0; i < 100 * 1000; i++) + foo (aa, bb, cc, dd, rr, N, i % 4); +} + + +/* Test that we actually unswitched something. */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 0" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 1" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 2" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 3" 1 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-11.c b/gcc/testsuite/gcc.dg/loop-unswitch-11.c new file mode 100644 index 00000000000..04da28c0762 --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-11.c @@ -0,0 +1,45 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +int +foo(double *a, double *b, double *c, double *d, double *r, int size, int order) +{ + for (int i = 0; i < size; i++) + { + double tmp, tmp2; + + switch(order) + { + case 5 ... 6: + case 9: + tmp = -8 * a[i]; + tmp2 = 2 * b[i]; + break; + case 11: + tmp = 3 * a[i] - 2 * b[i]; + tmp2 = 5 * b[i] - 2 * c[i]; + break; + case 22: + tmp = 9 * a[i] + 2 * b[i] + c[i]; + tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i]; + break; + case 33: + tmp = 3 * a[i] + 2 * b[i] - c[i]; + tmp2 = b[i] - 2 * c[i] + 8 * d[i]; + break; + defaut: + __builtin_unreachable (); + } + + double x = 3 * tmp + d[i] + tmp; + double y = 3.4f * tmp + d[i] + tmp2; + r[i] = x + y; + } + + return 0; +} + +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* \\+ 4294967291.*order.* == 9" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 1" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 2" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 3" 1 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-12.c b/gcc/testsuite/gcc.dg/loop-unswitch-12.c new file mode 100644 index 00000000000..052c456846c --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-12.c @@ -0,0 +1,28 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +int +foo(double *a, double *b, double *c, double *d, double *r, int size, int order) +{ + for (int i = 0; i < size; i++) + { + double tmp; + + if (order == 1) + tmp = -8 * a[i]; + else + tmp = -4 * b[i]; + + double x = 3 * tmp + d[i] + tmp; + + if (order == 1) + x += 2; + + double y = 3.4f * tmp + d[i]; + r[i] = x + y; + } + + return 0; +} + +/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order.* == 1" 1 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-13.c b/gcc/testsuite/gcc.dg/loop-unswitch-13.c new file mode 100644 index 00000000000..d09c4aabc4e --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-13.c @@ -0,0 +1,35 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fno-thread-jumps -fdump-tree-unswitch-optimized" } */ + +int +foo(double *a, double *b, double *c, double *d, double *r, int size, unsigned order) +{ + for (int i = 0; i < size; i++) + { + double tmp; + + switch (order) + { + case 0 ... 4: + tmp = -8 * a[i]; + break; + default: + tmp = -4 * b[i]; + break; + } + + double x = 3 * tmp + d[i] + tmp; + + /* This and the case 0 ... 4 condition should only be unswitched once + since they are mutually excluded. */ + if (order >= 5) + x += 2; + + double y = 3.4f * tmp + d[i]; + r[i] = x + y; + } + + return 0; +} + +/* { dg-final { scan-tree-dump-times "unswitching loop . on .\[^\n\r\]*. with condition" 1 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-14.c b/gcc/testsuite/gcc.dg/loop-unswitch-14.c new file mode 100644 index 00000000000..129805eb918 --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-14.c @@ -0,0 +1,60 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized --param=max-unswitch-insns=1000" } */ + +int +__attribute__((noipa)) +foo(double *a, double *b, double *c, double *d, double *r, int size, int order) +{ + for (int i = 0; i < size; i++) + { + double tmp, tmp2; + + if (order <= 0) + tmp = 123; + + switch(order) + { + case 0: + tmp += -8 * a[i]; + tmp2 = 2 * b[i]; + break; + case 1: + tmp = 3 * a[i] - 2 * b[i]; + tmp2 = 5 * b[i] - 2 * c[i]; + break; + case 2: + tmp = 9 * a[i] + 2 * b[i] + c[i]; + tmp2 = 4 * b[i] + 2 * c[i] + 8 * d[i]; + break; + case 3: + tmp = 3 * a[i] + 2 * b[i] - c[i]; + tmp2 = b[i] - 2 * c[i] + 8 * d[i]; + break; + defaut: + __builtin_unreachable (); + } + + double x = 3 * tmp + d[i] + tmp; + double y = 3.4f * tmp + d[i] + tmp2; + r[i] = x + y; + } + + return 0; +} + +#define N 16 * 1024 +double aa[N], bb[N], cc[N], dd[N], rr[N]; + +int main() +{ + for (int i = 0; i < 100 * 1000; i++) + foo (aa, bb, cc, dd, rr, N, i % 4); +} + + +/* Test that we actually unswitched something. */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* <= 0" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 0" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 1" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 2" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .switch. with condition: order.* == 3" 1 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-15.c b/gcc/testsuite/gcc.dg/loop-unswitch-15.c new file mode 100644 index 00000000000..87139bb3334 --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-15.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +void bar(); +void baz(); +void foo (int a, int b, int n) +{ + for (int i = 0; i < n; ++i) + if (a < b) + bar (); + else + baz (); +} + +/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition:" "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-16.c b/gcc/testsuite/gcc.dg/loop-unswitch-16.c new file mode 100644 index 00000000000..4b0b40015ac --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-16.c @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized --param max-unswitch-insns=100" } */ + +void bar (int); +void foo (int a, int b, int c, int n) +{ + for (int i = 0; i < n; ++i) + { + if (a > 5) + bar (1); + if (b < 10) + bar (2); + if (c != 5) + bar (3); + } +} + +/* Verify we can unswitch all permutations of the predicates. */ +/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition" 7 "unswitch" } } */ +/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition: a" "unswitch" } } */ +/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition: b" "unswitch" } } */ +/* { dg-final { scan-tree-dump "unswitching loop . on .if. with condition: c" "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-17.c b/gcc/testsuite/gcc.dg/loop-unswitch-17.c new file mode 100644 index 00000000000..8655e09a51c --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-17.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +int foo (int a) +{ + do + { + if (a == 1) + return 0; + switch (a) + { + case 1: + return 5; + case 2: + return 7; + case 3: + return 11; + default:; + } + } + while (1); +} + +/* { dg-final { scan-tree-dump-times "unswitching loop" 3 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-7.c b/gcc/testsuite/gcc.dg/loop-unswitch-7.c new file mode 100644 index 00000000000..db2f93096cb --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-7.c @@ -0,0 +1,28 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fno-thread-jumps -fdump-tree-unswitch-optimized" } */ + +int +foo(double *a, double *b, double *c, double *d, double *r, int size, float order) +{ + for (int i = 0; i < size; i++) + { + double tmp; + + if (order == 1.f) + tmp = -8 * a[i]; + else + tmp = -4 * b[i]; + + double x = 3 * tmp + d[i] + tmp; + + if (order == 1.f) + x += 2; + + double y = 3.4f * tmp + d[i]; + r[i] = x + y; + } + + return 0; +} + +/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order.* == 1.0e" 1 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-8.c b/gcc/testsuite/gcc.dg/loop-unswitch-8.c new file mode 100644 index 00000000000..32796e9b7f8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-8.c @@ -0,0 +1,31 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +int +foo(double *a, double *b, double *c, double *d, double *r, int size, int order) +{ + for (int i = 0; i < size; i++) + { + double tmp; + + if (order < 3) + tmp = -8 * a[i]; + else + tmp = -4 * b[i]; + + double x = 3 * tmp + d[i] + tmp; + + if (5 > order) + x += 2; + + if (order == 12345) + x *= 5; + + double y = 3.4f * tmp + d[i]; + r[i] = x + y; + } + + return 0; +} + +/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order" 3 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-9.c b/gcc/testsuite/gcc.dg/loop-unswitch-9.c new file mode 100644 index 00000000000..5e50b078bba --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-9.c @@ -0,0 +1,27 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -funswitch-loops -fdump-tree-unswitch-optimized" } */ + +int +foo(double *a, double *b, double *c, double *d, double *r, int size, int order) +{ + for (int i = 0; i < size; i++) + { + double tmp; + + if (order == 1) + tmp = -8 * a[i]; + else + { + if (order == 2) + tmp = -4 * b[i]; + else + tmp = a[i]; + } + + r[i] = 3.4f * tmp + d[i]; + } + + return 0; +} + +/* { dg-final { scan-tree-dump-times "unswitching loop . on .if. with condition: order" 2 "unswitch" } } */ diff --git a/gcc/testsuite/gcc.dg/torture/20220518-1.c b/gcc/testsuite/gcc.dg/torture/20220518-1.c new file mode 100644 index 00000000000..1822aee6151 --- /dev/null +++ b/gcc/testsuite/gcc.dg/torture/20220518-1.c @@ -0,0 +1,39 @@ +/* { dg-do compile } */ +/* { dg-additional-options "-funswitch-loops" } */ + +enum { + MOD_WVG_MASK_TEX_USE_INT, + MOD_WVG_MASK_TEX_USE_RED, + MOD_WVG_MASK_TEX_USE_BLUE, + MOD_WVG_MASK_TEX_USE_SAT, + MOD_WVG_MASK_TEX_USE_VAL, + MOD_WVG_MASK_TEX_USE_ALPHA +} foo_num; +float *foo_org_w; +int *foo_new_w; +float foo_fact; +int foo_tex_use_channel, foo_i, foo_texres_0; +void foo() +{ + for (; foo_num;) + switch (foo_tex_use_channel) { + case MOD_WVG_MASK_TEX_USE_INT: + foo_org_w[foo_i] = foo_new_w[foo_i] * foo_texres_0; + break; + case MOD_WVG_MASK_TEX_USE_RED: + foo_org_w[foo_i] = 0; + case MOD_WVG_MASK_TEX_USE_BLUE: + foo_org_w[foo_i] = foo_fact + foo_org_w[foo_i]; + break; + case MOD_WVG_MASK_TEX_USE_SAT: + foo_org_w[foo_i] = foo_fact; + break; + case MOD_WVG_MASK_TEX_USE_VAL: + foo_org_w[foo_i] = 0; + case MOD_WVG_MASK_TEX_USE_ALPHA: + foo_org_w[foo_i] = foo_fact + foo_org_w[foo_i]; + break; + default: + foo_org_w[foo_i] = foo_new_w[foo_i] * foo_texres_0; + } +} diff --git a/gcc/testsuite/gcc.dg/torture/20220518-2.c b/gcc/testsuite/gcc.dg/torture/20220518-2.c new file mode 100644 index 00000000000..af70d7f6634 --- /dev/null +++ b/gcc/testsuite/gcc.dg/torture/20220518-2.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-additional-options "-funswitch-loops" } */ + +int Get_Spline_Val_sp_0, Get_Spline_Val_k; +double Get_Spline_Val_p, Get_Spline_Val_se_0_0_0; +double *Get_Spline_Val_v; +void Get_Spline_Val() { + int i; + for (;;) + if (i > Get_Spline_Val_sp_0) + Get_Spline_Val_k = Get_Spline_Val_se_0_0_0; + else if (Get_Spline_Val_sp_0 == 1) + Get_Spline_Val_v[Get_Spline_Val_k] = Get_Spline_Val_p; +} diff --git a/gcc/testsuite/gcc.dg/torture/20220525-1.c b/gcc/testsuite/gcc.dg/torture/20220525-1.c new file mode 100644 index 00000000000..55dad3140f7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/torture/20220525-1.c @@ -0,0 +1,33 @@ +/* { dg-do compile } */ +/* { dg-additional-options "-funswitch-loops" } */ + +int LIST_1, mb_pred_b_d4x4spatial_dec_picture_l0_rFrame, + mb_pred_b_d4x4spatial_dec_picture_l1_rFrame; +typedef struct { + char ref_idx[2]; +} PicMotionParams; +PicMotionParams mb_pred_b_d4x4spatial_dec_picture_mv_info; +int get_colocated_info_4x4___trans_tmp_1, get_colocated_info_4x4_list1_0; +int get_colocated_info_4x4() +{ + int moving = + get_colocated_info_4x4_list1_0 && get_colocated_info_4x4___trans_tmp_1; + return moving; +} +void mb_pred_b_d4x4spatial_dec_picture() +{ + char k; + for (;;) + { + k = 0; + for (; k < 4; k++) + if (mb_pred_b_d4x4spatial_dec_picture_l0_rFrame + || mb_pred_b_d4x4spatial_dec_picture_l1_rFrame == 0) + { + int is_not_moving = get_colocated_info_4x4(); + if (mb_pred_b_d4x4spatial_dec_picture_l1_rFrame) + if (is_not_moving) + mb_pred_b_d4x4spatial_dec_picture_mv_info.ref_idx[LIST_1] = 1; + } + } +} diff --git a/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c b/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c index 60447601238..f9eb5c65ebe 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/loop-6.c @@ -19,7 +19,7 @@ void xxx(void) /* Loop should be unswitched. */ -/* { dg-final { scan-tree-dump-times "Unswitching loop" 1 "unswitch" } } */ +/* { dg-final { scan-tree-dump-times "unswitching loop" 1 "unswitch" } } */ /* In effect there should be exactly three conditional jumps in the final program. */ diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc index 05fc4514714..aa9f1f10cb2 100644 --- a/gcc/tree-cfg.cc +++ b/gcc/tree-cfg.cc @@ -9040,11 +9040,16 @@ gimple_lv_add_condition_to_bb (basic_block first_head ATTRIBUTE_UNUSED, edge e0; /* Build new conditional expr */ + gsi = gsi_last_bb (cond_bb); + + cond_expr = force_gimple_operand_gsi_1 (&gsi, cond_expr, + is_gimple_condexpr_for_cond, + NULL_TREE, false, + GSI_CONTINUE_LINKING); new_cond_expr = gimple_build_cond_from_tree (cond_expr, NULL_TREE, NULL_TREE); /* Add new cond in cond_bb. */ - gsi = gsi_last_bb (cond_bb); gsi_insert_after (&gsi, new_cond_expr, GSI_NEW_STMT); /* Adjust edges appropriately to connect new head with first head diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index 2927f308234..f32f1a78f00 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -38,6 +38,10 @@ along with GCC; see the file COPYING3. If not see #include "cfghooks.h" #include "tree-ssa-loop-manip.h" #include "tree-vectorizer.h" +#include "tree-pretty-print.h" +#include "gimple-range.h" +#include "dbgcnt.h" +#include "cfganal.h" /* This file implements the loop unswitching, i.e. transformation of loops like @@ -75,9 +79,137 @@ along with GCC; see the file COPYING3. If not see tree-ssa-loop-im.cc ensures that all the suitable conditions are in this shape. */ -static class loop *tree_unswitch_loop (class loop *, basic_block, tree); -static bool tree_unswitch_single_loop (class loop *, int); -static tree tree_may_unswitch_on (basic_block, class loop *); +/* Loop unswitching algorithm for innermost loops works in the following steps: + + 1) Number of instructions is estimated for each BB that belongs to a loop. + 2) Unswitching candidates are found for gcond and gswitch statements + (note that an unswitching predicate for a gswitch actually corresponds + to a non-default edge so it can contain multiple cases). + 3) The so called unswitch predicates are stored in a cache where the + gimple_uid of the last stmt in a basic-block is an index to the cache. + 4) We consider one by one the unswitching candidates and calculate BBs that + will be reachable in the unswitch version. + 5) A selected predicate is chosen and we simplify the CFG (dead edges) in + both versions of the loop. We utilize both Ranger for condition + simplification and also symbol equivalence. The folded if conditions + are replaced with true/false values, while for gswitch we mark the + corresponding edges with a pass-defined unreachable flag. + 6) Every time we unswitch a loop, we save unswitch_predicate to a vector + together with information if true or false edge was taken. Doing that + we have a so called PREDICATE_PATH that is utilized for simplification + of the cloned loop. + 7) The process is repeated until we reach a growth threshold or all + unswitching opportunities are taken. */ + +/* A tuple that holds a GENERIC condition and value range for an unswitching + predicate. */ + +struct unswitch_predicate +{ + /* CTOR for a switch edge predicate. */ + unswitch_predicate (tree cond, tree lhs_, int edge_index_, edge e, + const int_range_max& edge_range) + : condition (cond), lhs (lhs_), + true_range (edge_range), edge_index (edge_index_), switch_p (true) + { + gcc_assert (!(e->flags & (EDGE_TRUE_VALUE|EDGE_FALSE_VALUE)) + && irange::supports_type_p (TREE_TYPE (lhs))); + false_range = true_range; + if (!false_range.varying_p () + && !false_range.undefined_p ()) + false_range.invert (); + num = predicates->length (); + predicates->safe_push (this); + } + + /* CTOR for a GIMPLE condition statement. */ + unswitch_predicate (gcond *stmt) + : switch_p (false) + { + if (EDGE_SUCC (gimple_bb (stmt), 0)->flags & EDGE_TRUE_VALUE) + edge_index = 0; + else + edge_index = 1; + lhs = gimple_cond_lhs (stmt); + tree rhs = gimple_cond_rhs (stmt); + enum tree_code code = gimple_cond_code (stmt); + condition = build2 (code, boolean_type_node, lhs, rhs); + if (irange::supports_type_p (TREE_TYPE (lhs))) + { + auto range_op = range_op_handler (code, TREE_TYPE (lhs)); + int_range<2> rhs_range (TREE_TYPE (rhs)); + if (CONSTANT_CLASS_P (rhs)) + rhs_range.set (rhs); + if (!range_op->op1_range (true_range, TREE_TYPE (lhs), + int_range<2> (boolean_true_node, + boolean_true_node), rhs_range) + || !range_op->op1_range (false_range, TREE_TYPE (lhs), + int_range<2> (boolean_false_node, + boolean_false_node), + rhs_range)) + { + true_range.set_varying (TREE_TYPE (lhs)); + false_range.set_varying (TREE_TYPE (lhs)); + } + } + num = predicates->length (); + predicates->safe_push (this); + } + + /* Copy ranges for purpose of usage in predicate path. */ + + inline void + copy_merged_ranges () + { + merged_true_range = true_range; + merged_false_range = false_range; + } + + /* GENERIC unswitching expression testing LHS against CONSTANT. */ + tree condition; + + /* LHS of the expression. */ + tree lhs; + + /* Initial ranges (when the expression is true/false) for the expression. */ + int_range_max true_range = {}, false_range = {}; + + /* Modified range that is part of a predicate path. */ + int_range_max merged_true_range = {}, merged_false_range = {}; + + /* Index of the edge the predicate belongs to in the successor vector. */ + int edge_index; + + /* Whether the predicate was created from a switch statement. */ + bool switch_p; + + /* The number of the predicate in the predicates vector below. */ + unsigned num; + + /* Vector of all used predicates, used for assigning a unique id that + can be used for bitmap operations. */ + static vec *predicates; +}; + +vec *unswitch_predicate::predicates; + +/* Ranger instance used in the pass. */ +static gimple_ranger *ranger = NULL; + +/* Cache storage for unswitch_predicate belonging to a basic block. */ +static vec> *bb_predicates; + +/* The type represents a predicate path leading to a basic block. */ +typedef vec> predicate_vector; + +static class loop *tree_unswitch_loop (class loop *, edge, tree); +static bool tree_unswitch_single_loop (class loop *, dump_user_location_t, + predicate_vector &predicate_path, + unsigned loop_size, unsigned &budget, + int ignored_edge_flag, bitmap); +static void +find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, + vec &candidates); static bool tree_unswitch_outer_loop (class loop *); static edge find_loop_guard (class loop *, vec&); static bool empty_bb_without_guard_p (class loop *, basic_block, @@ -86,26 +218,154 @@ static bool used_outside_loop_p (class loop *, tree, vec&); static void hoist_guard (class loop *, edge); static bool check_exit_phi (class loop *); static tree get_vop_from_header (class loop *); +static void clean_up_after_unswitching (int); + +/* Return vector of predicates that belong to a basic block. */ + +static vec & +get_predicates_for_bb (basic_block bb) +{ + gimple *last = last_stmt (bb); + return (*bb_predicates)[last == NULL ? 0 : gimple_uid (last)]; +} + +/* Save predicates that belong to a basic block. */ + +static void +set_predicates_for_bb (basic_block bb, vec predicates) +{ + gimple_set_uid (last_stmt (bb), bb_predicates->length ()); + bb_predicates->safe_push (predicates); +} + +/* Initialize LOOP information reused during the unswitching pass. + Return total number of instructions in the loop. */ + +static unsigned +init_loop_unswitch_info (class loop *loop) +{ + unsigned total_insns = 0; + + /* Calculate instruction count. */ + basic_block *bbs = get_loop_body (loop); + for (unsigned i = 0; i < loop->num_nodes; i++) + { + unsigned insns = 0; + for (gimple_stmt_iterator gsi = gsi_start_bb (bbs[i]); !gsi_end_p (gsi); + gsi_next (&gsi)) + insns += estimate_num_insns (gsi_stmt (gsi), &eni_size_weights); + + bbs[i]->aux = (void *)(uintptr_t)insns; + total_insns += insns; + } + + /* Find all unswitching candidates. */ + for (unsigned i = 0; i != loop->num_nodes; i++) + { + /* Find a bb to unswitch on. */ + vec candidates; + candidates.create (1); + find_unswitching_predicates_for_bb (bbs[i], loop, candidates); + if (!candidates.is_empty ()) + set_predicates_for_bb (bbs[i], candidates); + else + { + candidates.release (); + gimple *last = last_stmt (bbs[i]); + if (last != NULL) + gimple_set_uid (last, 0); + } + } + + free (bbs); + + return total_insns; +} /* Main entry point. Perform loop unswitching on all suitable loops. */ unsigned int -tree_ssa_unswitch_loops (void) +tree_ssa_unswitch_loops (function *fun) { - bool changed = false; + bool changed_unswitch = false; + bool changed_hoist = false; + auto_edge_flag ignored_edge_flag (fun); + + ranger = enable_ranger (fun); /* Go through all loops starting from innermost. */ - for (auto loop : loops_list (cfun, LI_FROM_INNERMOST)) + for (auto loop : loops_list (fun, LI_FROM_INNERMOST)) { if (!loop->inner) - /* Unswitch innermost loop. */ - changed |= tree_unswitch_single_loop (loop, 0); + { + /* Perform initial tests if unswitch is eligible. */ + dump_user_location_t loc = find_loop_location (loop); + + /* Do not unswitch in cold regions. */ + if (optimize_loop_for_size_p (loop)) + { + if (dump_enabled_p ()) + dump_printf_loc (MSG_NOTE, loc, + "Not unswitching cold loops\n"); + continue; + } + + /* If the loop is not expected to iterate, there is no need + for unswitching. */ + HOST_WIDE_INT iterations = estimated_loop_iterations_int (loop); + if (iterations < 0) + iterations = likely_max_loop_iterations_int (loop); + if (iterations >= 0 && iterations <= 1) + { + if (dump_enabled_p ()) + dump_printf_loc (MSG_NOTE, loc, + "Not unswitching, loop is not expected" + " to iterate\n"); + continue; + } + + bb_predicates = new vec> (); + bb_predicates->safe_push (vec ()); + unswitch_predicate::predicates = new vec (); + + /* Unswitch innermost loop. */ + unsigned int loop_size = init_loop_unswitch_info (loop); + unsigned int budget = loop_size + param_max_unswitch_insns; + + predicate_vector predicate_path; + predicate_path.create (8); + auto_bitmap handled; + changed_unswitch + |= tree_unswitch_single_loop (loop, loc, predicate_path, + loop_size, budget, + ignored_edge_flag, handled); + predicate_path.release (); + + for (auto predlist : bb_predicates) + predlist.release (); + bb_predicates->release (); + delete bb_predicates; + bb_predicates = NULL; + + for (auto pred : unswitch_predicate::predicates) + delete pred; + unswitch_predicate::predicates->release (); + delete unswitch_predicate::predicates; + unswitch_predicate::predicates = NULL; + } else - changed |= tree_unswitch_outer_loop (loop); + changed_hoist |= tree_unswitch_outer_loop (loop); } - if (changed) + disable_ranger (fun); + clear_aux_for_blocks (); + + if (changed_unswitch) + clean_up_after_unswitching (ignored_edge_flag); + + if (changed_unswitch || changed_hoist) return TODO_cleanup_cfg; + return 0; } @@ -184,319 +444,588 @@ is_maybe_undefined (const tree name, gimple *stmt, class loop *loop) } /* Checks whether we can unswitch LOOP on condition at end of BB -- one of its - basic blocks (for what it means see comments below). */ + basic blocks (for what it means see comments below). + All candidates all filled to the provided vector CANDIDATES. */ -static tree -tree_may_unswitch_on (basic_block bb, class loop *loop) +static void +find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, + vec &candidates) { gimple *last, *def; - gcond *stmt; - tree cond, use; + tree use; basic_block def_bb; ssa_op_iter iter; /* BB must end in a simple conditional jump. */ last = last_stmt (bb); - if (!last || gimple_code (last) != GIMPLE_COND) - return NULL_TREE; - stmt = as_a (last); - - /* To keep the things simple, we do not directly remove the conditions, - but just replace tests with 0 != 0 resp. 1 != 0. Prevent the infinite - loop where we would unswitch again on such a condition. */ - if (gimple_cond_true_p (stmt) || gimple_cond_false_p (stmt)) - return NULL_TREE; - - /* Condition must be invariant. */ - FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_USE) + if (!last) + return; + + if (gcond *stmt = safe_dyn_cast (last)) { - def = SSA_NAME_DEF_STMT (use); + /* To keep the things simple, we do not directly remove the conditions, + but just replace tests with 0 != 0 resp. 1 != 0. Prevent the infinite + loop where we would unswitch again on such a condition. */ + if (gimple_cond_true_p (stmt) || gimple_cond_false_p (stmt)) + return; + + /* At least the LHS needs to be symbolic. */ + if (TREE_CODE (gimple_cond_lhs (stmt)) != SSA_NAME) + return; + + /* Condition must be invariant. */ + FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_USE) + { + def = SSA_NAME_DEF_STMT (use); + def_bb = gimple_bb (def); + if (def_bb + && flow_bb_inside_loop_p (loop, def_bb)) + return; + /* Unswitching on undefined values would introduce undefined + behavior that the original program might never exercise. */ + if (is_maybe_undefined (use, stmt, loop)) + return; + } + + unswitch_predicate *predicate = new unswitch_predicate (stmt); + candidates.safe_push (predicate); + } + else if (gswitch *stmt = safe_dyn_cast (last)) + { + unsigned nlabels = gimple_switch_num_labels (stmt); + tree idx = gimple_switch_index (stmt); + if (TREE_CODE (idx) != SSA_NAME + || nlabels < 1) + return; + /* Index must be invariant. */ + def = SSA_NAME_DEF_STMT (idx); def_bb = gimple_bb (def); if (def_bb && flow_bb_inside_loop_p (loop, def_bb)) - return NULL_TREE; + return; /* Unswitching on undefined values would introduce undefined behavior that the original program might never exercise. */ - if (is_maybe_undefined (use, stmt, loop)) - return NULL_TREE; - } + if (is_maybe_undefined (idx, stmt, loop)) + return; + + /* Build compound expression for all outgoing edges of the switch. */ + auto_vec preds; + auto_vec edge_range; + preds.safe_grow_cleared (EDGE_COUNT (gimple_bb (stmt)->succs), true); + edge_range.safe_grow_cleared (EDGE_COUNT (gimple_bb (stmt)->succs), true); + edge e; + edge_iterator ei; + unsigned edge_index = 0; + FOR_EACH_EDGE (e, ei, gimple_bb (stmt)->succs) + e->aux = (void *)(uintptr_t)edge_index++; + for (unsigned i = 1; i < gimple_switch_num_labels (stmt); ++i) + { + tree lab = gimple_switch_label (stmt, i); + tree cmp; + int_range<2> lab_range; + if (CASE_HIGH (lab) != NULL_TREE) + { + tree cmp1 = fold_build2 (GE_EXPR, boolean_type_node, idx, + CASE_LOW (lab)); + tree cmp2 = fold_build2 (LE_EXPR, boolean_type_node, idx, + CASE_HIGH (lab)); + cmp = fold_build2 (BIT_AND_EXPR, boolean_type_node, cmp1, cmp2); + lab_range.set (CASE_LOW (lab), CASE_HIGH (lab)); + } + else + { + cmp = fold_build2 (EQ_EXPR, boolean_type_node, idx, + CASE_LOW (lab)); + lab_range.set (CASE_LOW (lab)); + } - cond = build2 (gimple_cond_code (stmt), boolean_type_node, - gimple_cond_lhs (stmt), gimple_cond_rhs (stmt)); + /* Combine the expression with the existing one. */ + basic_block dest = label_to_block (cfun, CASE_LABEL (lab)); + e = find_edge (gimple_bb (stmt), dest); + tree &expr = preds[(uintptr_t)e->aux]; + if (expr == NULL_TREE) + expr = cmp; + else + expr = fold_build2 (BIT_IOR_EXPR, boolean_type_node, expr, cmp); + edge_range[(uintptr_t)e->aux].union_ (lab_range); + } - return cond; + /* Now register the predicates. */ + for (edge_index = 0; edge_index < preds.length (); ++edge_index) + { + edge e = EDGE_SUCC (gimple_bb (stmt), edge_index); + e->aux = NULL; + if (preds[edge_index] != NULL_TREE) + { + unswitch_predicate *predicate + = new unswitch_predicate (preds[edge_index], idx, + edge_index, e, + edge_range[edge_index]); + candidates.safe_push (predicate); + } + } + } } -/* Simplifies COND using checks in front of the entry of the LOOP. Just very - simplish (sufficient to prevent us from duplicating loop in unswitching - unnecessarily). */ +/* Merge ranges for the last item of PREDICATE_PATH with a predicate + that shared the same LHS. */ -static tree -simplify_using_entry_checks (class loop *loop, tree cond) +static void +merge_last (predicate_vector &predicate_path) { - edge e = loop_preheader_edge (loop); - gimple *stmt; + unswitch_predicate *last_predicate = predicate_path.last ().first; - while (1) + for (int i = predicate_path.length () - 2; i >= 0; i--) { - stmt = last_stmt (e->src); - if (stmt - && gimple_code (stmt) == GIMPLE_COND - && gimple_cond_code (stmt) == TREE_CODE (cond) - && operand_equal_p (gimple_cond_lhs (stmt), - TREE_OPERAND (cond, 0), 0) - && operand_equal_p (gimple_cond_rhs (stmt), - TREE_OPERAND (cond, 1), 0)) - return (e->flags & EDGE_TRUE_VALUE - ? boolean_true_node - : boolean_false_node); - - if (!single_pred_p (e->src)) - return cond; - - e = single_pred_edge (e->src); - if (e->src == ENTRY_BLOCK_PTR_FOR_FN (cfun)) - return cond; + unswitch_predicate *predicate = predicate_path[i].first; + bool true_edge = predicate_path[i].second; + + if (operand_equal_p (predicate->lhs, last_predicate->lhs, 0)) + { + irange &other = (true_edge ? predicate->merged_true_range + : predicate->merged_false_range); + last_predicate->merged_true_range.intersect (other); + last_predicate->merged_false_range.intersect (other); + return; + } } } -/* Unswitch single LOOP. NUM is number of unswitchings done; we do not allow - it to grow too much, it is too easy to create example on that the code would - grow exponentially. */ +/* Add PREDICATE to PREDICATE_PATH on TRUE_EDGE. */ -static bool -tree_unswitch_single_loop (class loop *loop, int num) +static void +add_predicate_to_path (predicate_vector &predicate_path, + unswitch_predicate *predicate, bool true_edge) { - basic_block *bbs; - class loop *nloop; - unsigned i, found; - tree cond = NULL_TREE; - gimple *stmt; - bool changed = false; - HOST_WIDE_INT iterations; - - dump_user_location_t loc = find_loop_location (loop); + predicate->copy_merged_ranges (); + predicate_path.safe_push (std::make_pair (predicate, true_edge)); + merge_last (predicate_path); +} - /* Perform initial tests if unswitch is eligible. */ - if (num == 0) +static bool +find_range_for_lhs (predicate_vector &predicate_path, tree lhs, + int_range_max &range) +{ + for (int i = predicate_path.length () - 1; i >= 0; i--) { - /* Do not unswitch in cold regions. */ - if (optimize_loop_for_size_p (loop)) - { - if (dump_enabled_p ()) - dump_printf_loc (MSG_NOTE, loc, - "Not unswitching cold loops\n"); - return false; - } + unswitch_predicate *predicate = predicate_path[i].first; + bool true_edge = predicate_path[i].second; - /* The loop should not be too large, to limit code growth. */ - if (tree_num_loop_insns (loop, &eni_size_weights) - > (unsigned) param_max_unswitch_insns) + if (operand_equal_p (predicate->lhs, lhs, 0)) { - if (dump_enabled_p ()) - dump_printf_loc (MSG_NOTE, loc, - "Not unswitching, loop too big\n"); - return false; + range = (true_edge ? predicate->merged_true_range + : predicate->merged_false_range); + return !range.undefined_p (); } + } - /* If the loop is not expected to iterate, there is no need - for unswitching. */ - iterations = estimated_loop_iterations_int (loop); - if (iterations < 0) - iterations = likely_max_loop_iterations_int (loop); - if (iterations >= 0 && iterations <= 1) + return false; +} + +/* Simplifies STMT using the predicate we unswitched on which is the last + in PREDICATE_PATH. For switch statements add newly unreachable edges + to IGNORED_EDGES (but do not set IGNORED_EDGE_FLAG on them). */ + +static tree +evaluate_control_stmt_using_entry_checks (gimple *stmt, + predicate_vector &predicate_path, + int ignored_edge_flag, + hash_set *ignored_edges) +{ + unswitch_predicate *last_predicate = predicate_path.last ().first; + bool true_edge = predicate_path.last ().second; + + if (gcond *cond = dyn_cast (stmt)) + { + tree lhs = gimple_cond_lhs (cond); + if (!operand_equal_p (lhs, last_predicate->lhs)) + return NULL_TREE; + /* Try a symbolic match which works for floating point and fully + symbolic conditions. */ + if (gimple_cond_code (cond) == TREE_CODE (last_predicate->condition) + && operand_equal_p (gimple_cond_rhs (cond), + TREE_OPERAND (last_predicate->condition, 1))) + return true_edge ? boolean_true_node : boolean_false_node; + /* Else try ranger if it supports LHS. */ + else if (irange::supports_type_p (TREE_TYPE (lhs))) { - if (dump_enabled_p ()) - dump_printf_loc (MSG_NOTE, loc, - "Not unswitching, loop is not expected" - " to iterate\n"); - return false; + int_range<2> r; + int_range_max path_range; + + if (find_range_for_lhs (predicate_path, lhs, path_range) + && fold_range (r, cond, path_range) + && r.singleton_p ()) + return r.zero_p () ? boolean_false_node : boolean_true_node; } } + else if (gswitch *swtch = dyn_cast (stmt)) + { + unsigned nlabels = gimple_switch_num_labels (swtch); - i = 0; - bbs = get_loop_body (loop); - found = loop->num_nodes; + tree idx = gimple_switch_index (swtch); - while (1) - { - /* Find a bb to unswitch on. */ - for (; i < loop->num_nodes; i++) - if ((cond = tree_may_unswitch_on (bbs[i], loop))) - break; + /* Already folded switch. */ + if (TREE_CONSTANT (idx)) + return NULL_TREE; - if (i == loop->num_nodes) + int_range_max path_range; + if (!find_range_for_lhs (predicate_path, idx, path_range)) + return NULL_TREE; + + tree result = NULL_TREE; + edge single_edge = NULL; + for (unsigned i = 0; i < nlabels; ++i) { - if (dump_enabled_p () - && num > param_max_unswitch_level) - dump_printf_loc (MSG_MISSED_OPTIMIZATION, loc, - "Not unswitching anymore, hit max level\n"); + tree lab = gimple_switch_label (swtch, i); + basic_block dest = label_to_block (cfun, CASE_LABEL (lab)); + edge e = find_edge (gimple_bb (stmt), dest); + if (e->flags & ignored_edge_flag) + continue; - if (found == loop->num_nodes) + int_range_max r; + if (!ranger->gori ().outgoing_edge_range_p (r, e, idx, + *get_global_range_query ())) + continue; + r.intersect (path_range); + if (r.undefined_p ()) + ignored_edges->add (e); + else { - free (bbs); - return changed; + if (!single_edge) + { + single_edge = e; + result = CASE_LOW (lab); + } + else if (single_edge != e) + result = NULL; } - break; } - cond = simplify_using_entry_checks (loop, cond); - stmt = last_stmt (bbs[i]); - if (integer_nonzerop (cond)) - { - /* Remove false path. */ - gimple_cond_set_condition_from_tree (as_a (stmt), - boolean_true_node); - changed = true; - } - else if (integer_zerop (cond)) + /* Only one edge from the switch is alive. */ + if (single_edge && result) + return result; + } + + return NULL_TREE; +} + +/* Simplify LOOP based on PREDICATE_PATH where dead edges are properly + marked. */ + +static bool +simplify_loop_version (class loop *loop, predicate_vector &predicate_path, + int ignored_edge_flag, bitmap handled) +{ + bool changed = false; + basic_block *bbs = get_loop_body (loop); + + hash_set ignored_edges; + for (unsigned i = 0; i != loop->num_nodes; i++) + { + vec &predicates = get_predicates_for_bb (bbs[i]); + if (predicates.is_empty ()) + continue; + + gimple *stmt = last_stmt (bbs[i]); + tree folded = evaluate_control_stmt_using_entry_checks (stmt, + predicate_path, + ignored_edge_flag, + &ignored_edges); + + if (gcond *cond = dyn_cast (stmt)) { - /* Remove true path. */ - gimple_cond_set_condition_from_tree (as_a (stmt), - boolean_false_node); - changed = true; + if (folded) + { + /* Remove path. */ + if (integer_nonzerop (folded)) + gimple_cond_set_condition_from_tree (cond, boolean_true_node); + else + gimple_cond_set_condition_from_tree (cond, boolean_false_node); + + gcc_assert (predicates.length () == 1); + bitmap_set_bit (handled, predicates[0]->num); + + update_stmt (cond); + changed = true; + } } - /* Do not unswitch too much. */ - else if (num > param_max_unswitch_level) + else if (gswitch *swtch = dyn_cast (stmt)) { - i++; - continue; + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, bbs[i]->succs) + if (ignored_edges.contains (e)) + e->flags |= ignored_edge_flag; + + for (unsigned j = 0; j < predicates.length (); j++) + { + edge e = EDGE_SUCC (bbs[i], predicates[j]->edge_index); + if (ignored_edges.contains (e)) + bitmap_set_bit (handled, predicates[j]->num); + } + + if (folded) + { + gimple_switch_set_index (swtch, folded); + update_stmt (swtch); + changed = true; + } } - /* In nested tree_unswitch_single_loop first optimize all conditions - using entry checks, then discover still reachable blocks in the - loop and find the condition only among those still reachable bbs. */ - else if (num != 0) + } + + free (bbs); + return changed; +} + +/* Evaluate reachable blocks in LOOP and call VISIT on them, aborting the + DFS walk if VISIT returns true. When PREDICATE_PATH is specified then + take into account that when computing reachability, otherwise just + look at the simplified state and IGNORED_EDGE_FLAG. */ + +template +static void +evaluate_bbs (class loop *loop, predicate_vector *predicate_path, + int ignored_edge_flag, VisitOp visit) +{ + auto_bb_flag reachable_flag (cfun); + auto_vec worklist (loop->num_nodes); + auto_vec reachable (loop->num_nodes); + hash_set ignored_edges; + + loop->header->flags |= reachable_flag; + worklist.quick_push (loop->header); + reachable.safe_push (loop->header); + + while (!worklist.is_empty ()) + { + edge e; + edge_iterator ei; + int flags = ignored_edge_flag; + basic_block bb = worklist.pop (); + + if (visit (bb)) + break; + + gimple *last = last_stmt (bb); + if (gcond *cond = safe_dyn_cast (last)) { - if (found == loop->num_nodes) - found = i; - i++; - continue; + if (gimple_cond_true_p (cond)) + flags = EDGE_FALSE_VALUE; + else if (gimple_cond_false_p (cond)) + flags = EDGE_TRUE_VALUE; + else if (predicate_path) + { + tree res; + if (!get_predicates_for_bb (bb).is_empty () + && (res = evaluate_control_stmt_using_entry_checks + (cond, *predicate_path, ignored_edge_flag, + &ignored_edges))) + flags = (integer_nonzerop (res) + ? EDGE_FALSE_VALUE : EDGE_TRUE_VALUE); + } } - else + else if (gswitch *swtch = safe_dyn_cast (last)) + if (predicate_path + && !get_predicates_for_bb (bb).is_empty ()) + evaluate_control_stmt_using_entry_checks (swtch, *predicate_path, + ignored_edge_flag, + &ignored_edges); + + /* Note that for the moment we do not account reachable conditions + which are simplified to take a known edge as zero size nor + are we accounting for the required addition of the versioning + condition. Those should cancel out conservatively. */ + + FOR_EACH_EDGE (e, ei, bb->succs) { - found = i; - break; - } + basic_block dest = e->dest; - update_stmt (stmt); - i++; + if (dest->loop_father == loop + && !(dest->flags & reachable_flag) + && !(e->flags & flags) + && !ignored_edges.contains (e)) + { + dest->flags |= reachable_flag; + worklist.safe_push (dest); + reachable.safe_push (dest); + } + } } - if (num != 0) - { - basic_block *tos, *worklist; + /* Clear the flag from basic blocks. */ + while (!reachable.is_empty ()) + reachable.pop ()->flags &= ~reachable_flag; +} - /* When called recursively, first do a quick discovery - of reachable bbs after the above changes and only - consider conditions in still reachable bbs. */ - tos = worklist = XNEWVEC (basic_block, loop->num_nodes); +/* Evaluate how many instruction will we have if we unswitch LOOP (with BBS) + based on PREDICATE predicate (using PREDICATE_PATH). Store the + result in TRUE_SIZE and FALSE_SIZE. */ - for (i = 0; i < loop->num_nodes; i++) - bbs[i]->flags &= ~BB_REACHABLE; +static void +evaluate_loop_insns_for_predicate (class loop *loop, + predicate_vector &predicate_path, + unswitch_predicate *predicate, + int ignored_edge_flag, + unsigned *true_size, unsigned *false_size) +{ + unsigned size = 0; + auto sum_size = [&](basic_block bb) -> bool + { size += (uintptr_t)bb->aux; return false; }; + + add_predicate_to_path (predicate_path, predicate, true); + evaluate_bbs (loop, &predicate_path, ignored_edge_flag, sum_size); + predicate_path.pop (); + unsigned true_loop_cost = size; + + size = 0; + add_predicate_to_path (predicate_path, predicate, false); + evaluate_bbs (loop, &predicate_path, ignored_edge_flag, sum_size); + predicate_path.pop (); + unsigned false_loop_cost = size; + + *true_size = true_loop_cost; + *false_size = false_loop_cost; +} - /* Start with marking header. */ - *tos++ = bbs[0]; - bbs[0]->flags |= BB_REACHABLE; +/* Unswitch single LOOP. PREDICATE_PATH contains so far used predicates + for unswitching. BUDGET is number of instruction for which we can increase + the loop and is updated when unswitching occurs. */ - /* Iterate: find everything reachable from what we've already seen - within the same innermost loop. Don't look through false edges - if condition is always true or true edges if condition is - always false. */ - while (tos != worklist) +static bool +tree_unswitch_single_loop (class loop *loop, dump_user_location_t loc, + predicate_vector &predicate_path, + unsigned loop_size, unsigned &budget, + int ignored_edge_flag, bitmap handled) +{ + class loop *nloop; + bool changed = false; + unswitch_predicate *predicate = NULL; + basic_block predicate_bb = NULL; + unsigned true_size = 0, false_size = 0; + + auto check_predicates = [&](basic_block bb) -> bool + { + for (auto pred : get_predicates_for_bb (bb)) { - basic_block b = *--tos; - edge e; - edge_iterator ei; - int flags = 0; + if (bitmap_bit_p (handled, pred->num)) + continue; - if (EDGE_COUNT (b->succs) == 2) - { - gimple *stmt = last_stmt (b); - if (stmt - && gimple_code (stmt) == GIMPLE_COND) - { - gcond *cond_stmt = as_a (stmt); - if (gimple_cond_true_p (cond_stmt)) - flags = EDGE_FALSE_VALUE; - else if (gimple_cond_false_p (cond_stmt)) - flags = EDGE_TRUE_VALUE; - } - } + evaluate_loop_insns_for_predicate (loop, predicate_path, + pred, ignored_edge_flag, + &true_size, &false_size); - FOR_EACH_EDGE (e, ei, b->succs) + /* We'll get LOOP replaced with a simplified version according + to PRED estimated to TRUE_SIZE and a copy simplified + according to the inverted PRED estimated to FALSE_SIZE. */ + if (true_size + false_size < budget + loop_size) { - basic_block dest = e->dest; - - if (dest->loop_father == loop - && !(dest->flags & BB_REACHABLE) - && !(e->flags & flags)) - { - *tos++ = dest; - dest->flags |= BB_REACHABLE; - } + predicate = pred; + predicate_bb = bb; + + /* There are cases where true_size and false_size add up to + less than the original loop_size. We do not want to + grow the remaining budget because of that. */ + if (true_size + false_size > loop_size) + budget -= (true_size + false_size - loop_size); + + /* FIXME: right now we select first candidate, but we can + choose the cheapest or hottest one. */ + return true; } + else if (dump_enabled_p ()) + dump_printf_loc (MSG_NOTE, loc, + "not unswitching condition, cost too big " + "(%u insns copied to %u and %u)\n", loop_size, + true_size, false_size); } + return false; + }; + /* Check predicates of reachable blocks. */ + evaluate_bbs (loop, NULL, ignored_edge_flag, check_predicates); - free (worklist); + if (predicate != NULL) + { + if (!dbg_cnt (loop_unswitch)) + goto exit; - /* Find a bb to unswitch on. */ - for (; found < loop->num_nodes; found++) - if ((bbs[found]->flags & BB_REACHABLE) - && (cond = tree_may_unswitch_on (bbs[found], loop))) - break; + if (dump_enabled_p ()) + { + dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc, + "unswitching loop %d on %qs with condition: %T\n", + loop->num, predicate->switch_p ? "switch" : "if", + predicate->condition); + dump_printf_loc (MSG_NOTE, loc, + "optimized sizes estimated to %u (true) " + "and %u (false) from original size %u\n", + true_size, false_size, loop_size); + } - if (found == loop->num_nodes) + bitmap_set_bit (handled, predicate->num); + initialize_original_copy_tables (); + /* Unswitch the loop on this condition. */ + nloop = tree_unswitch_loop (loop, EDGE_SUCC (predicate_bb, + predicate->edge_index), + predicate->condition); + if (!nloop) { - free (bbs); - return changed; + free_original_copy_tables (); + goto exit; } - } - if (dump_enabled_p ()) - dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc, - "Unswitching loop on condition: %G\n", - last_stmt (bbs[found])); + /* Copy BB costs. */ + basic_block *bbs2 = get_loop_body (nloop); + for (unsigned i = 0; i < nloop->num_nodes; i++) + bbs2[i]->aux = get_bb_original (bbs2[i])->aux; + free (bbs2); - initialize_original_copy_tables (); - /* Unswitch the loop on this condition. */ - nloop = tree_unswitch_loop (loop, bbs[found], cond); - if (!nloop) - { free_original_copy_tables (); - free (bbs); - return changed; - } - /* Update the SSA form after unswitching. */ - update_ssa (TODO_update_ssa); - free_original_copy_tables (); + /* Update the SSA form after unswitching. */ + update_ssa (TODO_update_ssa); + + /* Invoke itself on modified loops. */ + bitmap handled_copy = BITMAP_ALLOC (NULL); + bitmap_copy (handled_copy, handled); + add_predicate_to_path (predicate_path, predicate, false); + changed |= simplify_loop_version (nloop, predicate_path, + ignored_edge_flag, handled_copy); + tree_unswitch_single_loop (nloop, loc, predicate_path, + false_size, budget, + ignored_edge_flag, handled_copy); + predicate_path.pop (); + BITMAP_FREE (handled_copy); + + /* FIXME: After unwinding above we have to reset all ->handled + flags as otherwise we fail to realize unswitching opportunities + in the below recursion. See gcc.dg/loop-unswitch-16.c */ + add_predicate_to_path (predicate_path, predicate, true); + changed |= simplify_loop_version (loop, predicate_path, + ignored_edge_flag, handled); + tree_unswitch_single_loop (loop, loc, predicate_path, + true_size, budget, + ignored_edge_flag, handled); + predicate_path.pop (); + changed = true; + } - /* Invoke itself on modified loops. */ - tree_unswitch_single_loop (nloop, num + 1); - tree_unswitch_single_loop (loop, num + 1); - free (bbs); - return true; +exit: + return changed; } -/* Unswitch a LOOP w.r. to given basic block UNSWITCH_ON. We only support - unswitching of innermost loops. COND is the condition determining which - loop is entered -- the new loop is entered if COND is true. Returns NULL - if impossible, new loop otherwise. */ +/* Unswitch a LOOP w.r. to given EDGE_TRUE. We only support unswitching of + innermost loops. COND is the condition determining which loop is entered; + the new loop is entered if COND is true. Returns NULL if impossible, new + loop otherwise. */ static class loop * -tree_unswitch_loop (class loop *loop, - basic_block unswitch_on, tree cond) +tree_unswitch_loop (class loop *loop, edge edge_true, tree cond) { - profile_probability prob_true; - edge edge_true, edge_false; - /* Some sanity checking. */ - gcc_assert (flow_bb_inside_loop_p (loop, unswitch_on)); - gcc_assert (EDGE_COUNT (unswitch_on->succs) == 2); + gcc_assert (flow_bb_inside_loop_p (loop, edge_true->src)); + gcc_assert (EDGE_COUNT (edge_true->src->succs) >= 2); gcc_assert (loop->inner == NULL); - extract_true_false_edges_from_block (unswitch_on, &edge_true, &edge_false); - prob_true = edge_true->probability; + profile_probability prob_true = edge_true->probability; return loop_version (loop, unshare_expr (cond), NULL, prob_true, prob_true.invert (), @@ -1010,6 +1539,57 @@ check_exit_phi (class loop *loop) return true; } +/* Remove all dead cases from switches that are unswitched. */ + +static void +clean_up_after_unswitching (int ignored_edge_flag) +{ + basic_block bb; + edge e; + edge_iterator ei; + + FOR_EACH_BB_FN (bb, cfun) + { + gswitch *stmt= safe_dyn_cast (last_stmt (bb)); + if (stmt && !CONSTANT_CLASS_P (gimple_switch_index (stmt))) + { + unsigned nlabels = gimple_switch_num_labels (stmt); + unsigned index = 1; + tree lab = gimple_switch_default_label (stmt); + edge default_e = find_edge (gimple_bb (stmt), + label_to_block (cfun, CASE_LABEL (lab))); + for (unsigned i = 1; i < nlabels; ++i) + { + tree lab = gimple_switch_label (stmt, i); + basic_block dest = label_to_block (cfun, CASE_LABEL (lab)); + edge e = find_edge (gimple_bb (stmt), dest); + if (e == NULL) + ; /* The edge is already removed. */ + else if (e->flags & ignored_edge_flag) + { + /* We may not remove the default label so we also have + to preserve its edge. But we can remove the + non-default CASE sharing the edge. */ + if (e != default_e) + remove_edge (e); + } + else + { + gimple_switch_set_label (stmt, index, lab); + ++index; + } + } + + if (index != nlabels) + gimple_switch_set_num_labels (stmt, index); + } + + /* Clean up the ignored_edge_flag from edges. */ + FOR_EACH_EDGE (e, ei, bb->succs) + e->flags &= ~ignored_edge_flag; + } +} + /* Loop unswitching pass. */ namespace { @@ -1046,7 +1626,7 @@ pass_tree_unswitch::execute (function *fun) if (number_of_loops (fun) <= 1) return 0; - return tree_ssa_unswitch_loops (); + return tree_ssa_unswitch_loops (fun); } } // anon namespace -- Gitee From 6370653e8925930361e180f31831261d6cadbb26 Mon Sep 17 00:00:00 2001 From: Richard Biener Date: Mon, 30 May 2022 13:19:38 +0200 Subject: [PATCH 03/10] [Backport] tree-optimization/105763: Avoid abnormals with ranger queries Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=ae575e93b6f7fb7077940cb7410f899da184ec2d In unswitching we use ranger to simplify switch statements so we have to avoid doing anything for abnormals. 2022-05-30 Richard Biener PR tree-optimization/105763 * tree-ssa-loop-unswitch.cc (find_unswitching_predicates_for_bb): Check gimple_range_ssa_p. * gcc.dg/pr105763.c: New testcase. (cherry picked from commit ae575e93b6f7fb7077940cb7410f899da184ec2d) Signed-off-by: Zhongjie Guo (cherry picked from commit 044e4eff8a8dbb9fb7e1eda29eb80879dfc8f565) --- gcc/testsuite/gcc.dg/pr105763.c | 21 +++++++++++++++++++++ gcc/tree-ssa-loop-unswitch.cc | 3 +-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/pr105763.c diff --git a/gcc/testsuite/gcc.dg/pr105763.c b/gcc/testsuite/gcc.dg/pr105763.c new file mode 100644 index 00000000000..4c76b17e73d --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr105763.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O3" } */ + +int rl2_decode_png_bit_depth; +int *rl2_decode_png_p_data; +void png_destroy_read_struct (); +int __attribute__((returns_twice)) _setjmp (); +void rl2_decode_png_row_pointers() +{ + unsigned sample_type = 0; + _setjmp(); + switch (rl2_decode_png_bit_depth) + case 6: + sample_type = 7; + png_destroy_read_struct(); + for (;;) + switch (sample_type) + case 3: + case 5: + *rl2_decode_png_p_data; +} diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index f32f1a78f00..a55905c2c68 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -494,8 +494,7 @@ find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, { unsigned nlabels = gimple_switch_num_labels (stmt); tree idx = gimple_switch_index (stmt); - if (TREE_CODE (idx) != SSA_NAME - || nlabels < 1) + if (!gimple_range_ssa_p (idx) || nlabels < 1) return; /* Index must be invariant. */ def = SSA_NAME_DEF_STMT (idx); -- Gitee From 6f54395e6175f5caab868fbb8c86a1f67504135f Mon Sep 17 00:00:00 2001 From: Jakub Jelinek Date: Wed, 1 Jun 2022 14:00:49 +0200 Subject: [PATCH 04/10] [Backport] unswitch: Fold case label lo/hi values to index type [PR105770] Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=0d5cc976a36af07c9790c38a99a0b98110c89795 The following testcase ICEs because we use different types in comparison, idx has int type, while CASE_LOW has char type. While I believe all CASE_{LOW,HIGH} in the same switch have to use the same or compatible type, the index expression can have a promoted type as happens in this testcase. Other spots that handle switches do such foldings too. 2022-06-01 Jakub Jelinek PR tree-optimization/105770 * tree-ssa-loop-unswitch.cc (find_unswitching_predicates_for_bb): Cast CASE_LOW and CASE_HIGH to TREE_TYPE (idx) before comparisons with idx. * gcc.dg/pr105770.c: New test. (cherry picked from commit 0d5cc976a36af07c9790c38a99a0b98110c89795) Signed-off-by: Zhongjie Guo (cherry picked from commit ab718bf75d70ed3206b93249159c3993bcb62c95) --- gcc/testsuite/gcc.dg/pr105770.c | 19 +++++++++++++++++++ gcc/tree-ssa-loop-unswitch.cc | 9 ++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/pr105770.c diff --git a/gcc/testsuite/gcc.dg/pr105770.c b/gcc/testsuite/gcc.dg/pr105770.c new file mode 100644 index 00000000000..c1e01402920 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr105770.c @@ -0,0 +1,19 @@ +/* PR tree-optimization/105770 */ +/* { dg-do compile } */ +/* { dg-options "-O1 -funswitch-loops -fno-tree-forwprop" } */ + +char a; + +void +foo (void) +{ + while (a) + switch (a) + { + case ' ': + case '\t': + return; + } + + __builtin_unreachable (); +} diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index a55905c2c68..f590860a43a 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -494,6 +494,7 @@ find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, { unsigned nlabels = gimple_switch_num_labels (stmt); tree idx = gimple_switch_index (stmt); + tree idx_type = TREE_TYPE (idx); if (!gimple_range_ssa_p (idx) || nlabels < 1) return; /* Index must be invariant. */ @@ -525,16 +526,18 @@ find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, if (CASE_HIGH (lab) != NULL_TREE) { tree cmp1 = fold_build2 (GE_EXPR, boolean_type_node, idx, - CASE_LOW (lab)); + fold_convert (idx_type, + CASE_LOW (lab))); tree cmp2 = fold_build2 (LE_EXPR, boolean_type_node, idx, - CASE_HIGH (lab)); + fold_convert (idx_type, + CASE_HIGH (lab))); cmp = fold_build2 (BIT_AND_EXPR, boolean_type_node, cmp1, cmp2); lab_range.set (CASE_LOW (lab), CASE_HIGH (lab)); } else { cmp = fold_build2 (EQ_EXPR, boolean_type_node, idx, - CASE_LOW (lab)); + fold_convert (idx_type, CASE_LOW (lab))); lab_range.set (CASE_LOW (lab)); } -- Gitee From 9d2627010f75caa24032e55516c88d33b91c842c Mon Sep 17 00:00:00 2001 From: Richard Biener Date: Wed, 1 Jun 2022 15:42:06 +0200 Subject: [PATCH 05/10] [Backport] tree-optimization/105802: Fix another unswitching type issue Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=4a6b8d9aad9f68eec223cc126d9effbf45e37271 This also fixes the type of the irange used for unswitching of switch statements. PR tree-optimization/105802 * tree-ssa-loop-unswitch.cc (find_unswitching_predicates_for_bb): Make sure to also compute the range in the type of the switch index. * g++.dg/opt/pr105802.C: New testcase. (cherry picked from commit 4a6b8d9aad9f68eec223cc126d9effbf45e37271) Signed-off-by: Zhongjie Guo (cherry picked from commit 1ff1f6fed3758682890a80a5f23846ed29cf12b2) --- gcc/testsuite/g++.dg/opt/pr105802.C | 23 +++++++++++++++++++++++ gcc/tree-ssa-loop-unswitch.cc | 17 +++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 gcc/testsuite/g++.dg/opt/pr105802.C diff --git a/gcc/testsuite/g++.dg/opt/pr105802.C b/gcc/testsuite/g++.dg/opt/pr105802.C new file mode 100644 index 00000000000..2514245d00a --- /dev/null +++ b/gcc/testsuite/g++.dg/opt/pr105802.C @@ -0,0 +1,23 @@ +// { dg-do compile } +// { dg-options "-O3" } + +enum E { E0, E1 }; + +void bar (); +void baz (); + +int c; + +void +foo (int i) +{ + E e = (E) i; + while (c) + switch (e) + { + case E0: + bar (); + case E1: + baz (); + } +} diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index f590860a43a..4f25a50ceb7 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -523,22 +523,19 @@ find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, tree lab = gimple_switch_label (stmt, i); tree cmp; int_range<2> lab_range; + tree low = fold_convert (idx_type, CASE_LOW (lab)); if (CASE_HIGH (lab) != NULL_TREE) { - tree cmp1 = fold_build2 (GE_EXPR, boolean_type_node, idx, - fold_convert (idx_type, - CASE_LOW (lab))); - tree cmp2 = fold_build2 (LE_EXPR, boolean_type_node, idx, - fold_convert (idx_type, - CASE_HIGH (lab))); + tree high = fold_convert (idx_type, CASE_HIGH (lab)); + tree cmp1 = fold_build2 (GE_EXPR, boolean_type_node, idx, low); + tree cmp2 = fold_build2 (LE_EXPR, boolean_type_node, idx, high); cmp = fold_build2 (BIT_AND_EXPR, boolean_type_node, cmp1, cmp2); - lab_range.set (CASE_LOW (lab), CASE_HIGH (lab)); + lab_range.set (low, high); } else { - cmp = fold_build2 (EQ_EXPR, boolean_type_node, idx, - fold_convert (idx_type, CASE_LOW (lab))); - lab_range.set (CASE_LOW (lab)); + cmp = fold_build2 (EQ_EXPR, boolean_type_node, idx, low); + lab_range.set (low); } /* Combine the expression with the existing one. */ -- Gitee From ba10061c4bed943705ea87d796fbb51379538398 Mon Sep 17 00:00:00 2001 From: Richard Biener Date: Tue, 27 Sep 2022 10:16:52 +0200 Subject: [PATCH 06/10] [Backport] Unswitch most profitable condition first Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=7314b98b1bcd382c996a79bdcd0a1cb1e9231f55 When doing the loop unswitching re-org we promised to followup with improvements on the cost modeling. The following makes sure we try to unswitch on the most profitable condition first. As most profitable we pick the condition leading to the edge with the highest profile count. Note the profile is only applied when picking the first unswitching opportunity since the profile counts are not updated with earlier unswitchings in mind. Further opportunities are picked in DFS order. * tree-ssa-loop-unswitch.cc (unswitch_predicate::count): New. (unswitch_predicate::unswitch_predicate): Initialize count. (init_loop_unswitch_info): First collect candidates and determine the outermost loop to unswitch. (tree_ssa_unswitch_loops): First perform all guard hoisting, then perform unswitching on innermost loop predicates. (find_unswitching_predicates_for_bb): Keep track of the most profitable predicate to unswitch on. (tree_unswitch_single_loop): Unswitch given predicate if not NULL. (cherry picked from commit 7314b98b1bcd382c996a79bdcd0a1cb1e9231f55) Signed-off-by: Zhongjie Guo (cherry picked from commit bf16fa7fab6f70b74cd7b44ae39fd3e77c138a94) --- gcc/tree-ssa-loop-unswitch.cc | 66 ++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index 4f25a50ceb7..0da6d01012a 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -118,6 +118,7 @@ struct unswitch_predicate if (!false_range.varying_p () && !false_range.undefined_p ()) false_range.invert (); + count = e->count (); num = predicates->length (); predicates->safe_push (this); } @@ -126,7 +127,8 @@ struct unswitch_predicate unswitch_predicate (gcond *stmt) : switch_p (false) { - if (EDGE_SUCC (gimple_bb (stmt), 0)->flags & EDGE_TRUE_VALUE) + basic_block bb = gimple_bb (stmt); + if (EDGE_SUCC (bb, 0)->flags & EDGE_TRUE_VALUE) edge_index = 0; else edge_index = 1; @@ -134,6 +136,7 @@ struct unswitch_predicate tree rhs = gimple_cond_rhs (stmt); enum tree_code code = gimple_cond_code (stmt); condition = build2 (code, boolean_type_node, lhs, rhs); + count = EDGE_SUCC (bb, 0)->count ().max (EDGE_SUCC (bb, 1)->count ()); if (irange::supports_type_p (TREE_TYPE (lhs))) { auto range_op = range_op_handler (code, TREE_TYPE (lhs)); @@ -180,6 +183,9 @@ struct unswitch_predicate /* Index of the edge the predicate belongs to in the successor vector. */ int edge_index; + /* The profile count of this predicate. */ + profile_count count; + /* Whether the predicate was created from a switch statement. */ bool switch_p; @@ -206,10 +212,14 @@ static class loop *tree_unswitch_loop (class loop *, edge, tree); static bool tree_unswitch_single_loop (class loop *, dump_user_location_t, predicate_vector &predicate_path, unsigned loop_size, unsigned &budget, - int ignored_edge_flag, bitmap); + int ignored_edge_flag, bitmap, + unswitch_predicate * = NULL, + basic_block = NULL); static void find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, - vec &candidates); + vec &candidates, + unswitch_predicate *&hottest, + basic_block &hottest_bb); static bool tree_unswitch_outer_loop (class loop *); static edge find_loop_guard (class loop *, vec&); static bool empty_bb_without_guard_p (class loop *, basic_block, @@ -242,7 +252,8 @@ set_predicates_for_bb (basic_block bb, vec predicates) Return total number of instructions in the loop. */ static unsigned -init_loop_unswitch_info (class loop *loop) +init_loop_unswitch_info (class loop *loop, unswitch_predicate *&hottest, + basic_block &hottest_bb) { unsigned total_insns = 0; @@ -259,13 +270,16 @@ init_loop_unswitch_info (class loop *loop) total_insns += insns; } + hottest = NULL; + hottest_bb = NULL; /* Find all unswitching candidates. */ for (unsigned i = 0; i != loop->num_nodes; i++) { /* Find a bb to unswitch on. */ vec candidates; candidates.create (1); - find_unswitching_predicates_for_bb (bbs[i], loop, candidates); + find_unswitching_predicates_for_bb (bbs[i], loop, candidates, + hottest, hottest_bb); if (!candidates.is_empty ()) set_predicates_for_bb (bbs[i], candidates); else @@ -329,7 +343,10 @@ tree_ssa_unswitch_loops (function *fun) unswitch_predicate::predicates = new vec (); /* Unswitch innermost loop. */ - unsigned int loop_size = init_loop_unswitch_info (loop); + unswitch_predicate *hottest; + basic_block hottest_bb; + unsigned int loop_size = init_loop_unswitch_info (loop, hottest, + hottest_bb); unsigned int budget = loop_size + param_max_unswitch_insns; predicate_vector predicate_path; @@ -338,7 +355,8 @@ tree_ssa_unswitch_loops (function *fun) changed_unswitch |= tree_unswitch_single_loop (loop, loc, predicate_path, loop_size, budget, - ignored_edge_flag, handled); + ignored_edge_flag, handled, + hottest, hottest_bb); predicate_path.release (); for (auto predlist : bb_predicates) @@ -449,7 +467,9 @@ is_maybe_undefined (const tree name, gimple *stmt, class loop *loop) static void find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, - vec &candidates) + vec &candidates, + unswitch_predicate *&hottest, + basic_block &hottest_bb) { gimple *last, *def; tree use; @@ -489,6 +509,14 @@ find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, unswitch_predicate *predicate = new unswitch_predicate (stmt); candidates.safe_push (predicate); + /* If we unswitch on this predicate we isolate both paths, so + pick the highest count for updating of the hottest predicate + to unswitch on first. */ + if (!hottest || predicate->count > hottest->count) + { + hottest = predicate; + hottest_bb = bb; + } } else if (gswitch *stmt = safe_dyn_cast (last)) { @@ -561,6 +589,11 @@ find_unswitching_predicates_for_bb (basic_block bb, class loop *loop, edge_index, e, edge_range[edge_index]); candidates.safe_push (predicate); + if (!hottest || predicate->count > hottest->count) + { + hottest = predicate; + hottest_bb = bb; + } } } } @@ -888,13 +921,15 @@ evaluate_loop_insns_for_predicate (class loop *loop, /* Unswitch single LOOP. PREDICATE_PATH contains so far used predicates for unswitching. BUDGET is number of instruction for which we can increase - the loop and is updated when unswitching occurs. */ + the loop and is updated when unswitching occurs. If HOTTEST is not + NULL then pick this candidate as the one to unswitch on. */ static bool tree_unswitch_single_loop (class loop *loop, dump_user_location_t loc, predicate_vector &predicate_path, unsigned loop_size, unsigned &budget, - int ignored_edge_flag, bitmap handled) + int ignored_edge_flag, bitmap handled, + unswitch_predicate *hottest, basic_block hottest_bb) { class loop *nloop; bool changed = false; @@ -939,8 +974,15 @@ tree_unswitch_single_loop (class loop *loop, dump_user_location_t loc, } return false; }; - /* Check predicates of reachable blocks. */ - evaluate_bbs (loop, NULL, ignored_edge_flag, check_predicates); + + if (hottest) + { + predicate = hottest; + predicate_bb = hottest_bb; + } + else + /* Check predicates of reachable blocks. */ + evaluate_bbs (loop, NULL, ignored_edge_flag, check_predicates); if (predicate != NULL) { -- Gitee From 0a168dac8527629454ac83761220a6142a865a69 Mon Sep 17 00:00:00 2001 From: Richard Biener Date: Mon, 28 Nov 2022 09:19:33 +0100 Subject: [PATCH 07/10] [Backport] tree-optimization/107876: Fix switch unswitching Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=238cf114de16518c6569f0cdd2c4d6211ac98a74 The following shows a missed update of dominators when unswitching removes unreachable edges from switch stmts it unswitches. Fixed by wiping dominator info in that case. PR tree-optimization/107876 * tree-ssa-loop-unswitch.cc (clean_up_after_unswitching): Wipe dominator info if we removed an edge. * g++.dg/tree-ssa/pr107876.C: New testcase. (cherry picked from commit 238cf114de16518c6569f0cdd2c4d6211ac98a74) Signed-off-by: Zhongjie Guo (cherry picked from commit 40306f6c7e4f6e62fb69eb62ba009b61ff319ca3) --- gcc/testsuite/g++.dg/tree-ssa/pr107876.C | 38 ++++++++++++++++++++++++ gcc/tree-ssa-loop-unswitch.cc | 10 ++++++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/tree-ssa/pr107876.C diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr107876.C b/gcc/testsuite/g++.dg/tree-ssa/pr107876.C new file mode 100644 index 00000000000..6cff2f1b166 --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/pr107876.C @@ -0,0 +1,38 @@ +// { dg-do compile } +// { dg-require-effective-target c++11 } +// { dg-options "-O2 -funswitch-loops --param max-unswitch-insns=5 -fdump-tree-unswitch-details" } + +class X { +public: + X(); + X(const X&); + X(const volatile X &); + ~X(); +}; + +X test17(int i) { + if (false) { + impossible: + if (i == 3) + return X(); + } + + while (true) { + X x; + if (i == 0) + return x; + if (i == 1) + break; + if (i == 2) + continue; + if (i == 3) + goto impossible; + if (i == 4) + __builtin_exit(1); + if (i == 5) + return x; + } + return X(); +} + +// { dg-final { scan-tree-dump "unswitching loop 1 on .switch. with condition: i_\[0-9\]+\\(D\\) == 2" "unswitch" } } diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index 0da6d01012a..b56329bc747 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -1588,6 +1588,7 @@ clean_up_after_unswitching (int ignored_edge_flag) basic_block bb; edge e; edge_iterator ei; + bool removed_edge = false; FOR_EACH_BB_FN (bb, cfun) { @@ -1612,7 +1613,10 @@ clean_up_after_unswitching (int ignored_edge_flag) to preserve its edge. But we can remove the non-default CASE sharing the edge. */ if (e != default_e) - remove_edge (e); + { + remove_edge (e); + removed_edge = true; + } } else { @@ -1629,6 +1633,10 @@ clean_up_after_unswitching (int ignored_edge_flag) FOR_EACH_EDGE (e, ei, bb->succs) e->flags &= ~ignored_edge_flag; } + + /* If we removed an edge we possibly have to recompute dominators. */ + if (removed_edge) + free_dominance_info (CDI_DOMINATORS); } /* Loop unswitching pass. */ -- Gitee From 73ebd1a2a74d22296cde6d368a178c93366020fa Mon Sep 17 00:00:00 2001 From: Zhongjie Guo Date: Thu, 6 Aug 2026 19:21:57 +0800 Subject: [PATCH 08/10] [Bugfix] Honor unswitch budget for hottest predicate The hottest-first backport selected its initial predicate without computing the two specialized loop sizes. As a result it bypassed the normal growth budget and reported zero costs. Route both preselected and DFS predicates through the same cost and budget check. gcc/ChangeLog: * tree-ssa-loop-unswitch.cc (tree_unswitch_single_loop): Factor the per-predicate cost check and use it for the hottest predicate. gcc/testsuite/ChangeLog: * g++.dg/tree-ssa/pr107876-2.C: New test. Signed-off-by: Zhongjie Guo (cherry picked from commit b0966d226746588e8bb080d2e016e0a67d9b67cf) --- gcc/testsuite/g++.dg/tree-ssa/pr107876-2.C | 39 ++++++++++++ gcc/tree-ssa-loop-unswitch.cc | 69 +++++++++++----------- 2 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 gcc/testsuite/g++.dg/tree-ssa/pr107876-2.C diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr107876-2.C b/gcc/testsuite/g++.dg/tree-ssa/pr107876-2.C new file mode 100644 index 00000000000..80e226ec212 --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/pr107876-2.C @@ -0,0 +1,39 @@ +// { dg-do compile } +// { dg-require-effective-target c++11 } +// { dg-options "-O2 -funswitch-loops --param max-unswitch-insns=0 -fdump-tree-unswitch-details" } + +class X { +public: + X(); + X(const X&); + X(const volatile X &); + ~X(); +}; + +X test17(int i) { + if (false) { + impossible: + if (i == 3) + return X(); + } + + while (true) { + X x; + if (i == 0) + return x; + if (i == 1) + break; + if (i == 2) + continue; + if (i == 3) + goto impossible; + if (i == 4) + __builtin_exit(1); + if (i == 5) + return x; + } + return X(); +} + +// { dg-final { scan-tree-dump "not unswitching condition, cost too big" "unswitch" } } +// { dg-final { scan-tree-dump-not "unswitching loop" "unswitch" } } diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index b56329bc747..30d99ccf76b 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -937,49 +937,50 @@ tree_unswitch_single_loop (class loop *loop, dump_user_location_t loc, basic_block predicate_bb = NULL; unsigned true_size = 0, false_size = 0; - auto check_predicates = [&](basic_block bb) -> bool + auto check_predicate = [&](unswitch_predicate *pred, basic_block bb) -> bool { - for (auto pred : get_predicates_for_bb (bb)) + if (bitmap_bit_p (handled, pred->num)) + return false; + + evaluate_loop_insns_for_predicate (loop, predicate_path, + pred, ignored_edge_flag, + &true_size, &false_size); + + /* We'll get LOOP replaced with a simplified version according + to PRED estimated to TRUE_SIZE and a copy simplified according to + the inverted PRED estimated to FALSE_SIZE. */ + if (true_size + false_size < budget + loop_size) { - if (bitmap_bit_p (handled, pred->num)) - continue; + predicate = pred; + predicate_bb = bb; - evaluate_loop_insns_for_predicate (loop, predicate_path, - pred, ignored_edge_flag, - &true_size, &false_size); + /* There are cases where true_size and false_size add up to less + than the original loop_size. We do not want to grow the + remaining budget because of that. */ + if (true_size + false_size > loop_size) + budget -= (true_size + false_size - loop_size); - /* We'll get LOOP replaced with a simplified version according - to PRED estimated to TRUE_SIZE and a copy simplified - according to the inverted PRED estimated to FALSE_SIZE. */ - if (true_size + false_size < budget + loop_size) - { - predicate = pred; - predicate_bb = bb; - - /* There are cases where true_size and false_size add up to - less than the original loop_size. We do not want to - grow the remaining budget because of that. */ - if (true_size + false_size > loop_size) - budget -= (true_size + false_size - loop_size); - - /* FIXME: right now we select first candidate, but we can - choose the cheapest or hottest one. */ - return true; - } - else if (dump_enabled_p ()) - dump_printf_loc (MSG_NOTE, loc, - "not unswitching condition, cost too big " - "(%u insns copied to %u and %u)\n", loop_size, - true_size, false_size); + return true; } + else if (dump_enabled_p ()) + dump_printf_loc (MSG_NOTE, loc, + "not unswitching condition, cost too big " + "(%u insns copied to %u and %u)\n", loop_size, + true_size, false_size); + return false; }; - if (hottest) + auto check_predicates = [&](basic_block bb) -> bool { - predicate = hottest; - predicate_bb = hottest_bb; - } + for (auto pred : get_predicates_for_bb (bb)) + if (check_predicate (pred, bb)) + return true; + return false; + }; + + if (hottest) + check_predicate (hottest, hottest_bb); else /* Check predicates of reachable blocks. */ evaluate_bbs (loop, NULL, ignored_edge_flag, check_predicates); -- Gitee From 0ad64b3f9547b4dec1d5b81028e5546b98e723d4 Mon Sep 17 00:00:00 2001 From: Richard Biener Date: Mon, 23 Oct 2023 11:25:17 +0200 Subject: [PATCH 09/10] [Backport] tree-optimization/111917: Fix bogus IL after guard hoisting Reference: https://gcc.gnu.org/cgit/gcc/commit/?id=d96bd4aade170fcd86f5f09b68b770dde798e631 The unswitching code to hoist guards inserts conditions in wrong places. The following fixes this, simplifying code. PR tree-optimization/111917 * tree-ssa-loop-unswitch.cc (hoist_guard): Always insert new conditional after last stmt. * gcc.dg/torture/pr111917.c: New testcase. (cherry picked from commit d96bd4aade170fcd86f5f09b68b770dde798e631) Signed-off-by: Zhongjie Guo (cherry picked from commit cce30d1e3f3f0916c9ad77d96a7600d938a8ec26) --- gcc/testsuite/gcc.dg/torture/pr111917.c | 23 +++++++++++++++++++++++ gcc/tree-ssa-loop-unswitch.cc | 5 +---- 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/torture/pr111917.c diff --git a/gcc/testsuite/gcc.dg/torture/pr111917.c b/gcc/testsuite/gcc.dg/torture/pr111917.c new file mode 100644 index 00000000000..532e30200b5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/torture/pr111917.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-additional-options "-funswitch-loops" } */ + +long t; +long a() { + long b = t, c = t; + for (; b < 31; b++) + c <<= 1; + return c; +} +long t1; +static +int d() { + if (!t1) + return 0; +e: +f: + for (; a();) + ; + goto f; + return 0; +} +int main() { d(); } diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc index 30d99ccf76b..303ec9d3b71 100644 --- a/gcc/tree-ssa-loop-unswitch.cc +++ b/gcc/tree-ssa-loop-unswitch.cc @@ -1410,10 +1410,7 @@ hoist_guard (class loop *loop, edge guard) cond_stmt = as_a (stmt); extract_true_false_edges_from_block (guard_bb, &te, &fe); /* Insert guard to PRE_HEADER. */ - if (!empty_block_p (pre_header)) - gsi = gsi_last_bb (pre_header); - else - gsi = gsi_start_bb (pre_header); + gsi = gsi_last_bb (pre_header); /* Create copy of COND_STMT. */ new_cond_stmt = gimple_build_cond (gimple_cond_code (cond_stmt), gimple_cond_lhs (cond_stmt), -- Gitee From 89036980e19453347143a10818e64e1e9cafa17d Mon Sep 17 00:00:00 2001 From: Zhongjie Guo Date: Thu, 6 Aug 2026 16:23:08 +0800 Subject: [PATCH 10/10] [Testsuite] Add runtime coverage for switch loop unswitching Add an execution test covering signed ranges, unsigned case values, fallthrough edges, and default edges after switch-aware loop unswitching. gcc/testsuite/ChangeLog: * gcc.dg/loop-unswitch-18.c: New test. Signed-off-by: Zhongjie Guo (cherry picked from commit f3e378d6b1838be1162142ac2ac934c2396ed6b5) --- gcc/testsuite/gcc.dg/loop-unswitch-18.c | 101 ++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/loop-unswitch-18.c diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-18.c b/gcc/testsuite/gcc.dg/loop-unswitch-18.c new file mode 100644 index 00000000000..e3451889a85 --- /dev/null +++ b/gcc/testsuite/gcc.dg/loop-unswitch-18.c @@ -0,0 +1,101 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -funswitch-loops -fno-thread-jumps -fdump-tree-unswitch-details" } */ + +volatile int effects; + +__attribute__ ((noinline, noclone)) +static int +record (int value) +{ + effects++; + return value; +} + +__attribute__ ((noipa)) +static int +signed_switch (int selector) +{ + int result = 0; + + for (int i = 0; i != 8; ++i) + switch (selector) + { + case -3 ... -1: + result += record (i); + break; + case 0: + result += 7; + /* Fall through. */ + case 2: + result += record (2 * i); + break; + default: + result -= record (i); + break; + } + + return result; +} + +__attribute__ ((noipa)) +static unsigned +unsigned_switch (unsigned selector) +{ + unsigned result = 0; + + for (unsigned i = 0; i != 8; ++i) + switch (selector) + { + case 0: + result += record (3); + break; + case 1 ... 3: + result += record (i + 1); + break; + case (unsigned) -1: + result += record (2); + break; + default: + result += record (5); + break; + } + + return result; +} + +static void +check_signed (int selector, int expected) +{ + effects = 0; + if (signed_switch (selector) != expected || effects != 8) + __builtin_abort (); +} + +static void +check_unsigned (unsigned selector, unsigned expected) +{ + effects = 0; + if (unsigned_switch (selector) != expected || effects != 8) + __builtin_abort (); +} + +int +main (void) +{ + check_signed (-4, -28); + check_signed (-3, 28); + check_signed (-1, 28); + check_signed (0, 112); + check_signed (1, -28); + check_signed (2, 56); + + check_unsigned (0, 24); + check_unsigned (1, 36); + check_unsigned (3, 36); + check_unsigned (4, 40); + check_unsigned ((unsigned) -1, 16); + return 0; +} + +/* Verify that the runtime checks exercise switch-aware unswitching. */ +/* { dg-final { scan-tree-dump "unswitching loop . on .switch. with condition" "unswitch" } } */ -- Gitee