diff mbox series

[5/8] tree-dynamic-object-size: Handle GIMPLE_ASSIGN

Message ID 20211007221432.1029249-6-siddhesh@gotplt.org
State Superseded
Headers show
Series __builtin_dynamic_object_size and more | expand

Commit Message

Siddhesh Poyarekar Oct. 7, 2021, 10:14 p.m. UTC
Follow assignment operations and pointer-plus operations to get the
effective object size.  Unlike tree-object-size, this pass handles
negative offsets correctly and returns a size within the bounds of the
parent object; offsets > SIZE_MAX / 2 are considered negative to
emulate ssize_t.

To make negative offsets work, all object size computations store the
"whole variable" size to ensure that the negative offsets don't go
beyond bounds of the object within which PTR points.  As mentioned in
the previous patch, this makes the size_for_offset expressions quite
complex, but the aim at the moment is to be more correct than fast.
We can tweak this later if it is found to have a noticeable
performance impact.

When gcc is invoked without optimization, attempt to minimally
recognize GIMPLE_ASSIGN and ADDR_EXPR expressions so that
__builtin_dynamic_object_size is at least minimally useful without
optimization.

gcc/ChangeLog:

	* tree-dynamic-object-size.c (object_whole_sizes,
	object_whole_size_exprs): New vectors.
	(size_for_offset): Support negative offsets.
	(whole_var_size): Adjust.
	(addr_dyn_object_size): New argument wholesizep.
	(emit_size_stmts): New arguments wholesize_ssa and
	wholesize_expr.  Emit statements for whole size.
	(emit_size_phi_stmts): Likewise.
	(size_bound_expr): New function.
	(eval_size_expr): Call it.  New arguments wholesize and
	wholesize_expr.
	(gimplify_size_exprs): Adjust for whole sizes.
	(compute_builtin_dyn_object_size): Do simple object size
	computation for unoptimized case.
	(expr_object_size, cache_object_size,cache_object_size_copy,
	set_object_size_ssa, call_object_size): Adjust for wholesize.
	(make_object_size_name, plus_stmt_object_size): New functions.
	(collect_object_sizes_for): Support GIMPLE_ASSIGN.  Adjust for
	wholesize.
	(init_dynamic_object_sizes): Allocate object_wholesizes and
	object_wholesize_exprs.
	(fini_dynamic_object_sizes): Deallocate object_wholesizes and
	object_wholesize_exprs.

gcc/testsuite/ChangeLog

	* gcc.dg/builtin-dynamic-object-size-0.c (dynarray_struct):
	New struct.
	(test_dynarray_struct, test_substring,
	test_substring_ptrplus): New tests.
	(main): Call them.

Signed-off-by: Siddhesh Poyarekar <siddhesh@gotplt.org>
---
 .../gcc.dg/builtin-dynamic-object-size-0.c    |  50 ++
 gcc/tree-dynamic-object-size.c                | 435 +++++++++++++++---
 2 files changed, 413 insertions(+), 72 deletions(-)
diff mbox series

Patch

diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c
index 22c86190341..3c2c4c84264 100644
--- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c
+++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c
@@ -219,6 +219,42 @@  test_deploop (size_t sz, size_t cond)
   return __builtin_dynamic_object_size (bin, 0);
 }
 
