Hi, I updated my patch based on all your comments.
the major changes are the following: 1. replace the candidate calls with __builtin_str(n)cmp_eq instead of __builtin_memcmp_eq; in builtins.c, when expanding the new __builtin_str(n)cmp_eq call, expand them first as __builtin_memcmp_eq, if Not succeed, change the call back to __builtin_str(n)cmp. 2. change the call to “get_range_strlen” with “compute_objsize”. 3. add the missing case for equality checking with zero; 4. adjust the new testing case for PR83026; add a new testing case for the missing case added in 3. 5. update “uhwi” to “shwi” for where it needs; 6. some other minor format changes. the changes are retested on x86 and aarch64, bootstrapped and regression tested. no issue. Okay for trunk? thanks. Qing Please see the updated patch: gcc/ChangeLog: +2017-12-21 Qing Zhao <qing.z...@oracle.com> + + PR middle-end/78809 + PR middle-end/83026 + * builtins.c (expand_builtin): Add the handling of BUILT_IN_STRCMP_EQ + and BUILT_IN_STRNCMP_EQ. + * builtins.def: Add new builtins BUILT_IN_STRCMP_EQ and + BUILT_IN_STRNCMP_EQ. + * tree-ssa-strlen.c (compute_string_length): New function. + (handle_builtin_string_cmp): New function to handle calls to + string compare functions. + (strlen_optimize_stmt): Add handling to builtin string compare + calls. + * tree.c (build_common_builtin_nodes): Add new defines of + BUILT_IN_STRNCMP_EQ and BUILT_IN_STRCMP_EQ. + gcc/testsuite/ChangeLog +2017-12-21 Qing Zhao <qing.z...@oracle.com> + + PR middle-end/78809 + * gcc.dg/strcmpopt_2.c: New testcase. + * gcc.dg/strcmpopt_3.c: New testcase. + + PR middle-end/83026 + * gcc.dg/strcmpopt_3.c: New testcase. + --- gcc/builtins.c | 33 ++++++ gcc/builtins.def | 5 + gcc/testsuite/gcc.dg/strcmpopt_2.c | 67 ++++++++++++ gcc/testsuite/gcc.dg/strcmpopt_3.c | 31 ++++++ gcc/testsuite/gcc.dg/strcmpopt_4.c | 16 +++ gcc/tree-ssa-strlen.c | 215 +++++++++++++++++++++++++++++++++++++ gcc/tree.c | 8 ++ 7 files changed, 375 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_2.c create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_3.c create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_4.c diff --git a/gcc/builtins.c b/gcc/builtins.c index 6b25253..a5f6885 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -6953,12 +6953,45 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, return target; break; + /* expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it + back to a BUILT_IN_STRCMP. Remember to delete the 3rd paramater + when changing it to a strcmp call. */ + case BUILT_IN_STRCMP_EQ: + target = expand_builtin_memcmp (exp, target, true); + if (target) + return target; + + /* change this call back to a BUILT_IN_STRCMP. */ + TREE_OPERAND (exp, 1) + = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRCMP)); + + /* delete the last parameter. */ + unsigned int i; + vec<tree, va_gc> *arg_vec; + vec_alloc (arg_vec, 2); + for (i = 0; i < 2; i++) + arg_vec->quick_push (CALL_EXPR_ARG (exp, i)); + exp = build_call_vec (TREE_TYPE (exp), CALL_EXPR_FN (exp), arg_vec); + /* FALLTHROUGH */ + case BUILT_IN_STRCMP: target = expand_builtin_strcmp (exp, target); if (target) return target; break; + /* expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it + back to a BUILT_IN_STRNCMP. */ + case BUILT_IN_STRNCMP_EQ: + target = expand_builtin_memcmp (exp, target, true); + if (target) + return target; + + /* change it back to a BUILT_IN_STRNCMP. */ + TREE_OPERAND (exp, 1) + = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRNCMP)); + /* FALLTHROUGH */ + case BUILT_IN_STRNCMP: target = expand_builtin_strncmp (exp, target, mode); if (target) diff --git a/gcc/builtins.def b/gcc/builtins.def index 2281021..9eb79fd 100644 --- a/gcc/builtins.def +++ b/gcc/builtins.def @@ -949,6 +949,11 @@ DEF_BUILTIN_STUB (BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX, "__builtin_alloca_with_ali equality with zero. */ DEF_BUILTIN_STUB (BUILT_IN_MEMCMP_EQ, "__builtin_memcmp_eq") +/* An internal version of strcmp/strncmp, used when the result is only + tested for equality with zero. */ +DEF_BUILTIN_STUB (BUILT_IN_STRCMP_EQ, "__builtin_strcmp_eq") +DEF_BUILTIN_STUB (BUILT_IN_STRNCMP_EQ, "__builtin_strncmp_eq") + /* Object size checking builtins. */ DEF_GCC_BUILTIN (BUILT_IN_OBJECT_SIZE, "object_size", BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST) DEF_EXT_LIB_BUILTIN_CHKP (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) diff --git a/gcc/testsuite/gcc.dg/strcmpopt_2.c b/gcc/testsuite/gcc.dg/strcmpopt_2.c new file mode 100644 index 0000000..0131b8f --- /dev/null +++ b/gcc/testsuite/gcc.dg/strcmpopt_2.c @@ -0,0 +1,67 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -fdump-tree-strlen" } */ + +char s[100] = {'a','b','c','d'}; +typedef struct { char s[8]; int x; } S; + +__attribute__ ((noinline)) int +f1 (S *s) +{ + return __builtin_strcmp (s->s, "abc") != 0; +} + +__attribute__ ((noinline)) int +f2 (void) +{ + return __builtin_strcmp (s, "abc") != 0; +} + +__attribute__ ((noinline)) int +f3 (S *s) +{ + return __builtin_strcmp ("abc", s->s) != 0; +} + +__attribute__ ((noinline)) int +f4 (void) +{ + return __builtin_strcmp ("abc", s) != 0; +} + +__attribute__ ((noinline)) int +f5 (S *s) +{ + return __builtin_strncmp (s->s, "abc", 3) != 0; +} + +__attribute__ ((noinline)) int +f6 (void) +{ + return __builtin_strncmp (s, "abc", 2) != 0; +} + +__attribute__ ((noinline)) int +f7 (S *s) +{ + return __builtin_strncmp ("abc", s->s, 3) != 0; +} + +__attribute__ ((noinline)) int +f8 (void) +{ + return __builtin_strncmp ("abc", s, 2) != 0; +} + +int main (void) +{ + S ss = {{'a','b','c'}, 2}; + + if (f1 (&ss) != 0 || f2 () != 1 || f3 (&ss) != 0 || + f4 () != 1 || f5 (&ss) != 0 || f6 () != 0 || + f7 (&ss) != 0 || f8 () != 0) + __builtin_abort (); + + return 0; +} + +/* { dg-final { scan-tree-dump-times "cmp_eq \\(" 8 "strlen" } } */ diff --git a/gcc/testsuite/gcc.dg/strcmpopt_3.c b/gcc/testsuite/gcc.dg/strcmpopt_3.c new file mode 100644 index 0000000..86a0d7a --- /dev/null +++ b/gcc/testsuite/gcc.dg/strcmpopt_3.c @@ -0,0 +1,31 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -fdump-tree-strlen" } */ + +__attribute__ ((noinline)) int +f1 (void) +{ + char *s0= "abcd"; + char s[8]; + __builtin_strcpy (s, s0); + return __builtin_strcmp(s, "abc") != 0; +} + +__attribute__ ((noinline)) int +f2 (void) +{ + char *s0 = "ab"; + char s[8]; + __builtin_strcpy (s, s0); + return __builtin_strcmp("abc", s) != 0; +} + +int main (void) +{ + if (f1 () != 1 + || f2 () != 1) + __builtin_abort (); + + return 0; +} + +/* { dg-final { scan-tree-dump-times "strcmp" 0 "strlen" } } */ diff --git a/gcc/testsuite/gcc.dg/strcmpopt_4.c b/gcc/testsuite/gcc.dg/strcmpopt_4.c new file mode 100644 index 0000000..d727bc3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/strcmpopt_4.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-strlen" } */ + +typedef struct { char s[8]; int x; } S; +extern int max_i; + +int +f1 (S * s) +{ + int result, i; + for (i = 0; i < max_i; i++) + result += __builtin_strcmp (s->s, "abc") != 0 ? 2 : 1; + return result; +} + +/* { dg-final { scan-tree-dump-times "cmp_eq \\(" 1 "strlen" } } */ diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index 94f20ef..57563ef 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -2540,6 +2540,216 @@ handle_builtin_memcmp (gimple_stmt_iterator *gsi) return false; } +/* Given an index to the strinfo vector, compute the string length for the + corresponding string. Return -1 when unknown. */ + +static HOST_WIDE_INT +compute_string_length (int idx) +{ + HOST_WIDE_INT string_leni = -1; + gcc_assert (idx != 0); + + if (idx < 0) + string_leni = ~idx; + else + { + strinfo *si = get_strinfo (idx); + if (si) + { + tree const_string_len = get_string_length (si); + if (const_string_len && tree_fits_shwi_p (const_string_len)) + string_leni = tree_to_shwi (const_string_len); + } + } + + if (string_leni < 0) + return -1; + + return string_leni; +} + +/* Handle a call to strcmp or strncmp. When the result is ONLY used to do + equality test against zero: + + A. When both arguments are constant strings and it's a strcmp: + * if the length of the strings are NOT equal, we can safely fold the call + to a non-zero value. + * otherwise, do nothing now. + + B. When one of the arguments is constant string, try to replace the call with + a __builtin_str(n)cmp_eq call where possible, i.e: + + strncmp (s, STR, C) (!)= 0 in which, s is a pointer to a string, STR is a + constant string, C is a constant. + if (C <= strlen(STR) && sizeof_array(s) > C) + { + replace this call with + strncmp_eq (s, STR, C) (!)= 0 + } + if (C > strlen(STR) + { + it can be safely treated as a call to strcmp (s, STR) (!)= 0 + can handled by the following strcmp. + } + + strcmp (s, STR) (!)= 0 in which, s is a pointer to a string, STR is a + constant string. + if (sizeof_array(s) > strlen(STR)) + { + replace this call with + strcmp_eq (s, STR, strlen(STR)+1) (!)= 0 + } + */ + +static bool +handle_builtin_string_cmp (gimple_stmt_iterator *gsi) +{ + gcall *stmt = as_a <gcall *> (gsi_stmt (*gsi)); + tree res = gimple_call_lhs (stmt); + use_operand_p use_p; + imm_use_iterator iter; + tree arg1 = gimple_call_arg (stmt, 0); + tree arg2 = gimple_call_arg (stmt, 1); + int idx1 = get_stridx (arg1); + int idx2 = get_stridx (arg2); + HOST_WIDE_INT length = -1; + bool is_ncmp = false; + + if (!res) + return true; + + /* When both arguments are unknown, do nothing. */ + if (idx1 == 0 && idx2 == 0) + return true; + + /* Handle strncmp function. */ + if (gimple_call_num_args (stmt) == 3) + { + tree len = gimple_call_arg (stmt, 2); + if (tree_fits_shwi_p (len)) + length = tree_to_shwi (len); + + is_ncmp = true; + } + + /* For strncmp, if the length argument is NOT known, do nothing. */ + if (is_ncmp && length < 0) + return true; + + /* When the result is ONLY used to do equality test against zero. */ + FOR_EACH_IMM_USE_FAST (use_p, iter, res) + { + gimple *use_stmt = USE_STMT (use_p); + + if (is_gimple_debug (use_stmt)) + continue; + if (gimple_code (use_stmt) == GIMPLE_ASSIGN) + { + tree_code code = gimple_assign_rhs_code (use_stmt); + if (code == COND_EXPR) + { + tree cond_expr = gimple_assign_rhs1 (use_stmt); + if ((TREE_CODE (cond_expr) != EQ_EXPR + && (TREE_CODE (cond_expr) != NE_EXPR)) + || !integer_zerop (TREE_OPERAND (cond_expr, 1))) + return true; + } + else if (code == EQ_EXPR || code == NE_EXPR) + { + if (!integer_zerop (gimple_assign_rhs2 (use_stmt))) + return true; + } + else + return true; + } + else if (gimple_code (use_stmt) == GIMPLE_COND) + { + tree_code code = gimple_cond_code (use_stmt); + if ((code != EQ_EXPR && code != NE_EXPR) + || !integer_zerop (gimple_cond_rhs (use_stmt))) + return true; + } + else + return true; + } + + /* When both arguments are known, and their strlens are unequal, we can + safely fold the call to a non-zero value for strcmp; + othewise, do nothing now. */ + if (idx1 != 0 && idx2 != 0) + { + HOST_WIDE_INT const_string_leni1 = compute_string_length (idx1); + HOST_WIDE_INT const_string_leni2 = compute_string_length (idx2); + + if (!is_ncmp + && const_string_leni1 != -1 + && const_string_leni2 != -1 + && const_string_leni1 != const_string_leni2) + { + replace_call_with_value (gsi, integer_one_node); + return false; + } + return true; + } + + /* When one of args is constant string. */ + tree var_string = NULL_TREE; + HOST_WIDE_INT const_string_leni = -1; + + if (idx1) + { + const_string_leni = compute_string_length (idx1); + var_string = arg2; + } + else + { + gcc_checking_assert (idx2); + const_string_leni = compute_string_length (idx2); + var_string = arg1; + } + + if (const_string_leni < 0) + return true; + + unsigned HOST_WIDE_INT var_sizei = 0; + /* try to determine the minimum size of the object pointed by var_string. */ + tree size = compute_objsize (var_string, 2); + + if (!size) + return true; + + if (tree_fits_uhwi_p (size)) + var_sizei = tree_to_uhwi (size); + + if (var_sizei == 0) + return true; + + /* For strncmp, if length > const_string_leni , this call can be safely + transformed to a strcmp. */ + if (is_ncmp && length > const_string_leni) + is_ncmp = false; + + unsigned HOST_WIDE_INT final_length + = is_ncmp ? length : const_string_leni + 1; + + /* Replace strcmp or strncmp with the corresponding str(n)cmp_eq. */ + if (var_sizei > final_length) + { + tree fn + = (is_ncmp + ? builtin_decl_implicit (BUILT_IN_STRNCMP_EQ) + : builtin_decl_implicit (BUILT_IN_STRCMP_EQ)); + if (!fn) + return true; + tree const_string_len = build_int_cst (size_type_node, final_length); + update_gimple_call (gsi, fn, 3, arg1, arg2, const_string_len); + } + else + return true; + + return false; +} + /* Handle a POINTER_PLUS_EXPR statement. For p = "abcd" + 2; compute associated length, or if p = q + off is pointing to a '\0' character of a string, call @@ -2939,6 +3149,11 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi) if (!handle_builtin_memcmp (gsi)) return false; break; + case BUILT_IN_STRCMP: + case BUILT_IN_STRNCMP: + if (!handle_builtin_string_cmp (gsi)) + return false; + break; default: break; } diff --git a/gcc/tree.c b/gcc/tree.c index ed1852b..e23b3cd 100644 --- a/gcc/tree.c +++ b/gcc/tree.c @@ -10047,6 +10047,14 @@ build_common_builtin_nodes (void) "__builtin_memcmp_eq", ECF_PURE | ECF_NOTHROW | ECF_LEAF); + local_define_builtin ("__builtin_strncmp_eq", ftype, BUILT_IN_STRNCMP_EQ, + "__builtin_strncmp_eq", + ECF_PURE | ECF_NOTHROW | ECF_LEAF); + + local_define_builtin ("__builtin_strcmp_eq", ftype, BUILT_IN_STRCMP_EQ, + "__builtin_strcmp_eq", + ECF_PURE | ECF_NOTHROW | ECF_LEAF); + /* If there's a possibility that we might use the ARM EABI, build the alternate __cxa_end_cleanup node used to resume from C++. */ if (targetm.arm_eabi_unwinder) -- 1.9.1 > On Dec 14, 2017, at 2:45 PM, Jakub Jelinek <ja...@redhat.com> wrote: > > On Thu, Dec 14, 2017 at 01:45:21PM -0600, Qing Zhao wrote: >> 2017-12-11 Qing Zhao <qing.z...@oracle.com <mailto:qing.z...@oracle.com>> > > No " <mailto:qing.z...@oracle.com>" in ChangeLog entries please. > >> --- a/gcc/tree-ssa-strlen.c >> +++ b/gcc/tree-ssa-strlen.c >> @@ -2541,6 +2541,198 @@ handle_builtin_memcmp (gimple_stmt_iterator *gsi) >> return false; >> } >> >> +/* Given an index to the strinfo vector, compute the string length for the >> + corresponding string. Return -1 when unknown. */ >> + >> +static HOST_WIDE_INT >> +compute_string_length (int idx) >> +{ >> + HOST_WIDE_INT string_leni = -1; >> + gcc_assert (idx != 0); >> + >> + if (idx < 0) >> + string_leni = ~idx; >> + else >> + { >> + strinfo *si = get_strinfo (idx); >> + if (si) >> + { >> + tree const_string_len = get_string_length (si); >> + string_leni >> + = (const_string_len && tree_fits_uhwi_p (const_string_len) >> + ? tree_to_uhwi(const_string_len) : -1); > > So, you are returning a signed HWI, then clearly tree_fits_uhwi_p and > tree_to_uhwi are inappropriate, you should have used tree_fits_shwi_p > and tree_to_shwi. Space after function name is missing too. > And, as you start by initializing string_leni to -1, there is no > point to write it this way rather than > if (const_string_len && tree_fits_shwi_p (const_string_len)) > string_leni = tree_to_shwi (const_string_len); > >> + } >> + } > > Maybe also do > if (string_leni < 0) > return -1; > >> + return string_leni; > > unless the callers just look for negative value as unusable. > >> + tree len = gimple_call_arg (stmt, 2); >> + if (tree_fits_uhwi_p (len)) >> + length = tree_to_uhwi (len); > > Similarly to above, you are mixing signed and unsigned HWIs too much. > >> + if (gimple_code (ustmt) == GIMPLE_ASSIGN) > > if (is_gimple_assign (ustmt)) > > Usually we use use_stmt instead of ustmt. > >> + { >> + gassign *asgn = as_a <gassign *> (ustmt); > > No need for the gassign and ugly as_a, gimple_assign_rhs_code > as well as gimple_assign_rhs2 can be called on gimple * too. > >> + tree_code code = gimple_assign_rhs_code (asgn); >> + if ((code != EQ_EXPR && code != NE_EXPR) >> + || !integer_zerop (gimple_assign_rhs2 (asgn))) >> + return true; >> + } >> + else if (gimple_code (ustmt) == GIMPLE_COND) >> + { >> + tree_code code = gimple_cond_code (ustmt); >> + if ((code != EQ_EXPR && code != NE_EXPR) >> + || !integer_zerop (gimple_cond_rhs (ustmt))) >> + return true; > > There is another case you are missing, assign stmt with > gimple_assign_rhs_code COND_EXPR, where gimple_assign_rhs1 is > tree with TREE_CODE EQ_EXPR or NE_EXPR with TREE_OPERAND (rhs1, 1) > integer_zerop. > >> + /* When both arguments are known, and their strlens are unequal, we can >> + safely fold the call to a non-zero value for strcmp; >> + othewise, do nothing now. */ >> + if (idx1 != 0 && idx2 != 0) >> + { >> + HOST_WIDE_INT const_string_leni1 = -1; >> + HOST_WIDE_INT const_string_leni2 = -1; >> + const_string_leni1 = compute_string_length (idx1); >> + const_string_leni2 = compute_string_length (idx2); > > Why do you initialize the vars when you immediately overwrite it? > Just do > HOST_WIDE_INT const_string_leni1 = compute_string_length (idx1); > etc. > >> + /* When one of args is constant string. */ >> + tree var_string; >> + HOST_WIDE_INT const_string_leni = -1; >> + >> + if (idx1) >> + { >> + const_string_leni = compute_string_length (idx1); >> + var_string = arg2; >> + } >> + else if (idx2) >> + { >> + const_string_leni = compute_string_length (idx2); >> + var_string = arg1; >> + } > > Haven't you checked earlier that one of idx1 and idx2 is non-zero? > If so, then the else if (idx2) will just might confuse -Wuninitialized, > if you just use else, you don't need to initialize const_string_leni > either. > >> + /* Try to get the min and max string length for var_string, the max >> length is >> + the size of the array - 1, recorded in size[1]. */ >> + get_range_strlen (var_string, size); >> + if (size[1] && tree_fits_uhwi_p (size[1])) >> + var_sizei = tree_to_uhwi (size[1]) + 1; > > This is something that looks problematic to me. get_range_strlen returns > some conservative upper bound on the string length, which is fine if > var_string points to say a TREE_STATIC variable where you know the allocated > size, or automatic variable. But if somebody passes you a pointer to a > structure and the source doesn't contain aggregate copying for it, not sure > if you can take for granted that all the bytes are readable after the '\0' > in the string. Hopefully at least for flexible array members and arrays in > such positions get_range_strlen will not provide the upper bound, but even > in other cases it doesn't feel safe to me. > > Furthermore, in the comments you say that you do it only for small strings, > but in the patch I can't see any upper bound, so you could transform strlen > that would happen to return say just 1 or 2 with a function call that > possibly reads megabytes of data (memcmp may read all bytes, not just stop > at the first difference). >> + unsigned HOST_WIDE_INT final_length >> + = is_ncmp ? length : (const_string_leni + 1); > > Why the ()s? > > Jakub