This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] handle local aggregate initialization in strlen, take 2 (PR 83821)


When a subsequent element or member of a local aggregate containing
a prior character array is initialized the strlen pass discards
the length it computed for the prior element/member.  E.g., here:

  struct { char a[4], b[4]; } s = { "1", "12" };

even though strlen (s.b) is folded to 2, strlen (s.a) is not.  (Ditto
for other stores even to members of other types.)  This causes hundreds
(over 700 in GCC) to thousands (nearly 3,000 in Binutils/GDB and some
36,000 in the kernel) of instances of previously computed string lengths
to end up discarded and so besides emitting less than optimal code also
defeats buffer overflow detection in such cases.

Attached is a resubmission of a previously approved patch that I never
committed (the original had a bug that was noted during review that
I subsequently fixed but I didn't remember to post the corrected patch).
Tested on x86_64-linux.

Martin
PR tree-optimization/83821 - local aggregate initialization defeats strlen optimization

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-80.c: New test.
	* gcc.dg/strlenopt-81.c: Same.
	* gcc.dg/strlenopt-82.c: Same.
	* gcc.dg/strlenopt-83.c: Same.
	* gcc.dg/strlenopt-84.c: Same.
	* gcc.dg/tree-ssa/calloc-4.c: Same.
	* gcc.dg/tree-ssa/calloc-5.c: Same.

diff --git a/gcc/testsuite/c-c++-common/Warray-bounds-4.c b/gcc/testsuite/c-c++-common/Warray-bounds-4.c
index 961107a3472..05b7b1e8d8a 100644
--- a/gcc/testsuite/c-c++-common/Warray-bounds-4.c
+++ b/gcc/testsuite/c-c++-common/Warray-bounds-4.c
@@ -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-80.c b/gcc/testsuite/gcc.dg/strlenopt-80.c
new file mode 100644
index 00000000000..e049e0c30e0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-80.c
@@ -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-81.c b/gcc/testsuite/gcc.dg/strlenopt-81.c
new file mode 100644
index 00000000000..3e86fa3c90a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-81.c
@@ -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/strlenopt-82.c b/gcc/testsuite/gcc.dg/strlenopt-82.c
new file mode 100644
index 00000000000..f3cdede20f3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-82.c
@@ -0,0 +1,223 @@
+/* PR tree-optimization/83821 - local aggregate initialization defeats
+   strlen optimization
+   { dg-do compile }
+   { 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
index 00000000000..5baafea5c32
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-83.c
@@ -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
index 00000000000..d6102b6e6f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-84.c
@@ -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/tree-ssa/calloc-4.c b/gcc/testsuite/gcc.dg/tree-ssa/calloc-4.c
new file mode 100644
index 00000000000..b3a1d0c2c69
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/calloc-4.c
@@ -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
index 00000000000..3d3e5a14542
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/calloc-5.c
@@ -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" } } */
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index ef2717d6a29..8332c9b18e6 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -1067,11 +1067,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;
@@ -1082,18 +1087,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;
 }
 
@@ -3186,11 +3232,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)
@@ -4158,7 +4208,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)
+handle_store (gimple_stmt_iterator *gsi, bool *zero_write)
 {
   int idx = -1;
   strinfo *si = NULL;
@@ -4185,7 +4235,10 @@ handle_store (gimple_stmt_iterator *gsi)
 	  if (offset == 0)
 	    ssaname = TREE_OPERAND (lhs, 0);
 	  else if (si == NULL || compare_nonzero_chars (si, offset) < 0)
-	    return true;
+	    {
+	      *zero_write = initializer_zerop (rhs);
+	      return true;
+	    }
 	}
     }
   else
@@ -4219,6 +4272,7 @@ handle_store (gimple_stmt_iterator *gsi)
     {
       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
@@ -4583,6 +4637,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);
@@ -4642,7 +4697,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:
@@ -4789,9 +4844,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)
@@ -4841,7 +4900,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))
+	if (is_char_store && !handle_store (gsi, &zero_write))
 	  return false;
       }
     }
@@ -4854,7 +4913,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;
 }
 

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]