PR tree-optimization/83821 - local aggregate initialization defeats strlen optimization
authorMartin Sebor <msebor@redhat.com>
Wed, 16 Oct 2019 19:24:36 +0000 (19:24 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Wed, 16 Oct 2019 19:24:36 +0000 (13:24 -0600)
gcc/ChangeLog:

PR tree-optimization/83821
* tree-ssa-strlen.c (maybe_invalidate): Add argument.  Consider
the length of a string when available.
(handle_builtin_memset) Add argument.
(handle_store, strlen_check_and_optimize_call): Same.
(check_and_optimize_stmt): Same.  Pass it to callees.

gcc/testsuite/ChangeLog:

PR tree-optimization/83821
* c-c++-common/Warray-bounds-4.c: Remove XFAIL.
* gcc.dg/strlenopt-82.c: New test.
* gcc.dg/strlenopt-83.c: Same.
* gcc.dg/strlenopt-84.c: Same.
* gcc.dg/strlenopt-85.c: Same.
* gcc.dg/strlenopt-86.c: Same.
* gcc.dg/tree-ssa/calloc-4.c: Same.
* gcc.dg/tree-ssa/calloc-5.c: Same.

From-SVN: r277080

gcc/ChangeLog
gcc/testsuite/ChangeLog
gcc/testsuite/c-c++-common/Warray-bounds-4.c
gcc/testsuite/gcc.dg/strlenopt-82.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/strlenopt-83.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/strlenopt-84.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/strlenopt-85.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/strlenopt-86.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/calloc-4.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c [new file with mode: 0644]
gcc/tree-ssa-strlen.c

index 018a0a1832d609cccec28bbb7e6b870538d50f08..b81945b6eb2c86bed4ed303bc0babe4a04b303cd 100644 (file)
@@ -1,3 +1,12 @@
+2019-10-16  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/83821
+       * tree-ssa-strlen.c (maybe_invalidate): Add argument.  Consider
+       the length of a string when available.
+       (handle_builtin_memset) Add argument.
+       (handle_store, strlen_check_and_optimize_call): Same.
+       (check_and_optimize_stmt): Same.  Pass it to callees.
+
 2019-10-16  Martin Sebor  <msebor@redhat.com>
 
        PR tree-optimization/91996
index 8816472a8a4be02546ec1d62f3706778b6a46607..0785e413e9187d63756712c82a1184d1e24f6ad7 100644 (file)
@@ -1,3 +1,15 @@
+2019-10-16  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/83821
+       * c-c++-common/Warray-bounds-4.c: Remove XFAIL.
+       * gcc.dg/strlenopt-82.c: New test.
+       * gcc.dg/strlenopt-83.c: Same.
+       * gcc.dg/strlenopt-84.c: Same.
+       * gcc.dg/strlenopt-85.c: Same.
+       * gcc.dg/strlenopt-86.c: Same.
+       * gcc.dg/tree-ssa/calloc-4.c: Same.
+       * gcc.dg/tree-ssa/calloc-5.c: Same.
+
 2019-10-16  Martin Sebor  <msebor@redhat.com>
 
        PR tree-optimization/91996
index 961107a3472814748ceacc4f92474b13654bcbb1..a09a08d658df295e9509dab12192e8db1929a368 100644 (file)
@@ -63,7 +63,7 @@ void test_strcpy_bounds_memarray_range (void)
   TM ("0", "",     ma.a5 + i, ma.a5);
   TM ("01", "",    ma.a5 + i, ma.a5);
   TM ("012", "",   ma.a5 + i, ma.a5);
-  TM ("0123", "",  ma.a5 + i, ma.a5);     /* { dg-warning "offset 6 from the object at .ma. is out of the bounds of referenced subobject .a5. with type .char\\\[5]. at offset 0" "strcpy" { xfail *-*-* } } */
+  TM ("0123", "",  ma.a5 + i, ma.a5);     /* { dg-warning "offset 5 from the object at .ma. is out of the bounds of referenced subobject .\(MA::\)?a5. with type .char *\\\[5]. at offset 0" "strcpy" } */
 
 #if __i386__ || __x86_64__
   /* Disabled for non-x86 targets due to bug 83462.  */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-82.c b/gcc/testsuite/gcc.dg/strlenopt-82.c
new file mode 100644 (file)
index 0000000..8070f6c
--- /dev/null
@@ -0,0 +1,224 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+
+   Avoid exercising targets other than x86_64 in LP64 mode due to PR 83543
+   - strlen of a local array member not optimized on some targets
+   { dg-do compile { target { { i?86-*-* x86_64-*-* } && lp64 } } }
+
+   { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+#include "strlenopt.h"
+
+#define CAT(x, y) x ## y
+#define CONCAT(x, y) CAT (x, y)
+#define FAILNAME(name) CONCAT (call_ ## name ##_on_line_, __LINE__)
+
+#define FAIL(name) do {                                \
+    extern void FAILNAME (name) (void);                \
+    FAILNAME (name)();                         \
+  } while (0)
+
+/* Macro to emit a call to function named
+     call_in_true_branch_not_eliminated_on_line_NNN()
+   for each call that's expected to be eliminated.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that no such call appears in output.  */
+#define ELIM(expr) \
+  if (!(expr)) FAIL (in_true_branch_not_eliminated); else (void)0
+
+/* Macro to emit a call to a function named
+     call_made_in_{true,false}_branch_on_line_NNN()
+   for each call that's expected to be retained.  The dg-final
+   scan-tree-dump-time directive at the bottom of the test verifies
+   that the expected number of both kinds of calls appears in output
+   (a pair for each line with the invocation of the KEEP() macro.  */
+#define KEEP(expr)                             \
+  if (expr)                                    \
+    FAIL (made_in_true_branch);                        \
+  else                                         \
+    FAIL (made_in_false_branch)
+
+#define STR10 "0123456789"
+#define STR20 STR10 STR10
+#define STR30 STR20 STR10
+#define STR40 STR20 STR20
+
+void elim_char_array_init_consecutive (void)
+{
+  char a[][10] = { "1", "12", "123", "1234", "12345", "12345" };
+
+  ELIM (strlen (a[0]) == 1);
+  ELIM (strlen (a[1]) == 2);
+  ELIM (strlen (a[2]) == 3);
+  ELIM (strlen (a[3]) == 4);
+  ELIM (strlen (a[4]) == 5);
+}
+
+void elim_char_array_cpy_consecutive (void)
+{
+  char a[5][10];
+
+  strcpy (a[0], "12345");
+  strcpy (a[1], "1234");
+  strcpy (a[2], "123");
+  strcpy (a[3], "12");
+  strcpy (a[4], "1");
+
+  ELIM (strlen (a[0]) == 5);
+  ELIM (strlen (a[1]) == 4);
+  ELIM (strlen (a[2]) == 3);
+  ELIM (strlen (a[3]) == 2);
+  ELIM (strlen (a[4]) == 1);
+}
+
+void elim_clear_char_array_cpy_consecutive (void)
+{
+  char a[5][10] = { };
+
+  strcpy (a[0], "12345");
+  strcpy (a[1], "1234");
+  strcpy (a[2], "123");
+  strcpy (a[3], "12");
+  strcpy (a[4], "1");
+
+  ELIM (strlen (a[0]) == 5);
+  ELIM (strlen (a[1]) == 4);
+  ELIM (strlen (a[2]) == 3);
+  ELIM (strlen (a[3]) == 2);
+  ELIM (strlen (a[4]) == 1);
+}
+
+struct Consec
+{
+  char s1[sizeof STR40];
+  char s2[sizeof STR40];
+  const char *p1;
+  const char *p2;
+};
+
+void elim_struct_init_consecutive (void)
+{
+  struct Consec a = { STR10, STR10, STR10, STR10 };
+
+  ELIM (strlen (a.s1) == sizeof STR10 - 1);
+  ELIM (strlen (a.s2) == sizeof STR10 - 1);
+  ELIM (strlen (a.p1) == sizeof STR10 - 1);
+  ELIM (strlen (a.p2) == sizeof STR10 - 1);
+}
+
+void elim_struct_array_init_consecutive (void)
+{
+  struct Consec a[2] = {
+    { STR10, STR20, STR30, STR40 },
+    { STR40, STR30, STR20, STR10 }
+  };
+
+  ELIM (strlen (a[0].s1) == sizeof STR10 - 1);
+  ELIM (strlen (a[0].s2) == sizeof STR20 - 1);
+  ELIM (strlen (a[0].p1) == sizeof STR30 - 1);
+  ELIM (strlen (a[0].p2) == sizeof STR40 - 1);
+
+  ELIM (strlen (a[1].s1) == sizeof STR40 - 1);
+  ELIM (strlen (a[1].s2) == sizeof STR30 - 1);
+  ELIM (strlen (a[1].p1) == sizeof STR20 - 1);
+  ELIM (strlen (a[1].p2) == sizeof STR10 - 1);
+}
+
+struct NonConsec
+{
+  char s1[sizeof STR40];
+  int i1;
+  char s2[sizeof STR40];
+  int i2;
+  const char *p1;
+  int i3;
+  const char *p2;
+  int i4;
+};
+
+void elim_struct_init_nonconsecutive (void)
+{
+  struct NonConsec b = { STR10, 123, STR20, 456, b.s1, 789, b.s2, 123 };
+
+  ELIM (strlen (b.s1) == sizeof STR10 - 1);
+  ELIM (strlen (b.s2) == sizeof STR20 - 1);
+  ELIM (strlen (b.p1) == sizeof STR10 - 1);
+  ELIM (strlen (b.p2) == sizeof STR20 - 1);
+}
+
+void elim_struct_assign_tmp_nonconsecutive (void)
+{
+  struct NonConsec b = { "a", 1, "b", 2, "c", 3, "d", 4 };
+
+  b = (struct NonConsec){ STR10, 123, STR20, 456, STR30, 789, STR40, 123 };
+
+  ELIM (strlen (b.s1) == sizeof STR10 - 1);
+  ELIM (strlen (b.s2) == sizeof STR20 - 1);
+  ELIM (strlen (b.p1) == sizeof STR30 - 1);
+  ELIM (strlen (b.p2) == sizeof STR40 - 1);
+}
+
+const struct NonConsec bcst = {
+  STR40, -1, STR30, -2, STR20, -3, STR10, -4
+};
+
+void elim_struct_assign_cst_nonconsecutive (void)
+{
+  struct NonConsec b = { "a", 1, "b", 2, "c", 3, "d" };
+
+  b = bcst;
+
+  ELIM (strlen (b.s1) == sizeof STR40 - 1);
+  ELIM (strlen (b.s2) == sizeof STR30 - 1);
+  ELIM (strlen (b.p1) == sizeof STR20 - 1);
+  ELIM (strlen (b.p2) == sizeof STR10 - 1);
+}
+
+void elim_struct_copy_cst_nonconsecutive (void)
+{
+  struct NonConsec b = { "a", 1, "b", 2, "c", 3, "d" };
+  memcpy (&b, &bcst, sizeof b);
+
+  /* ELIM (strlen (b.s1) == sizeof STR40 - 1);
+     ELIM (strlen (b.s2) == sizeof STR30 - 1); */
+  ELIM (strlen (b.p1) == sizeof STR20 - 1);
+  ELIM (strlen (b.p2) == sizeof STR10 - 1);
+}
+
+
+#line 1000
+
+int sink (void*);
+
+void keep_init_nonconsecutive (void)
+{
+  struct NonConsec b = {
+    STR10, 123, STR20, 456, b.s1, 789, b.s2,
+    sink (&b)
+  };
+
+  KEEP (strlen (b.s1) == sizeof STR10 - 1);
+  KEEP (strlen (b.s2) == sizeof STR10 - 1);
+  KEEP (strlen (b.p1) == sizeof STR10 - 1);
+  KEEP (strlen (b.p2) == sizeof STR10 - 1);
+}
+
+void keep_assign_tmp_nonconsecutive (void)
+{
+  struct NonConsec b = { "a", 1, "b", 2, "c", 3, "d", 4 };
+
+  b = (struct NonConsec){
+    STR10, 123, STR20, 456, STR30, 789, STR40,
+    sink (&b)
+  };
+
+  KEEP (strlen (b.s1) == sizeof STR10 - 1);
+  KEEP (strlen (b.s2) == sizeof STR20 - 1);
+  KEEP (strlen (b.p1) == sizeof STR30 - 1);
+  KEEP (strlen (b.p2) == sizeof STR40 - 1);
+}
+
+/* { dg-final { scan-tree-dump-times "call_in_true_branch_not_eliminated_" 0 "optimized" } }
+
+   { dg-final { scan-tree-dump-times "call_made_in_true_branch_on_line_1\[0-9\]\[0-9\]\[0-9\]" 8 "optimized" } }
+   { dg-final { scan-tree-dump-times "call_made_in_false_branch_on_line_1\[0-9\]\[0-9\]\[0-9\]" 8 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-83.c b/gcc/testsuite/gcc.dg/strlenopt-83.c
new file mode 100644 (file)
index 0000000..5baafea
--- /dev/null
@@ -0,0 +1,83 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   { dg-do compile }
+   { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+#include "strlenopt.h"
+char *p_p2, *p_p5, *p_p9, *p_p14;
+
+unsigned n0, n1, n2, n3, n4;
+
+
+static inline __attribute__ ((always_inline)) void
+elim_strlen_of_consecutive_strcpy (char *p)
+{
+  p_p2 = p + 2;
+  __builtin_strcpy (p_p2, "12");
+
+  p_p5 = p_p2 + 3;
+  __builtin_strcpy (p_p5, "124");
+
+  p_p9 = p_p5 + 4;
+  __builtin_strcpy (p_p9, "1245");
+
+  p_p14 = p_p9 + 5;
+
+  n0 = __builtin_strlen (p);
+  n1 = __builtin_strlen (p_p2);
+  n2 = __builtin_strlen (p_p5);
+  n3 = __builtin_strlen (p_p9);
+
+  /* The following isn't handled yet:
+     n4 = __builtin_strlen (p_p14); */
+
+  if (n0 || n1 != 2 || n2 != 3 || n3 != 4)
+    __builtin_abort ();
+}
+
+
+void elim_strlen_of_consecutive_strcpy_in_alloca (unsigned n)
+{
+  /* Only known sizes are handled so far.  */
+  n = 14;
+
+  char *p = __builtin_alloca (n);
+
+  *p = '\0';
+
+  elim_strlen_of_consecutive_strcpy (p);
+}
+
+
+void elim_strlen_of_consecutive_strcpy_in_vla (unsigned n)
+{
+  /* Only known sizes are handled so far.  */
+  n = 14;
+
+  char vla[n];
+
+  *vla = '\0';
+
+  elim_strlen_of_consecutive_strcpy (vla);
+}
+
+void elim_strlen_of_consecutive_strcpy_in_malloc (unsigned n)
+{
+  char *p = __builtin_malloc (n);
+
+  *p = '\0';
+
+  elim_strlen_of_consecutive_strcpy (p);
+}
+
+
+void elim_strlen_of_consecutive_strcpy_in_calloc (unsigned n)
+{
+  char *p = __builtin_calloc (n, 1);
+
+  /* Do not store into *P to verify that strlen knows it's zero.  */
+
+  elim_strlen_of_consecutive_strcpy (p);
+}
+
+/* { dg-final { scan-tree-dump-not "abort" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-84.c b/gcc/testsuite/gcc.dg/strlenopt-84.c
new file mode 100644 (file)
index 0000000..d6102b6
--- /dev/null
@@ -0,0 +1,135 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   Verify that stores that overwrite an interior nul are correctly
+   reflected in strlen results.
+   { dg-do run }
+   { dg-options "-O2 -Wall" } */
+
+#define false (0 == 1)
+#define true (0 == 0)
+#define assert(e)                                                      \
+  ((e) ? (void)0 : (__builtin_printf ("assertion failed on line %i\n", \
+                                     __LINE__), __builtin_abort ()))
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+static inline int ATTR (always_inline)
+assign_and_get_length (char *p, _Bool clear)
+{
+  p[0] = 'a';
+
+  if (clear)
+    p[1] = 0;
+
+  p[2] = 'c';
+
+  if (clear)
+    p[3] = 0;
+
+  p[1] = 'b';
+
+  return __builtin_strlen (p);
+}
+
+ATTR (noipa) void array_get_length (void)
+{
+  char a[4];
+  unsigned n = assign_and_get_length (a, true);
+  assert (n == 3);
+}
+
+ATTR (noipa) void clear_array_get_length (void)
+{
+  char a[4] = { };
+  unsigned n = assign_and_get_length (a, false);
+  assert (n == 3);
+}
+
+ATTR (noipa) void calloc_get_length (void)
+{
+  char *p = __builtin_calloc (5, 1);
+  unsigned n = assign_and_get_length (p, false);
+  assert (n == 3);
+}
+
+ATTR (noipa) void malloc_get_length (void)
+{
+  char *p = __builtin_malloc (5);
+  unsigned n = assign_and_get_length (p, true);
+  assert (n == 3);
+}
+
+ATTR (noipa) void vla_get_length (int n)
+{
+  char a[n];
+  unsigned len = assign_and_get_length (a, true);
+  assert (len == 3);
+}
+
+
+static inline void ATTR (always_inline)
+assign_and_test_length (char *p, _Bool clear)
+{
+  p[0] = 'a';
+
+  if (clear)
+    p[1] = 0;
+
+  p[2] = 'c';
+
+  if (clear)
+    p[3] = 0;
+
+  unsigned n0 =  __builtin_strlen (p);
+
+  p[1] = 'b';
+
+  unsigned n1 =  __builtin_strlen (p);
+  assert (n0 != n1);
+}
+
+ATTR (noipa) void array_test_length (void)
+{
+  char a[4];
+  assign_and_test_length (a, true);
+}
+
+ATTR (noipa) void clear_array_test_length (void)
+{
+  char a[4] = { };
+  assign_and_test_length (a, false);
+}
+
+ATTR (noipa) void calloc_test_length (void)
+{
+  char *p = __builtin_calloc (5, 1);
+  assign_and_test_length (p, false);
+}
+
+ATTR (noipa) void malloc_test_length (void)
+{
+  char *p = __builtin_malloc (5);
+  assign_and_test_length (p, true);
+}
+
+ATTR (noipa) void vla_test_length (int n)
+{
+  char a[n];
+  assign_and_test_length (a, true);
+}
+
+int main (void)
+{
+  array_get_length ();
+  clear_array_get_length ();
+  calloc_get_length ();
+  malloc_get_length ();
+  vla_get_length (4);
+
+  array_test_length ();
+  clear_array_test_length ();
+  calloc_test_length ();
+  malloc_test_length ();
+  vla_test_length (4);
+}
+
diff --git a/gcc/testsuite/gcc.dg/strlenopt-85.c b/gcc/testsuite/gcc.dg/strlenopt-85.c
new file mode 100644 (file)
index 0000000..e049e0c
--- /dev/null
@@ -0,0 +1,44 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   Verify that a strlen() call is eliminated for a pointer to a region
+   of memory allocated by calloc() even if one or more nul bytes are
+   written into it.
+   { dg-do compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+unsigned n0, n1;
+
+void* elim_strlen_calloc_store_memset_1 (unsigned a, unsigned b)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[0] = '\0';
+  p[1] = '\0';
+  p[2] = '\0';
+  p[3] = '\0';
+
+  __builtin_memset (p, 0, b);
+
+  n0 = __builtin_strlen (p);
+
+  return p;
+}
+
+void* elim_strlen_calloc_store_memset_2 (unsigned a, unsigned b, unsigned c)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[1] = '\0';
+  __builtin_memset (p, 0, b);
+
+  n0 = __builtin_strlen (p);
+
+  p[3] = 0;
+  __builtin_memset (p, 0, c);
+
+  n1 = __builtin_strlen (p);
+
+  return p;
+}
+
+/* { dg-final { scan-tree-dump-not "__builtin_strlen" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt-86.c b/gcc/testsuite/gcc.dg/strlenopt-86.c
new file mode 100644 (file)
index 0000000..3e86fa3
--- /dev/null
@@ -0,0 +1,57 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   Verify that a strlen() call is not eliminated for a pointer to a region
+   of memory allocated by calloc() if a byte is written into the region
+   that isn't known to be nul.
+   { dg-do compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+unsigned n0, n1;
+
+void*
+keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[1] = 'x';
+
+  __builtin_memset (p, 0, b);
+
+  n0 = __builtin_strlen (p);
+
+  return p;
+}
+
+void*
+keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[1] = x;
+
+  __builtin_memset (p, 0, b);
+
+  n0 = __builtin_strlen (p);
+
+  return p;
+}
+
+void*
+keep_strlen_calloc_store_memset_2 (int x, unsigned a, unsigned b, unsigned c)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[1] = x;
+  __builtin_memset (p, 0, b);
+
+  n0 = __builtin_strlen (p);
+
+  p[3] = x;
+  __builtin_memset (p, 0, c);
+
+  n1 = __builtin_strlen (p);
+
+  return p;
+}
+
+/* { dg-final { scan-tree-dump-times "__builtin_strlen" 4 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/calloc-4.c b/gcc/testsuite/gcc.dg/tree-ssa/calloc-4.c
new file mode 100644 (file)
index 0000000..b3a1d0c
--- /dev/null
@@ -0,0 +1,37 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   Verify that a memset() call to zero out a subregion of memory
+   allocated by calloc() is eliminated even if a zero byte is written
+   into it in between the two calls.  See the calloc-2.c test that
+   verifies that the memset() calls isn't eliminated if the written
+   value is non-zero.
+   { dg-do compile }
+   { dg-options "-O2 -fdump-tree-optimized" } */
+
+void* elim_calloc_store_memset_1 (unsigned a, unsigned b)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[1] = '\0';
+
+  __builtin_memset (p, 0, b);   // should be eliminated
+
+  return p;
+}
+
+void* elim_calloc_store_memset_2 (unsigned a, unsigned b, unsigned c)
+{
+  char *p = __builtin_calloc (a, 1);
+
+  p[1] = '\0';
+  __builtin_memset (p, 0, b);   // should be eliminated
+
+  p[3] = '\0';
+  __builtin_memset (p, 0, c);   // should also be eliminated
+
+  return p;
+}
+
+/* { dg-final { scan-tree-dump-not "malloc" "optimized" } }
+   { dg-final { scan-tree-dump-times "_calloc \\\(" 2 "optimized" } }
+   { dg-final { scan-tree-dump-not "_memset \\\(" "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c b/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c
new file mode 100644 (file)
index 0000000..3d3e5a1
--- /dev/null
@@ -0,0 +1,22 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   Verify that with DSE disabled, a memset() call to zero out a subregion
+   of memory allocated by calloc() is not eliminated after a non-zero byte
+   is written into it using memset() in between the two calls.
+   { dg-do compile }
+   { dg-options "-O2 -fno-tree-dse -fdump-tree-optimized" } */
+
+char* keep_memset_calls (void)
+{
+  char *p = __builtin_calloc (12, 1);
+
+  __builtin_memset (p + 5, 1, 2);   /* dead store (not eliminated) */
+
+  __builtin_memset (p, 0, 12);      /* must not be eliminated */
+
+  return p;
+}
+
+/* { dg-final { scan-tree-dump-not "malloc" "optimized" } }
+   { dg-final { scan-tree-dump-times "_calloc \\\(" 1 "optimized" } }
+   { dg-final { scan-tree-dump-times "_memset \\\(" 2 "optimized" } } */
index bd1eb0d7ab8a68b2c58bed867d3030a56889ef89..ff9b92b5b75f9a6c903294c42610686933865128 100644 (file)
@@ -1094,11 +1094,16 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata,
 }
 
 /* Invalidate string length information for strings whose length
-   might change due to stores in stmt.  */
+   might change due to stores in stmt, except those marked DON'T
+   INVALIDATE.  For string-modifying statements, ZERO_WRITE is
+   set when the statement wrote only zeros.  */
 
 static bool
