This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: [2/2] PR 80769: Incorrect strlen optimisation
- From: Richard Sandiford <richard dot sandiford at linaro dot org>
- To: gcc-patches at gcc dot gnu dot org
- Date: Mon, 12 Jun 2017 07:35:37 +0100
- Subject: Re: [2/2] PR 80769: Incorrect strlen optimisation
- Authentication-results: sourceware.org; auth=none
- References: <87h90lut4v.fsf@linaro.org>
Ping*2
Richard Sandiford <richard.sandiford@linaro.org> writes:
> In this testcase, we (correctly) record after:
>
> strcpy (p1, "abcde");
> char *p2 = strchr (p1, '\0');
> strcpy (p2, q);
>
> that the length of p1 and p2 can be calculated by converting the
> second strcpy to:
>
> tmp = stpcpy (p2, q)
>
> and then doing tmp - p1 for p1 and tmp - p2 for p2. This is delayed
> until we know whether we actually need it. Then:
>
> char *p3 = strchr (p2, '\0');
>
> forces us to calculate the length of p2 in this way. At this point
> we had three related strinfos:
>
> p1: delayed length, calculated from tmp = stpcpy (p2, q)
> p2: known length, tmp - p2
> p3: known length, 0
>
> After:
>
> memcpy (p3, "x", 2);
>
> we use adjust_related_strinfos to add 1 to each length. However,
> that didn't do anything for delayed lengths because:
>
> else if (si->stmt != NULL)
> /* Delayed length computation is unaffected. */
> ;
>
> So after the memcpy we had:
>
> p1: delayed length, calculated from tmp = stpcpy (p2, q)
> p2: known length, tmp - p2 + 1
> p3: known length, 1
>
> where the length of p1 was no longer correct.
>
> I thought about three fixes:
>
> (1) Make adjust_related_strinfos drop strinfos with a delayed length.
>
> (2) Make adjust_related_strinfos compute the length itself
> (via get_string_length).
>
> (3) Make get_string_length update all related strinfos. We can then
> maintain the invariant that all lengths in a chain are delayed or
> none are.
>
> (3) seemed like the best. get_string_length has already made the
> invasive step of changing the code and computing the length for one
> strinfo. Updating the related strinfos is just a couple of fold_builds,
> of the kind that the pass already does in several places.
>
> The point is that the code above only triggers if one of the delayed
> lengths has been computed. That makes (1) unnecessarily pessimistic.
> It also wasn't obvious (to me) from first glance, so (2) might look
> more intrusive than it is. I think it becomes easier to reason about
> if we do (3) and can assume that all lengths are delayed or none are.
> It also makes the min-length/maybe-not-terminated patch easier.
>
> [ I can go into more detail about why this should be enough to
> maintain the invariant, and why the asserts should be valid,
> but the message is already pretty long. ]
>
> Tested on aarch64-linux-gnu and x86_64-linux-gnu. OK to install?
>
> Thanks,
> Richard
>
>
> 2017-05-16 Richard Sandiford <richard.sandiford@linaro.org>
>
> gcc/
> PR tree-optimization/80769
> * tree-ssa-strlen.c (strinfo): Document that "stmt" is also used
> for malloc and calloc. Document the new invariant that all related
> strinfos have delayed lengths or none do.
> (verify_related_strinfos): Move earlier in file.
> (set_endptr_and_length): New function, split out from...
> (get_string_length): ...here. Also set the lengths of related
> strinfos.
> (zero_length_string): Assert that chainsi has known (rather than
> delayed) lengths.
> (adjust_related_strinfos): Likewise.
>
> gcc/testsuite/
> PR tree-optimization/80769
> * gcc.dg/strlenopt-31.c: New test.
> * gcc.dg/strlenopt-31g.c: Likewise.
>
> Index: gcc/tree-ssa-strlen.c
> ===================================================================
> --- gcc/tree-ssa-strlen.c 2017-05-16 08:00:03.808133862 +0100
> +++ gcc/tree-ssa-strlen.c 2017-05-16 08:20:51.408572143 +0100
> @@ -61,7 +61,13 @@ struct strinfo
> tree length;
> /* Any of the corresponding pointers for querying alias oracle. */
> tree ptr;
> - /* Statement for delayed length computation. */
> + /* This is used for two things:
> +
> + - To record the statement that should be used for delayed length
> + computations. We maintain the invariant that all related strinfos
> + have delayed lengths or none do.
> +
> + - To record the malloc or calloc call that produced this result. */
> gimple *stmt;
> /* Pointer to '\0' if known, if NULL, it can be computed as
> ptr + length. */
> @@ -451,6 +457,45 @@ set_strinfo (int idx, strinfo *si)
> (*stridx_to_strinfo)[idx] = si;
> }
>
> +/* Return the first strinfo in the related strinfo chain
> + if all strinfos in between belong to the chain, otherwise NULL. */
> +
> +static strinfo *
> +verify_related_strinfos (strinfo *origsi)
> +{
> + strinfo *si = origsi, *psi;
> +
> + if (origsi->first == 0)
> + return NULL;
> + for (; si->prev; si = psi)
> + {
> + if (si->first != origsi->first)
> + return NULL;
> + psi = get_strinfo (si->prev);
> + if (psi == NULL)
> + return NULL;
> + if (psi->next != si->idx)
> + return NULL;
> + }
> + if (si->idx != si->first)
> + return NULL;
> + return si;
> +}
> +
> +/* Set SI's endptr to ENDPTR and compute its length based on SI->ptr.
> + Use LOC for folding. */
> +
> +static void
> +set_endptr_and_length (location_t loc, strinfo *si, tree endptr)
> +{
> + si->endptr = endptr;
> + si->stmt = NULL;
> + tree start_as_size = fold_convert_loc (loc, size_type_node, si->ptr);
> + tree end_as_size = fold_convert_loc (loc, size_type_node, endptr);
> + si->length = fold_build2_loc (loc, MINUS_EXPR, size_type_node,
> + end_as_size, start_as_size);
> +}
> +
> /* Return string length, or NULL if it can't be computed. */
>
> static tree
> @@ -546,12 +591,12 @@ get_string_length (strinfo *si)
> case BUILT_IN_STPCPY_CHK_CHKP:
> gcc_assert (lhs != NULL_TREE);
> loc = gimple_location (stmt);
> - si->endptr = lhs;
> - si->stmt = NULL;
> - lhs = fold_convert_loc (loc, size_type_node, lhs);
> - si->length = fold_convert_loc (loc, size_type_node, si->ptr);
> - si->length = fold_build2_loc (loc, MINUS_EXPR, size_type_node,
> - lhs, si->length);
> + set_endptr_and_length (loc, si, lhs);
> + for (strinfo *chainsi = verify_related_strinfos (si);
> + chainsi != NULL;
> + chainsi = get_next_strinfo (chainsi))
> + if (chainsi->length == NULL)
> + set_endptr_and_length (loc, chainsi, lhs);
> break;
> case BUILT_IN_MALLOC:
> break;
> @@ -620,32 +665,6 @@ unshare_strinfo (strinfo *si)
> return nsi;
> }
>
> -/* Return first strinfo in the related strinfo chain
> - if all strinfos in between belong to the chain, otherwise
> - NULL. */
> -
> -static strinfo *
> -verify_related_strinfos (strinfo *origsi)
> -{
> - strinfo *si = origsi, *psi;
> -
> - if (origsi->first == 0)
> - return NULL;
> - for (; si->prev; si = psi)
> - {
> - if (si->first != origsi->first)
> - return NULL;
> - psi = get_strinfo (si->prev);
> - if (psi == NULL)
> - return NULL;
> - if (psi->next != si->idx)
> - return NULL;
> - }
> - if (si->idx != si->first)
> - return NULL;
> - return si;
> -}
> -
> /* Attempt to create a new strinfo for BASESI + OFF, or find existing
> strinfo if there is any. Return it's idx, or 0 if no strinfo has
> been created. */
> @@ -749,7 +768,8 @@ zero_length_string (tree ptr, strinfo *c
> {
> do
> {
> - gcc_assert (si->length || si->stmt);
> + /* We shouldn't mix delayed and non-delayed lengths. */
> + gcc_assert (si->length);
> if (si->endptr == NULL_TREE)
> {
> si = unshare_strinfo (si);
> @@ -770,12 +790,17 @@ zero_length_string (tree ptr, strinfo *c
> return chainsi;
> }
> }
> - else if (chainsi->first || chainsi->prev || chainsi->next)
> + else
> {
> - chainsi = unshare_strinfo (chainsi);
> - chainsi->first = 0;
> - chainsi->prev = 0;
> - chainsi->next = 0;
> + /* We shouldn't mix delayed and non-delayed lengths. */
> + gcc_assert (chainsi->length);
> + if (chainsi->first || chainsi->prev || chainsi->next)
> + {
> + chainsi = unshare_strinfo (chainsi);
> + chainsi->first = 0;
> + chainsi->prev = 0;
> + chainsi->next = 0;
> + }
> }
> }
> idx = new_stridx (ptr);
> @@ -820,18 +845,13 @@ adjust_related_strinfos (location_t loc,
> tree tem;
>
> si = unshare_strinfo (si);
> - if (si->length)
> - {
> - tem = fold_convert_loc (loc, TREE_TYPE (si->length), adj);
> - si->length = fold_build2_loc (loc, PLUS_EXPR,
> - TREE_TYPE (si->length), si->length,
> - tem);
> - }
> - else if (si->stmt != NULL)
> - /* Delayed length computation is unaffected. */
> - ;
> - else
> - gcc_unreachable ();
> + /* We shouldn't see delayed lengths here; the caller must have
> + calculated the old length in order to calculate the
> + adjustment. */
> + gcc_assert (si->length);
> + tem = fold_convert_loc (loc, TREE_TYPE (si->length), adj);
> + si->length = fold_build2_loc (loc, PLUS_EXPR, TREE_TYPE (si->length),
> + si->length, tem);
>
> si->endptr = NULL_TREE;
> si->dont_invalidate = true;
> Index: gcc/testsuite/gcc.dg/strlenopt-31.c
> ===================================================================
> --- /dev/null 2017-05-11 19:11:40.989165404 +0100
> +++ gcc/testsuite/gcc.dg/strlenopt-31.c 2017-05-16 08:20:26.780371709 +0100
> @@ -0,0 +1,25 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2" } */
> +
> +#include "strlenopt.h"
> +
> +__attribute__((noinline, noclone)) int
> +bar (char *p1, const char *q)
> +{
> + strcpy (p1, "abcde");
> + char *p2 = strchr (p1, '\0');
> + strcpy (p2, q);
> + char *p3 = strchr (p2, '\0');
> + memcpy (p3, "x", 2);
> + return strlen (p1);
> +}
> +
> +int
> +main (void)
> +{
> + char buffer[10];
> + int res = bar (buffer, "foo");
> + if (strcmp (buffer, "abcdefoox") != 0 || res != 9)
> + abort ();
> + return 0;
> +}
> Index: gcc/testsuite/gcc.dg/strlenopt-31g.c
> ===================================================================
> --- /dev/null 2017-05-11 19:11:40.989165404 +0100
> +++ gcc/testsuite/gcc.dg/strlenopt-31g.c 2017-05-16 08:20:26.780371709 +0100
> @@ -0,0 +1,9 @@
> +/* { dg-do run { target *-*-linux* *-*-gnu* } } */
> +/* { dg-options "-O2 -fdump-tree-strlen" } */
> +
> +#define USE_GNU
> +#include "strlenopt-31.c"
> +
> +/* { dg-final { scan-tree-dump-times "stpcpy \\(" 1 "strlen" } } */
> +/* { dg-final { scan-tree-dump-times "memcpy \\(" 2 "strlen" } } */
> +/* { dg-final { scan-tree-dump-not "strlen \\(" "strlen" } } */