From c9ccc3961f5b8d333f5081b377cd9ee9e33079f7 Mon Sep 17 00:00:00 2001 From: Andi Kleen <ak@gcc.gnu.org> Date: Thu, 1 Aug 2024 20:10:27 -0700 Subject: [PATCH] Support if conversion for switches The gimple-if-to-switch pass converts if statements with multiple equal checks on the same value to a switch. This breaks vectorization which cannot handle switches. Teach the tree-if-conv pass used by the vectorizer to handle simple switch statements, like those created by if-to-switch earlier. These are switches that only have a single non default block, They are handled similar to COND in if conversion. This makes the vect-bitfield-read-1-not test fail. The test checks for a bitfield analysis failing, but it actually relied on the ifcvt erroring out early because the test is using a switch. The if conversion still does not work because the switch is not in a form that this patch can handle, but it fails much later and the bitfield analysis succeeds, which makes the test fail. I marked it xfail because it doesn't seem to be testing what it wants to test. PR tree-optimization/115866 gcc/ChangeLog: * tree-if-conv.cc (if_convertible_switch_p): New function. (if_convertible_stmt_p): Check for switch. (get_loop_body_in_if_conv_order): Handle switch. (predicate_bbs): Likewise. (predicate_statements): Likewise. (remove_conditions_and_labels): Likewise. (ifcvt_split_critical_edges): Likewise. (ifcvt_local_dce): Likewise. gcc/testsuite/ChangeLog: * gcc.dg/vect/vect-switch-ifcvt-1.c: New test. * gcc.dg/vect/vect-switch-ifcvt-2.c: New test. * gcc.dg/vect/vect-switch-search-line-fast.c: New test. * gcc.dg/vect/vect-bitfield-read-1-not.c: Change to xfail. --- .../gcc.dg/vect/vect-bitfield-read-1-not.c | 2 +- .../gcc.dg/vect/vect-switch-ifcvt-1.c | 115 ++++++++++++++++++ .../gcc.dg/vect/vect-switch-ifcvt-2.c | 49 ++++++++ .../vect/vect-switch-search-line-fast.c | 17 +++ gcc/tree-if-conv.cc | 93 +++++++++++++- 5 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-1.c create mode 100644 gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-2.c create mode 100644 gcc/testsuite/gcc.dg/vect/vect-switch-search-line-fast.c diff --git a/gcc/testsuite/gcc.dg/vect/vect-bitfield-read-1-not.c b/gcc/testsuite/gcc.dg/vect/vect-bitfield-read-1-not.c index 0d91067ebb27..85f4de8464a5 100644 --- a/gcc/testsuite/gcc.dg/vect/vect-bitfield-read-1-not.c +++ b/gcc/testsuite/gcc.dg/vect/vect-bitfield-read-1-not.c @@ -55,6 +55,6 @@ int main (void) return 0; } -/* { dg-final { scan-tree-dump-not "Bitfield OK to lower." "ifcvt" } } */ +/* { dg-final { scan-tree-dump-times "Bitfield OK to lower." 0 "ifcvt" { xfail *-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-1.c b/gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-1.c new file mode 100644 index 000000000000..f5352ef8ed7a --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-1.c @@ -0,0 +1,115 @@ +/* { dg-require-effective-target vect_int } */ +#include "tree-vect.h" + +extern void abort (void); + +int +f1 (char *s) +{ + int c = 0; + int i; + for (i = 0; i < 64; i++) + { + switch (*s) + { + case ',': + case '|': + c++; + } + s++; + } + return c; +} + +int +f2 (char *s) +{ + int c = 0; + int i; + for (i = 0; i < 64; i++) + { + if (*s != '#') + { + switch (*s) + { + case ',': + case '|': + c++; + } + } + s++; + } + return c; +} + +int +f3 (char *s) +{ + int c = 0; + int i; + for (i = 0; i < 64; i++) + { + if (*s != '#') + if (*s == ',' || *s == '|' || *s == '@' || *s == '*') + c++; + s++; + } + return c; +} + + +int +f4 (char *s) +{ + int c = 0; + int i; + for (i = 0; i < 64; i++) + { + if (*s == ',' || *s == '|' || *s == '@' || *s == '*') + c++; + s++; + } + return c; +} + +#define CHECK(f, str, res) \ + __builtin_strcpy(buf, str); n = f(buf); if (n != res) abort(); + +int +main () +{ + int n; + char buf[64]; + + __builtin_memset (buf, 0, sizeof buf); + + check_vect (); + + CHECK (f1, ",,,,,,,,,,", 10); + CHECK (f1, "||||||||||", 10); + CHECK (f1, "aaaaaaaaaa", 0); + CHECK (f1, "", 0); + CHECK (f1, ",|,|xxxxxx", 4); + + CHECK (f2, ",,,,,,,,,,", 10); + CHECK (f2, "||||||||||", 10); + CHECK (f2, "aaaaaaaaaa", 0); + CHECK (f2, "", 0); + CHECK (f2, ",|,|xxxxxx", 4); + + CHECK (f3, ",,,,,,,,,,", 10); + CHECK (f3, "||||||||||", 10); + CHECK (f3, "aaaaaaaaaa", 0); + CHECK (f3, "", 0); + CHECK (f3, ",|,|xxxxxx", 4); + + CHECK (f4, ",,,,,,,,,,", 10); + CHECK (f4, "||||||||||", 10); + CHECK (f4, "aaaaaaaaaa", 0); + CHECK (f4, "", 0); + CHECK (f4, ",|,|xxxxxx", 4); + + return 0; +} + +/* { dg-final { scan-tree-dump-times "vectorized 1 loops" 4 "vect" } } */ diff --git a/gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-2.c b/gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-2.c new file mode 100644 index 000000000000..f0fcb43e1770 --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/vect-switch-ifcvt-2.c @@ -0,0 +1,49 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target vect_int } */ + +/* Check for cases currently not supported by switch tree if conversion. */ + +int +f1 (char *s) +{ + int c = 0; + int i; + for (i = 0; i < 64; i++) + { + switch (*s) + { + case ',': + case '|': + c++; + break; + case '^': + c += 2; + break; + } + s++; + } + return c; +} + +int +f2 (char *s) +{ + int c = 0; + int i; + for (i = 0; i < 64; i++) + { + switch (*s) + { + case ',': + case '|': + c++; + /*fallthrough*/ + default: + c+=2; + } + s++; + } + return c; +} + +/* { dg-final { scan-tree-dump-times "vectorized 1 loops" 0 "vect" } } */ diff --git a/gcc/testsuite/gcc.dg/vect/vect-switch-search-line-fast.c b/gcc/testsuite/gcc.dg/vect/vect-switch-search-line-fast.c new file mode 100644 index 000000000000..15f3a4ef38a7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/vect-switch-search-line-fast.c @@ -0,0 +1,17 @@ +/* PR116126 -- once this works use this version in libcpp/lex.c. + This also requires working value range propagation for s/end. */ +/* { dg-do compile } */ +/* { dg-require-effective-target vect_int } */ + +const unsigned char *search_line_fast2 (const unsigned char *s, + const unsigned char *end) +{ + while (s < end) { + if (*s == '\n' || *s == '\r' || *s == '\\' || *s == '?') + break; + s++; + } + return s; +} + +/* { dg-final { scan-tree-dump-times "vectorized 1 loops" 1 "vect" { xfail *-*-* } } } */ diff --git a/gcc/tree-if-conv.cc b/gcc/tree-if-conv.cc index 57992b6decaf..5aac65cfbd18 100644 --- a/gcc/tree-if-conv.cc +++ b/gcc/tree-if-conv.cc @@ -124,6 +124,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-vectorizer.h" #include "tree-eh.h" #include "cgraph.h" +#include "print-tree.h" /* For lang_hooks.types.type_for_mode. */ #include "langhooks.h" @@ -1082,11 +1083,35 @@ if_convertible_gimple_assign_stmt_p (gimple *stmt, return true; } +/* Return true when SW switch statement is equivalent to cond, that + all non default labels point to the same label. + + Fallthrough is not checked for and could even happen + with cond (using goto), so is handled. + + This is intended for switches created by the if-switch-conversion + pass, but can handle some programmer supplied cases too. */ + +static bool +if_convertible_switch_p (gswitch *sw) +{ + if (gimple_switch_num_labels (sw) <= 1) + return false; + tree label = CASE_LABEL (gimple_switch_label (sw, 1)); + for (unsigned i = 1; i < gimple_switch_num_labels (sw); i++) + { + if (CASE_LABEL (gimple_switch_label (sw, i)) != label) + return false; + } + return true; +} + /* Return true when STMT is if-convertible. A statement is if-convertible if: - it is an if-convertible GIMPLE_ASSIGN, - it is a GIMPLE_LABEL or a GIMPLE_COND, + - it is a switch equivalent to COND - it is builtins call, - it is a call to a function with a SIMD clone. */ @@ -1100,6 +1125,9 @@ if_convertible_stmt_p (gimple *stmt, vec<data_reference_p> refs) case GIMPLE_COND: return true; + case GIMPLE_SWITCH: + return if_convertible_switch_p (as_a <gswitch *> (stmt)); + case GIMPLE_ASSIGN: return if_convertible_gimple_assign_stmt_p (stmt, refs); @@ -1327,6 +1355,7 @@ get_loop_body_in_if_conv_order (const class loop *loop) case GIMPLE_CALL: case GIMPLE_DEBUG: case GIMPLE_COND: + case GIMPLE_SWITCH: gimple_set_uid (gsi_stmt (gsi), 0); break; default: @@ -1426,6 +1455,58 @@ predicate_bbs (loop_p loop) cond = NULL_TREE; } + /* Assumes the limited COND like switches checked for earlier. */ + else if (gswitch *sw = safe_dyn_cast <gswitch *> (*gsi_last_bb (bb))) + { + location_t loc = gimple_location (*gsi_last_bb (bb)); + + tree default_label = CASE_LABEL (gimple_switch_default_label (sw)); + tree cond_label = CASE_LABEL (gimple_switch_label (sw, 1)); + + edge false_edge = find_edge (bb, label_to_block (cfun, default_label)); + edge true_edge = find_edge (bb, label_to_block (cfun, cond_label)); + + /* Create chain of switch tests for each case. */ + tree switch_cond = NULL_TREE; + tree index = gimple_switch_index (sw); + for (unsigned i = 1; i < gimple_switch_num_labels (sw); i++) + { + tree label = gimple_switch_label (sw, i); + tree case_cond; + if (CASE_HIGH (label)) + { + tree low = build2_loc (loc, GE_EXPR, + boolean_type_node, + index, CASE_LOW (label)); + tree high = build2_loc (loc, LE_EXPR, + boolean_type_node, + index, CASE_HIGH (label)); + case_cond = build2_loc (loc, TRUTH_AND_EXPR, + boolean_type_node, + low, high); + } + else + case_cond = build2_loc (loc, EQ_EXPR, + boolean_type_node, + index, + CASE_LOW (gimple_switch_label (sw, i))); + if (i > 1) + switch_cond = build2_loc (loc, TRUTH_OR_EXPR, + boolean_type_node, + case_cond, switch_cond); + else + switch_cond = case_cond; + } + + add_to_dst_predicate_list (loop, true_edge, unshare_expr (cond), + unshare_expr (switch_cond)); + switch_cond = build1_loc (loc, TRUTH_NOT_EXPR, boolean_type_node, + unshare_expr (switch_cond)); + add_to_dst_predicate_list (loop, false_edge, + unshare_expr (cond), switch_cond); + cond = NULL_TREE; + } + /* If current bb has only one successor, then consider it as an unconditional goto. */ if (single_succ_p (bb)) @@ -2852,9 +2933,9 @@ predicate_statements (loop_p loop) } } -/* Remove all GIMPLE_CONDs and GIMPLE_LABELs of all the basic blocks - other than the exit and latch of the LOOP. Also resets the - GIMPLE_DEBUG information. */ +/* Remove all GIMPLE_CONDs and GIMPLE_LABELs and GIMPLE_SWITCH of all + the basic blocks other than the exit and latch of the LOOP. Also + resets the GIMPLE_DEBUG information. */ static void remove_conditions_and_labels (loop_p loop) @@ -2875,6 +2956,7 @@ remove_conditions_and_labels (loop_p loop) { case GIMPLE_COND: case GIMPLE_LABEL: + case GIMPLE_SWITCH: gsi_remove (&gsi, true); break; @@ -3265,7 +3347,8 @@ ifcvt_split_critical_edges (class loop *loop, bool aggressive_if_conv) continue; /* Skip basic blocks not ending with conditional branch. */ - if (!safe_is_a <gcond *> (*gsi_last_bb (bb))) + if (!safe_is_a <gcond *> (*gsi_last_bb (bb)) + && !safe_is_a <gswitch *> (*gsi_last_bb (bb))) continue; FOR_EACH_EDGE (e, ei, bb->succs) @@ -3330,7 +3413,7 @@ ifcvt_local_dce (class loop *loop) continue; } code = gimple_code (stmt); - if (code == GIMPLE_COND || code == GIMPLE_CALL) + if (code == GIMPLE_COND || code == GIMPLE_CALL || code == GIMPLE_SWITCH) { gimple_set_plf (stmt, GF_PLF_2, true); worklist.safe_push (stmt); -- GitLab