-maybe_invalidate (gimple *stmt)
+maybe_invalidate (gimple *stmt, bool zero_write = false)
 {
+  if (dump_file && (dump_flags & TDF_DETAILS))
+    fprintf (dump_file, "  %s()\n", __func__);
+
   strinfo *si;
   unsigned int i;
   bool nonempty = false;
@@ -1109,18 +1114,59 @@ maybe_invalidate (gimple *stmt)
        if (!si->dont_invalidate)
          {
            ao_ref r;
-           /* Do not use si->nonzero_chars.  */
-           ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE);
+           tree size = NULL_TREE;
+           if (si->nonzero_chars)
+             {
+               /* Include the terminating nul in the size of the string
+                  to consider when determining possible clobber.  */
+               tree type = TREE_TYPE (si->nonzero_chars);
+               size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars,
+                                   build_int_cst (type, 1));
+             }
+           ao_ref_init_from_ptr_and_size (&r, si->ptr, size);
            if (stmt_may_clobber_ref_p_1 (stmt, &r))
              {
+               if (dump_file && (dump_flags & TDF_DETAILS))
+                 {
+                   if (size && tree_fits_uhwi_p (size))
+                     fprintf (dump_file,
+                              "  statement may clobber string %zu long\n",
+                              tree_to_uhwi (size));
+                   else
+                     fprintf (dump_file,
+                              "  statement may clobber string\n");
+                 }
+
                set_strinfo (i, NULL);
                free_strinfo (si);
                continue;
              }