+/* Address expressions.  */
+
+struct dynarray_struct
+{
+  long a;
+  char c[16];
+  int b;
+};
+
+size_t
+__attribute__ ((noinline))
+test_dynarray_struct (size_t sz, size_t off)
+{
+  struct dynarray_struct bin[sz];
+
+  return __builtin_dynamic_object_size (&bin[off].c, 0);
+}
+
+size_t
+__attribute__ ((noinline))
+test_substring (size_t sz, size_t off)
+{
+  char str[sz];
+
+  return __builtin_dynamic_object_size (&str[off], 0);
+}
+
+size_t
+__attribute__ ((noinline))
+test_substring_ptrplus (size_t sz, size_t off)
+{
+  int str[sz];
+
+  return __builtin_dynamic_object_size (str + off, 0);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -279,6 +315,20 @@  main (int argc, char **argv)
      FAIL ();
   if (test_dynarray (__builtin_strlen (argv[0])) != __builtin_strlen (argv[0]))
     FAIL ();
+  if (test_dynarray_struct (42, 4) !=
+      ((42 - 4) * sizeof (struct dynarray_struct)
+       - __builtin_offsetof (struct dynarray_struct, c)))
+    FAIL ();
+  if (test_dynarray_struct (42, 48) != 0)
+    FAIL ();
+  if (test_substring (128, 4) != 128 - 4)
+    FAIL ();
+  if (test_substring (128, 142) != 0)
+    FAIL ();
+  if (test_substring_ptrplus (128, 4) != (128 - 4) * sizeof (int))
+    FAIL ();
+  if (test_substring_ptrplus (128, 142) != 0)
+    FAIL ();
   if (test_dynarray_cond (0) != 16)
     FAIL ();
   if (test_dynarray_cond (1) != 8)
diff --git a/gcc/tree-dynamic-object-size.c b/gcc/tree-dynamic-object-size.c
index 6cc63abd010..f143a64777c 100644
--- a/gcc/tree-dynamic-object-size.c
+++ b/gcc/tree-dynamic-object-size.c
@@ -83,13 +83,18 @@  static void collect_object_sizes_for (struct object_size_info *, tree);
    object_sizes[2] is lower bound for number of bytes till the end of
    the object and object_sizes[3] lower bound for subobject.
 
-   These are merely SSA names of the sizes.  The actual size expressions are in
+   object_sizes are SSA names of the sizes.  The actual size expressions are in
    object_size_exprs and they need not be SSA.  */
 static vec<tree> object_sizes[4];
-
-/* The actual size expressions, indexed by the object SSA names.  */
 static vec<tree *>object_size_exprs[4];
 
+/* Indexed by object_size_type and SSA name versions of the object,
+   object_whole_sizes and object_whole_size_exprs have SSA names and
+   expressions of the whole objects in which the pointer points
+   respectively.  */
+static vec<tree> object_whole_sizes[4];
+static vec<tree *>object_whole_size_exprs[4];
+
 /* Bitmaps what object sizes have been computed already.  */
 static bitmap computed[4];
 
@@ -166,37 +171,102 @@  compute_object_offset (const_tree expr, const_tree var)
 }
 
 /* Given an object size SZ and offset OFF into it, compute the usable object
-   size.  The expression returns 0 for all offsets that invoke undefined
-   behaviour.  */
+   size.  For cases where the offset breaches object bounds, the expression
+   returns 0.
+
+   For positive offset (we asume > SIZE_MAX / 2 as negative to emulate
+   ssize_t) we assume that the offset, when scaled by typesize, is within
+   bounds of sz.  That is:
+
+   sz > typesize && off <= sz / typesize
+
+   For negative offsets, we ensure that the magnitude of the offset is within
+   bounds of the lower part of the object.  This ensures that underflows result
+   in zero size.  That is:
+
+   (off > SIZE_MAX / 2 && wholesize - min(wholesize, size) > typesize
+    && -off <= (wholesize - min(wholesize, size)) / typesize)
+
+   The whole thing is then capped to wholesize to cater for situations where
+   wholesize may have less memory allocated to it than needed, making subobject
+   sizes incorrect.  Effectively, the expression we generate is:
+
+   MIN(((sz > typesize && off <= sz / typesize)
+	|| (off > SIZE_MAX / 2 && wholesize - min(wholesize, size) > typesize
+	    && -off <= (wholesize - min(wholesize, size)) / typesize))
+       ? sz - off * typesize : 0, wholesize)
+
+   This is a fairly complex expression that may result in slow code in corner
+   cases but in most standard cases, many of those elements would be constant
+   (typesize certainly is) and is expected to be folded away.  */
 
 static tree
-size_for_offset (tree sz, tree off)
+size_for_offset (tree sz, tree off, tree wholesize, bool neg_offsets = false)
 {
+  tree typesize = NULL_TREE;
+  tree cond = NULL_TREE;
+  tree max_off = NULL_TREE;
+  tree tmp = NULL_TREE;
+
   /* A MEM_REF offset may be a pointer, where we need to figure out the
      multiplier based on the base type.  */
   if (POINTER_TYPE_P (TREE_TYPE (off)))
+    typesize = TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (off)));
+
+  /* Expression for positive offset.  */
+  if (typesize)
     {
-      tree typesize = TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (off)));
+      cond = fold_build2 (GT_EXPR, sizetype, sz, typesize);
+      max_off = fold_build2 (EXACT_DIV_EXPR, sizetype, sz, typesize);
+    }
+  else
+    max_off = sz;
 