+
+           if (size
+               && !zero_write
+               && si->stmt
+               && is_gimple_call (si->stmt)
+               && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt))
+                   == BUILT_IN_CALLOC))
+             {
+               /* If the clobber test above considered the length of
+                  the string (including the nul), then for (potentially)
+                  non-zero writes that might modify storage allocated by
+                  calloc consider the whole object and if it might be
+                  clobbered by the statement reset the allocation
+                  statement.  */
+               ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE);
+               if (stmt_may_clobber_ref_p_1 (stmt, &r))
+                 si->stmt = NULL;
+             }
          }
        si->dont_invalidate = false;
        nonempty = true;
       }
+
+  if (dump_file && (dump_flags & TDF_DETAILS))
+    fprintf (dump_file, "  %s() ==> %i\n", __func__, nonempty);
+
   return nonempty;
 }
 
@@ -3213,11 +3259,15 @@ handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
    return true when the call is transfomred, false otherwise.  */
 
 static bool
-handle_builtin_memset (gimple_stmt_iterator *gsi)
+handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write)
 {
   gimple *stmt2 = gsi_stmt (*gsi);
   if (!integer_zerop (gimple_call_arg (stmt2, 1)))
     return false;
+
+  /* Let the caller know the memset call cleared the destination.  */
+  *zero_write = true;
+
   tree ptr = gimple_call_arg (stmt2, 0);
   int idx1 = get_stridx (ptr);
   if (idx1 <= 0)
@@ -4223,7 +4273,7 @@ count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm,
    '*(int*)a = 12345').  Return true when handled.  */
 
 static bool
-handle_store (gimple_stmt_iterator *gsi, const vr_values *rvals)
+handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals)
 {
   int idx = -1;
   strinfo *si = NULL;
@@ -4250,7 +4300,10 @@ handle_store (gimple_stmt_iterator *gsi, const vr_values *rvals)
          if (offset == 0)
            ssaname = TREE_OPERAND (lhs, 0);
          else if (si == NULL || compare_nonzero_chars (si, offset, rvals) < 0)
-           return true;
+           {
+             *zero_write = initializer_zerop (rhs);
+             return true;
+           }
        }
     }
   else
@@ -4285,6 +4338,7 @@ handle_store (gimple_stmt_iterator *gsi, const vr_values *rvals)
     {
       rhs_minlen = lenrange[0];
       storing_nonzero_p = lenrange[1] > 0;
+      *zero_write = storing_all_zeros_p;
 
       /* Avoid issuing multiple warnings for the same LHS or statement.
         For example, -Warray-bounds may have already been issued for
@@ -4649,6 +4703,7 @@ is_char_type (tree type)
 
 static bool
 strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
+                               bool *zero_write,
                                const vr_values *rvals)
 {
   gimple *stmt = gsi_stmt (*gsi);
@@ -4708,7 +4763,7 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
       handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi);
       break;
     case BUILT_IN_MEMSET:
-      if (handle_builtin_memset (gsi))
+      if (handle_builtin_memset (gsi, zero_write))
        return false;
       break;
     case BUILT_IN_MEMCMP:
@@ -4855,9 +4910,13 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh,
 {
   gimple *stmt = gsi_stmt (*gsi);
 
+  /* For statements that modify a string, set to true if the write
+     is only zeros.  */
+  bool zero_write = false;
+
   if (is_gimple_call (stmt))
     {
-      if (!strlen_check_and_optimize_call (gsi, rvals))
+      if (!strlen_check_and_optimize_call (gsi, &zero_write, rvals))
        return false;
     }
   else if (!flag_optimize_strlen || !strlen_optimize)
@@ -4907,7 +4966,7 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh,
          }
 
        /* Handle a single or multibyte assignment.  */
-       if (is_char_store && !handle_store (gsi, rvals))
+       if (is_char_store && !handle_store (gsi, &zero_write, rvals))
          return false;
       }
     }
@@ -4920,7 +4979,7 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh,
     }
 
   if (gimple_vdef (stmt))
-    maybe_invalidate (stmt);
+    maybe_invalidate (stmt, zero_write);
   return true;
 }