-      if (typesize)
-	{
-	  /*  SZ < TYPESIZE ? SZ : TYPESIZE * MIN (SZ / TYPESIZE, OFF)  */
-	  tree cond = fold_build2 (LT_EXPR, sizetype, sz, typesize);
+  tmp = fold_build2 (LE_EXPR, sizetype, off, max_off);
+  cond = cond ? fold_build2 (TRUTH_ANDIF_EXPR, sizetype, cond, tmp) : tmp;
 
-	  tree tmp = fold_build2 (EXACT_DIV_EXPR, sizetype, sz, typesize);
-	  tmp = fold_build2 (MIN_EXPR, sizetype, tmp, off);
-	  tmp = fold_build2 (MULT_EXPR, sizetype, tmp, typesize);
+  /* Negative offsets.  */
+  if (wholesize && wholesize != sz && neg_offsets)
+    {
+      /* off > SIZE_MAX / 2 */
+      max_off = fold_build2 (RSHIFT_EXPR, sizetype, TYPE_MAX_VALUE (sizetype),
+			     size_one_node);
+      tree cond_neg = fold_build2 (GT_EXPR, sizetype, off, max_off);
 
-	  off = fold_build3 (COND_EXPR, sizetype, cond, sz, tmp);
+      tree neg_part = fold_build2 (MIN_EXPR, sizetype, wholesize, sz);
+      neg_part = fold_build2 (MINUS_EXPR, sizetype, wholesize, neg_part);
 
-	  return fold_build2 (MINUS_EXPR, sizetype, sz, off);
+      if (typesize)
+	{
+	  /* && wholesize - min(wholesize, size) > typesize */
+	  tmp = fold_build2 (GT_EXPR, sizetype, neg_part, typesize);
+	  cond_neg = fold_build2 (TRUTH_ANDIF_EXPR, sizetype, cond_neg, tmp);
+	  max_off = fold_build2 (EXACT_DIV_EXPR, sizetype, neg_part, typesize);
 	}
       else
-	off = fold_convert (sizetype, off);
+	max_off = neg_part;
+
+      /* && -off <= (wholesize - min(wholesize, size)) / typesize */
+      tmp = fold_build2 (MINUS_EXPR, sizetype, size_zero_node, off);
+      tmp = fold_build2 (LE_EXPR, sizetype, tmp, max_off);
+      cond_neg = fold_build2 (TRUTH_ANDIF_EXPR, sizetype, cond_neg, tmp);
+
+      cond = fold_build2 (TRUTH_ORIF_EXPR, sizetype, cond, cond_neg);
     }
 
-  off = fold_build2 (MIN_EXPR, sizetype, sz, off);
-  return fold_build2 (MINUS_EXPR, sizetype, sz, off);
+  off = typesize ? fold_build2 (MULT_EXPR, sizetype, sz, off) : off;
+
+  tree res = fold_build2 (MINUS_EXPR, sizetype, sz, off);
+  res = fold_build3 (COND_EXPR, sizetype, cond, res, size_zero_node);
+
+  /* Finally, cap the return value to wholesize.  This could typically happen
+     if the whole object did not get any actual storage, e.g. char var[0] or
+     less storage, e.g.
+     struct some_large_struct = __builtin_malloc (smolval).  */
+  if (wholesize)
+    res = fold_build2 (MIN_EXPR, sizetype, wholesize, res);
+
+  return res;
 }
 
 /* Peek through ADDR_EXPR operands to get the definition of the whole variable
@@ -239,7 +309,7 @@  whole_var_size (struct object_size_info *osi, tree pt_var,
 	  tree offset = wide_int_to_tree (size_type_node,
 					  mem_ref_offset (pt_var));
 
-	  pt_var_size = size_for_offset (pt_var_size, offset);
+	  pt_var_size = size_for_offset (pt_var_size, offset, pt_var_size);
 	}
     }
   else if (DECL_P (pt_var))
@@ -263,7 +333,8 @@  whole_var_size (struct object_size_info *osi, tree pt_var,
 
 static bool
 addr_dyn_object_size (struct object_size_info *osi, const_tree ptr,
-		      int object_size_type, tree *psize)
+		      int object_size_type, tree *psize,
+		      tree *wholesizep = NULL)
 {
   tree pt_var, pt_var_size = NULL_TREE, bytes;
 
@@ -289,12 +360,14 @@  addr_dyn_object_size (struct object_size_info *osi, const_tree ptr,
     {
       bytes = compute_object_offset (TREE_OPERAND (ptr, 0), pt_var);
       if (bytes != error_mark_node)
-	bytes = size_for_offset (pt_var_size, bytes);
+	bytes = size_for_offset (pt_var_size, bytes, pt_var_size);
     }
   else
     bytes = pt_var_size;
 
   *psize = bytes == error_mark_node ? NULL_TREE : bytes;
+  if (wholesizep)
+    *wholesizep = pt_var_size;
   return true;
 }
 
@@ -381,35 +454,63 @@  pass_through_call (const gcall *call)
   return NULL_TREE;
 }
 
+
 static void
-emit_size_stmts (gimple *stmt, tree size_ssa, tree size_expr)
+emit_size_stmts (gimple *stmt, tree wholesize_ssa,
+		 tree wholesize_expr, tree size_ssa, tree size_expr)
 {
   gimple_seq seq = NULL;
 
+  /* Gimplify the wholesize and size expressions.  */
+  if (!is_gimple_variable (wholesize_expr))
+    wholesize_expr = force_gimple_operand (wholesize_expr, &seq, true, NULL);
   if (!is_gimple_variable (size_expr))
-    size_expr = force_gimple_operand (size_expr, &seq, true, NULL);
+    {
+      gimple_seq s;
+      size_expr = force_gimple_operand (size_expr, &s, true, NULL);
+      gimple_seq_add_seq (&seq, s);
+    }
 
-  gassign *assign = gimple_build_assign (size_ssa, size_expr);
+  /* Assign them to their SSAs.  */
+  gassign *assign = gimple_build_assign (wholesize_ssa, wholesize_expr);
+  gimple_seq_add_stmt (&seq, assign);
+  assign = gimple_build_assign (size_ssa, size_expr);
   gimple_seq_add_stmt (&seq, assign);
 
-  /* Define object size right after the object is defined.  */
+  /* Define object size right before the object is defined, assuming that any
+     statements involved in evaluation of the object size expression precede
+     the definition statement.  For parameters, we don't have a definition
+     statement, so insert into the first code basic block.  */
   gimple_stmt_iterator i = gsi_for_stmt (stmt);
-  gsi_insert_seq_after (&i, seq, GSI_CONTINUE_LINKING);
+  gsi_insert_seq_before (&i, seq, GSI_CONTINUE_LINKING);
 }
 
 static void
-emit_size_phi_stmts (gimple *stmt, tree size_ssa, tree *sizes)
+emit_size_phi_stmts (gimple *stmt, tree wholesize_ssa, tree *wholesizes,
+		     tree size_ssa, tree *sizes)
 {
-  gphi *newphi = create_phi_node (size_ssa, gimple_bb (stmt));
+  gphi *wholephi = create_phi_node (wholesize_ssa, gimple_bb (stmt));
+  gphi *phi = create_phi_node (size_ssa, gimple_bb (stmt));
   gphi *obj_phi =  as_a <gphi *> (stmt);
 
   for (unsigned i = 0; i < gimple_phi_num_args (stmt); i++)
     {
+      gimple_seq seq = NULL;
+
+      /* If we built an expression, we will need to build statements
+	 and insert them on the edge right away.  */
+      if (!is_gimple_variable (wholesizes[i]))
+	wholesizes[i] = force_gimple_operand (wholesizes[i], &seq, true, NULL);
       if (!is_gimple_variable (sizes[i]))
 	{
-	  gimple_seq seq;
+	  gimple_seq s;
+	  sizes[i] = force_gimple_operand (sizes[i], &s, true, NULL);
+	  gimple_seq_add_seq (&seq, s);
+	}
+
+      if (seq)
+	{
 	  edge e = gimple_phi_arg_edge (obj_phi, i);
-	  sizes[i] = force_gimple_operand (sizes[i], &seq, true, NULL);
 
 	  /* Put the size definition before the last statement of the source
 	     block of the PHI edge.  This ensures that any branches at the end
@@ -421,14 +522,28 @@  emit_size_phi_stmts (gimple *stmt, tree size_ssa, tree *sizes)
 	  gsi_insert_seq_before (&gsi, seq, GSI_CONTINUE_LINKING);
 	}
 
-      add_phi_arg (newphi, sizes[i],
+      add_phi_arg (wholephi, wholesizes[i],
+		   gimple_phi_arg_edge (obj_phi, i),
+		   gimple_phi_arg_location (obj_phi, i));
+
+      add_phi_arg (phi, sizes[i],
 		   gimple_phi_arg_edge (obj_phi, i),
 		   gimple_phi_arg_location (obj_phi, i));
     }
 }
 
+/* Set upper bound to SIZE_MAX - 1 so that equality with (size_t) -1 fails.  */
+
+static tree
+size_bound_expr (tree sz)
+{
+  tree max = size_binop (MINUS_EXPR, TYPE_MAX_VALUE (sizetype), size_one_node);
+  return fold_build2 (MIN_EXPR, sizetype, max, sz);
+}
+
 static void
-eval_size_expr (tree var, tree size, tree *size_expr)
+eval_size_expr (tree var, tree wholesize, tree *wholesize_expr,
+		tree size, tree *size_expr)
 {
   if (size_expr != NULL)
     {
@@ -438,12 +553,16 @@  eval_size_expr (tree var, tree size, tree *size_expr)
 
       if (gimple_code (stmt) == GIMPLE_PHI)
 	{
-	  emit_size_phi_stmts (stmt, size, size_expr);
+	  emit_size_phi_stmts (stmt, wholesize, wholesize_expr,
+			       size, size_expr);
+	  delete[] wholesize_expr;
 	  delete[] size_expr;
 	}
       else
 	{
-	  emit_size_stmts (stmt, size, *size_expr);
+	  emit_size_stmts (stmt, wholesize, *wholesize_expr, size,
+			   size_bound_expr (*size_expr));
+	  delete wholesize_expr;
 	  delete size_expr;
 	}
     }
@@ -467,12 +586,20 @@  gimplify_size_exprs (object_size_info *osi, tree ptr)
 	{
 	  EXECUTE_IF_SET_IN_BITMAP (computed[object_size_type], 0, i, bi)
 	    {
+	      release_ssa_name (object_whole_sizes[object_size_type][i]);
 	      release_ssa_name (object_sizes[object_size_type][i]);
 	      if (gimple_code (SSA_NAME_DEF_STMT (ssa_name (i))) == GIMPLE_PHI
 		  && object_size_exprs[object_size_type][i])
-		delete [] object_size_exprs[object_size_type][i];
+		{
+		  delete [] object_whole_size_exprs[object_size_type][i];
+		  delete [] object_size_exprs[object_size_type][i];
+		}
 	      else
-		delete object_size_exprs[object_size_type][i];
+		{
+		  delete object_whole_size_exprs[object_size_type][i];
+		  delete object_size_exprs[object_size_type][i];
+		}
+	      object_whole_size_exprs[object_size_type][i] = NULL;
 	      object_size_exprs[object_size_type][i] = NULL;
 	    }
 	  bitmap_clear (computed[object_size_type]);
@@ -484,8 +611,12 @@  gimplify_size_exprs (object_size_info *osi, tree ptr)
   for (int object_size_type = 0; object_size_type <= 3; object_size_type++)
     EXECUTE_IF_SET_IN_BITMAP (computed[object_size_type], 0, i, bi)
       {
-	eval_size_expr (ssa_name (i), object_sizes[object_size_type][i],
+	eval_size_expr (ssa_name (i),
+			object_whole_sizes[object_size_type][i],
+			object_whole_size_exprs[object_size_type][i],
+			object_sizes[object_size_type][i],
 			object_size_exprs[object_size_type][i]);
+	object_whole_size_exprs[object_size_type][i] = NULL;
 	object_size_exprs[object_size_type][i] = NULL;
       }
 }
@@ -517,7 +648,32 @@  compute_builtin_dyn_object_size (tree ptr, int object_size_type, tree *psize,
       return false;
 
   if (computed[object_size_type] == NULL)
-    return false;
+    {
+      if (optimize || object_size_type & 1)
+	return false;
+
+      /* Like with __builtin_object_size, when not optimizing, rather than
+	 failing, make a small effort to determine the object size without the
+	 full benefit of the (costly) computation below.  */
+      gimple *def = SSA_NAME_DEF_STMT (ptr);
+      if (gimple_code (def) == GIMPLE_ASSIGN)
+	{
+	  tree_code code = gimple_assign_rhs_code (def);
+	  if (code == POINTER_PLUS_EXPR)
+	    {
+	      tree offset = gimple_assign_rhs2 (def);
+	      ptr = gimple_assign_rhs1 (def);
+
+	      if (compute_builtin_dyn_object_size (ptr, object_size_type,
+						   psize))
+		{
+		  *psize = size_for_offset (*psize, offset, *psize, true);
+		  return true;
+		}
+	    }
+	}
+      return false;
+    }
 
   if (bitmap_bit_p (computed[object_size_type], SSA_NAME_VERSION (ptr)))
     goto out;
@@ -530,6 +686,9 @@  compute_builtin_dyn_object_size (tree ptr, int object_size_type, tree *psize,
     {
       object_sizes[object_size_type].safe_grow (num_ssa_names, true);
       object_size_exprs[object_size_type].safe_grow (num_ssa_names, true);
+      object_whole_sizes[object_size_type].safe_grow (num_ssa_names, true);
+      object_whole_size_exprs[object_size_type].safe_grow (num_ssa_names,
+							   true);
     }
   if (dump_file)
     {
@@ -576,21 +735,37 @@  out:
    SSA_NAME.  */
 
 static void
-expr_object_size (struct object_size_info *osi, tree value, tree *sz)
+expr_object_size (struct object_size_info *osi, tree value, tree *sz,
+		  tree *wholeszp)
 {
   int object_size_type = osi->object_size_type;
-  tree bytes = NULL_TREE;
+  tree bytes = NULL_TREE, wholesz = NULL_TREE;
 
   if (TREE_CODE (value) == WITH_SIZE_EXPR)
     value = TREE_OPERAND (value, 0);
 
   if (TREE_CODE (value) == ADDR_EXPR)
-    addr_dyn_object_size (osi, value, object_size_type, &bytes);
+    addr_dyn_object_size (osi, value, object_size_type, &bytes, &wholesz);
 
   if (bytes)
     STRIP_NOPS (bytes);
 
+  if (wholesz)
+    STRIP_NOPS (wholesz);
+
   *sz = bytes;
+  *wholeszp = wholesz;
+}
+
+static tree
+make_object_size_name (tree ssa_name, tree *val, struct function *fun)
+{
+  if (ssa_name != NULL_TREE)
+    gcc_assert (*val == error_mark_node);
+  else
+    ssa_name = make_ssa_name_fn (fun, sizetype, 0);
+
+  return ssa_name;
 }
 
 static void
@@ -608,22 +783,29 @@  maybe_update_dependency_loop (struct object_size_info *osi, unsigned name,
 /* Add object size to the cache so that it can be reused.  */
 
 static void
-cache_object_size (struct object_size_info *osi, unsigned name, tree *sz)
+cache_object_size (struct object_size_info *osi, unsigned name, tree *sz,
+		   tree *wholesz)
 {
   int object_size_type = osi->object_size_type;
   struct function *fun = osi->fun;
 
-  gcc_assert (sz);
+  gcc_assert (sz && wholesz);
 
   maybe_update_dependency_loop (osi, name, *sz);
 
   /* Reuse SSA name if it was created for a dependency loop.  */
-  if (object_sizes[object_size_type][name] != NULL_TREE)
-    gcc_assert (*object_size_exprs[object_size_type][name] == error_mark_node);
-  else
-    object_sizes[object_size_type][name] = make_ssa_name_fn (fun, sizetype, 0);
+  object_sizes[object_size_type][name] =
+    make_object_size_name (object_sizes[object_size_type][name],
+			   object_size_exprs[object_size_type][name], fun);
   object_size_exprs[object_size_type][name] = sz;
 
+  /* Likewise for whole object size.  */
+  object_whole_sizes[object_size_type][name] =
+    make_object_size_name (object_whole_sizes[object_size_type][name],
+			   object_whole_size_exprs[object_size_type][name],
+			   fun);
+  object_whole_size_exprs[object_size_type][name] = wholesz;
+
   if (dump_file && (dump_flags & TDF_DETAILS))
     {
       fprintf (dump_file, "Caching size for ");
@@ -635,6 +817,14 @@  cache_object_size (struct object_size_info *osi, unsigned name, tree *sz)
       print_generic_expr (dump_file,
 			  *object_size_exprs[object_size_type][name],
 			  dump_flags);
+      fprintf (dump_file, ", Object whole size: ");
+      print_generic_expr (dump_file,
+			  object_whole_sizes[object_size_type][name],
+			  dump_flags);
+      fprintf (dump_file, " = ");
+      print_generic_expr (dump_file,
+			  *object_whole_size_exprs[object_size_type][name],
+			  dump_flags);
       fprintf (dump_file, "\n");
     }
 
@@ -644,21 +834,36 @@  cache_object_size (struct object_size_info *osi, unsigned name, tree *sz)
 /* Copy SZ and then call cache_object_size above.  */
 
 static void
-cache_object_size_copy (struct object_size_info *osi, unsigned name, tree sz)
+cache_object_size_copy (struct object_size_info *osi, unsigned name, tree sz,
+			tree wholesz)
 {
   int object_size_type = osi->object_size_type;
 
-  if (sz == NULL_TREE)
+  if (sz == NULL_TREE || wholesz == NULL_TREE)
     {
+      /* If we are unable to determine the size of a circular dependency,
+	 release its SSA.  */
       if (object_sizes[object_size_type][name] != NULL_TREE)
-	release_ssa_name (object_sizes[object_size_type][name]);
+	{
+	  release_ssa_name (object_sizes[object_size_type][name]);
+	  release_ssa_name (object_whole_sizes[object_size_type][name]);
+	}
       object_sizes[object_size_type][name] = NULL_TREE;
       object_size_exprs[object_size_type][name] = NULL;
+      object_whole_sizes[object_size_type][name] = NULL_TREE;
+      object_whole_size_exprs[object_size_type][name] = NULL;
       bitmap_set_bit (computed[object_size_type], name);
+      if (dump_file)
+	{
+	  fprintf (dump_file, "caching UNKNOWN size for ");
+	  print_generic_expr (dump_file, ssa_name (name), dump_flags);
+	  fprintf (dump_file, "\n");
+	}
+
       return;
     }
 
-  cache_object_size (osi, name, new tree (sz));
+  cache_object_size (osi, name, new tree (sz), new tree (wholesz));
 }
 
 /* Use size of ORIG for DEST and return it.  */
@@ -669,9 +874,59 @@  set_object_size_ssa (struct object_size_info *osi, tree dest, tree orig)
   collect_object_sizes_for (osi, orig);
 
   tree sz = object_sizes[osi->object_size_type][SSA_NAME_VERSION (orig)];
-  cache_object_size_copy (osi, SSA_NAME_VERSION (dest), sz);
+  tree wholesz =
+    object_whole_sizes[osi->object_size_type][SSA_NAME_VERSION (orig)];
+
+  cache_object_size_copy (osi, SSA_NAME_VERSION (dest), sz, wholesz);
 }
 
+/* Compute object_sizes for VAR, defined to the result of an assignment
+   with operator POINTER_PLUS_EXPR.  Return true if the object size might
+   need reexamination  later.  */
+
+static void
+plus_stmt_object_size (struct object_size_info *osi, tree var, gimple *stmt)
+{
+  int object_size_type = osi->object_size_type;
+  unsigned int varno = SSA_NAME_VERSION (var);
+  tree op0, op1, bytes = NULL_TREE, wholevar = NULL_TREE;
+
+  if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR)
+    {
+      op0 = gimple_assign_rhs1 (stmt);
+      op1 = gimple_assign_rhs2 (stmt);
+    }
+  else if (gimple_assign_rhs_code (stmt) == ADDR_EXPR)
+    {
+      tree rhs = TREE_OPERAND (gimple_assign_rhs1 (stmt), 0);
+      gcc_assert (TREE_CODE (rhs) == MEM_REF);
+      op0 = TREE_OPERAND (rhs, 0);
+      op1 = TREE_OPERAND (rhs, 1);
+    }
+  else
+    gcc_unreachable ();
+
+  /* Handle PTR + OFFSET here.  */
+  if (TREE_CODE (op0) == SSA_NAME)
+    {
+      unsigned op0no = SSA_NAME_VERSION (op0);
+
+      collect_object_sizes_for (osi, op0);
+      if (object_sizes[object_size_type][op0no])
+	{
+	  bytes = object_sizes[object_size_type][op0no];
+	  wholevar = object_whole_sizes[object_size_type][op0no];
+	  bytes = size_for_offset (bytes, op1, wholevar, true);
+	}
+    }
+  else if (TREE_CODE (op0) == ADDR_EXPR)
+    {
+      /* op0 will be ADDR_EXPR here.  */
+      if (addr_dyn_object_size (osi, op0, object_size_type, &bytes, &wholevar))
+	bytes = size_for_offset (bytes, op1, wholevar);
+    }
+  cache_object_size_copy (osi, varno, bytes, wholevar);
+}
 
 /* Compute object_sizes for PTR, defined to the result of a call.  */
 
@@ -680,11 +935,10 @@  call_object_size (struct object_size_info *osi, tree ptr, gcall *call)
 {
   gcc_assert (is_gimple_call (call));
 
-  cache_object_size_copy (osi, SSA_NAME_VERSION (ptr),
-			  alloc_object_size (call));
+  tree sz = alloc_object_size (call);
+  cache_object_size_copy (osi, SSA_NAME_VERSION (ptr), sz, sz);
 }
 
-
 /* Compute object sizes for VAR.
 
    - For allocation GIMPLE_CALL like malloc or calloc object size is the size
@@ -692,7 +946,9 @@  call_object_size (struct object_size_info *osi, tree ptr, gcall *call)
    - For a memcpy like GIMPLE_CALL that always returns one of its arguments,
      the object size is object size of that argument.
    - For GIMPLE_PHI, compute a PHI node with sizes of all branches in the PHI
-     node of the object.  */
+     node of the object.
+   - For GIMPLE_ASSIGN, return the size of the object the SSA name points
+     to.  */
 
 static void
 collect_object_sizes_for (struct object_size_info *osi, tree var)
@@ -705,13 +961,16 @@  collect_object_sizes_for (struct object_size_info *osi, tree var)
     return;
 
   if (bitmap_set_bit (osi->visited, varno))
-    object_sizes[object_size_type][varno] = NULL_TREE;
+    {
+      object_sizes[object_size_type][varno] = NULL_TREE;
+      object_whole_sizes[object_size_type][varno] = NULL_TREE;
+    }
   else
     {
       /* Add an SSA name but mark the expression as being an error_mark_node.
 	 When we go back up the stack, the error_mark_node should get
 	 overwritten by a proper expression.  */
-      cache_object_size (osi, varno, &error_mark_node);
+      cache_object_size (osi, varno, &error_mark_node, &error_mark_node);
       if (dump_file && (dump_flags & TDF_DETAILS))
 	{
 	  fprintf (dump_file, "Found a dependency loop at ");
@@ -732,6 +991,29 @@  collect_object_sizes_for (struct object_size_info *osi, tree var)
 
   switch (gimple_code (stmt))
     {
+    case GIMPLE_ASSIGN:
+      {
+	tree rhs = gimple_assign_rhs1 (stmt);
+	if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR
+	    || (gimple_assign_rhs_code (stmt) == ADDR_EXPR
+		&& TREE_CODE (TREE_OPERAND (rhs, 0)) == MEM_REF))
+	  plus_stmt_object_size (osi, var, stmt);
+	else if (gimple_assign_single_p (stmt)
+		 || gimple_assign_unary_nop_p (stmt))
+	  {
+	    if (TREE_CODE (rhs) == SSA_NAME
+		&& POINTER_TYPE_P (TREE_TYPE (rhs)))
+	      set_object_size_ssa (osi, var, rhs);
+	    else
+	      {
+		tree sz, wholesz;
+		expr_object_size (osi, rhs, &sz, &wholesz);
+		cache_object_size_copy (osi, varno, sz, wholesz);
+	      }
+	  }
+	break;
+      }
+
     case GIMPLE_CALL:
       {
 	gcall *call_stmt = as_a <gcall *> (stmt);
@@ -742,9 +1024,9 @@  collect_object_sizes_for (struct object_size_info *osi, tree var)
 	      set_object_size_ssa (osi, var, arg);
 	    else
 	      {
-		tree ret;
-		expr_object_size (osi, arg, &ret);
-		cache_object_size_copy (osi, varno, ret);
+		tree sz, wholesz;
+		expr_object_size (osi, arg, &sz, &wholesz);
+		cache_object_size_copy (osi, varno, sz, wholesz);
 	      }
 	  }
 	else
@@ -756,44 +1038,48 @@  collect_object_sizes_for (struct object_size_info *osi, tree var)
       {
 	unsigned i, num_args = gimple_phi_num_args (stmt);
 	tree *sizes = new tree[num_args] ();
+	tree *wholesizes = new tree[num_args] ();
 
 	/* Bail out if the size of any of the PHI arguments cannot be
 	   determined.  */
 	for (i = 0; i < gimple_phi_num_args (stmt); i++)
 	  {
 	    tree rhs = gimple_phi_arg_def (stmt, i);
-	    tree sz;
+	    tree sz, wholesz;
 
 	    if (TREE_CODE (rhs) != SSA_NAME)
-	      expr_object_size (osi, rhs, &sz);
+	      expr_object_size (osi, rhs, &sz, &wholesz);
 	    else
 	      {
 		collect_object_sizes_for (osi, rhs);
 		sz = object_sizes[object_size_type][SSA_NAME_VERSION (rhs)];
+		wholesz =
+		  object_whole_sizes[object_size_type][SSA_NAME_VERSION (rhs)];
 	      }
 
-	    if (sz == NULL_TREE)
+	    if (sz == NULL_TREE || wholesz == NULL_TREE)
 	      break;
 
 	    sizes[i] = sz;
+	    wholesizes[i] = wholesz;
 	  }
 
 	/* Record all possible sizes to build our PHI node later.  */
 	if (i == gimple_phi_num_args (stmt))
+	  cache_object_size (osi, varno, sizes, wholesizes);
+	else
 	  {
-	    cache_object_size (osi, varno, sizes);
-	    break;
+	    delete[] sizes;
+	    delete[] wholesizes;
+	    cache_object_size_copy (osi, varno, NULL_TREE, NULL_TREE);
 	  }
-	else
-	  delete[] sizes;
+	break;
       }
-    /* FALLTHROUGH */
 
     /* Bail out for all other cases.  */
     case GIMPLE_NOP:
-    case GIMPLE_ASSIGN:
     case GIMPLE_ASM:
-      cache_object_size_copy (osi, varno, NULL_TREE);
+      cache_object_size_copy (osi, varno, NULL_TREE, NULL_TREE);
       break;
 
     default:
@@ -817,6 +1103,9 @@  init_dynamic_object_sizes (void)
     {
       object_sizes[object_size_type].safe_grow (num_ssa_names, true);
       object_size_exprs[object_size_type].safe_grow (num_ssa_names, true);
+      object_whole_sizes[object_size_type].safe_grow (num_ssa_names, true);
+      object_whole_size_exprs[object_size_type].safe_grow (num_ssa_names,
+							   true);
       computed[object_size_type] = BITMAP_ALLOC (NULL);
     }
 }
@@ -833,6 +1122,8 @@  fini_dynamic_object_sizes (void)
     {
       object_sizes[object_size_type].release ();
       object_size_exprs[object_size_type].release ();
+      object_whole_sizes[object_size_type].release ();
+      object_whole_size_exprs[object_size_type].release ();
       BITMAP_FREE (computed[object_size_type]);
     }
 }