On 07/27/2016 03:01 AM, Aldy Hernandez wrote:
Just in case this got lost in noise, since I know there was a lot of
back and forth between Martin Sebor and I.

This is the last iteration.

Tested on x86-64 Linux.

OK for trunk?

curr


gcc/

        * Makefile.in (OBJS): Add gimple-ssa-warn-walloca.o.
        * passes.def: Add two instances of pass_walloca.
        * tree-pass.h (make_pass_walloca): New.
        * gimple-ssa-warn-walloca.c: New file.
        * doc/invoke.texi: Document -Walloca, -Walloca-larger-than=, and
        -Wvla-larger-than= options.

gcc/c-family/

        * c.opt (Walloca): New.
        (Walloca-larger-than=): New.
        (Wvla-larger-than=): New.
As someone already noted, it's gimple-ssa-warn-alloca, not gimple-ssa-warn-walloca for the ChangeLog entry.

On the nittish side, you're mixing C and C++ comment styles. Choosing one and sticking with it seems better :-)



+@item -Walloca
+@opindex Wno-alloca
+@opindex Walloca
+This option warns on all uses of @code{alloca} in the source.
+
+@item -Walloca-larger-than=@var{n}
+This option warns on calls to @code{alloca} that are not bounded by a
+controlling predicate limiting its size to @var{n} bytes, or calls to
+@code{alloca} where the bound is unknown.
So for each of these little examples, I'd stuff the code into a trivial function definition and make "n" a parameter. That way it's obvious the value of "n" comes from a context where we don't initially know its range, but we may be able to narrow the range due to statements in the function.

;
+
+class pass_walloca : public gimple_opt_pass
+{
+public:
+  pass_walloca (gcc::context *ctxt)
+    : gimple_opt_pass(pass_data_walloca, ctxt), first_time_p (false)
+  {}
+  opt_pass *clone () { return new pass_walloca (m_ctxt); }
+  void set_pass_param (unsigned int n, bool param)
+    {
+      gcc_assert (n == 0);
+      first_time_p = param;
+    }
ISTM that you're using "first_time_p" here, but in passes.def you refer to this parameter as "strict_mode_p" in comments.

ie:

+      NEXT_PASS (pass_walloca, /*strict_mode_p=*/false);

I'd just drop the /*strict_mode_p*/ comment in both places it appears in your patch's change to passes.def. I think we've generally frowned on those embedded comments, even though some have snuck in.

+
+// We have a few heuristics up our sleeve to determine if a call to
+// alloca() is within bounds.  Try them out and return the type of
+// alloca call this is based on its argument.
+//
+// Given a known argument (ARG) to alloca() and an EDGE (E)
+// calculating said argument, verify that the last statement in the BB
+// in E->SRC is a gate comparing ARG to an acceptable bound for
+// alloca().  See examples below.
+//
+// MAX_SIZE is WARN_ALLOCA= adjusted for VLAs.  It is the maximum size
+// in bytes we allow for arg.
+//
+// If the alloca bound is determined to be too large, ASSUMED_LIMIT is
+// set to the bound used to determine this.  ASSUMED_LIMIT is only set
+// for ALLOCA_BOUND_MAYBE_LARGE and ALLOCA_BOUND_DEFINITELY_LARGE.
+//
+// Returns the alloca type.
+
+static enum alloca_type
+alloca_call_type_by_arg (tree arg, edge e, unsigned max_size,
+                        wide_int *assumed_limit)
So I wonder if you ought to have a structure here for the return value which contains the alloca type and assumed limit. I know in the past we avoided aggregate returns, but these days that doesn't seem necessary. Seems cleaner than having a return value and output parameters.

+{
+  // All the tests bellow depend on the jump being on the TRUE path.
+  if (!(e->flags & EDGE_TRUE_VALUE))
+    return ALLOCA_UNBOUNDED;
Seems like a fairly arbitrary and undesirable limitation. Couldn't the developer just have easily written

if (arg > N>
   x = malloc (...)
else
   x = alloca (...)

It also seems like you'd want to handle the set of LT/LE/GT/GE rather than just LE. Or is it the case that we always canonicalize LT into LE by adjusting the constant (I vaguely remember running into that in RTL, so it's entirely possible and there'd likely be a canonicalization of GT/GE as well).

It also seems that once Andrew's infrastructure is in place this becomes dead code as we can just ask for the range at a point in the program, including for each incoming edge. You might want a comment to that effect.




+
+  /* Check for:
+     if (arg .cond. LIMIT) -or- if (LIMIT .cond. arg)
+       alloca(arg);
+
+     Where LIMIT has a bound of unknown range.  */
+  tree limit = NULL;
+  if (gimple_cond_lhs (last) == arg)
+    limit = gimple_cond_rhs (last);
+  else if (gimple_cond_rhs (last) == arg)
+    limit = gimple_cond_lhs (last);
+  if (limit && TREE_CODE (limit) == SSA_NAME)
+    {
+      wide_int min, max;
+      value_range_type range_type = get_range_info (limit, &min, &max);
+      if (range_type == VR_UNDEFINED || range_type == VR_VARYING)
+       return ALLOCA_BOUND_UNKNOWN;
+      // FIXME: We could try harder here and handle a possible range
+      // or anti-range.  Hopefully the upcoming changes to range info
+      // will give us finer grained info, and we can avoid somersaults
+      // here.
Ah, can't you set *assumed_limit here? It's just a matter of walking through the cases and making the most conservative assumption. So assume the condition is LT, both objects are unsigned types and LIMIT has a range like [5..25]. Then the resulting *assumed_limit must be 24.

ISTM it might also be worth checking VRP here -- I'd expect it to be able to make this kind of determination. that would be independent of this work (in the sense that if VRP isn't creating ranges for this, it should be fixed independently).


+    }
+
+  return ALLOCA_UNBOUNDED;
+}
+
+// Return TRUE if SSA's definition is a cast from a signed type.
+// If so, set *INVALID_CASTED_TYPE to the signed type.
+
+static bool
+cast_from_signed_p (tree ssa, tree *invalid_casted_type)
+{
+  gimple *def = SSA_NAME_DEF_STMT (ssa);
+  if (def
+      && !gimple_nop_p (def)
+      && gimple_assign_cast_p (def)
+      && !TYPE_UNSIGNED (TREE_TYPE (gimple_assign_rhs1 (def))))
+    {
+      *invalid_casted_type = TREE_TYPE (gimple_assign_rhs1 (def));
+      return true;
+    }
+  return false;
Note that we may have a cast from a signed type, but if the RHS of that cast has a positive range, then the cast isn't going to case the wrap-around effect that is so problematical. That might help cut down false positives.

+}
+
+// Return TURE if X has a maximum range of MAX, basically covering the
+// entire domain, in which case it's no range at all.
s/TURE/TRUE/


+
+static bool
+is_max (tree x, wide_int max)
+{
+  return wi::max_value (TREE_TYPE (x)) == max;
+}
I'm a bit surprised we don't have this kind of utility function elsewhere. I wonder if it'd be better to conceptualize this as a range query since it looks like you're always using get_range_info to get an object's range, then comparing what that returns to the maximal value of hte object's type. Maybe that's too much bikeshedding...



+
+// Analyze the alloca call in STMT and return an `enum alloca_type'
+// explaining what type of alloca it is.  IS_VLA is set if the alloca
+// call is really a BUILT_IN_ALLOCA_WITH_ALIGN, signifying a VLA.
+//
+// If the alloca bound is determined to be too large, ASSUMED_LIMIT is
+// set to the bound used to determine this.  ASSUMED_LIMIT is only set
+// for ALLOCA_BOUND_MAYBE_LARGE and ALLOCA_BOUND_DEFINITELY_LARGE.
+//
+// If the alloca call may be too large because of a cast from a signed
+// type to an unsigned type, set *INVALID_CASTED_TYPE to the
+// problematic signed type.
+
+static enum alloca_type
+alloca_call_type (gimple *stmt, bool is_vla, wide_int *assumed_limit,
+                 tree *invalid_casted_type)
Again, consider an aggregate return.

+{
+  gcc_assert (gimple_alloca_call_p (stmt));
+  tree len = gimple_call_arg (stmt, 0);
+  enum alloca_type w = ALLOCA_UNBOUNDED;
+  wide_int min, max;
+
+  gcc_assert (!is_vla || warn_vla_limit > 0);
+  gcc_assert (is_vla || warn_alloca_limit > 0);
+
+  // Adjust warn_alloca_max_size for VLAs, by taking the underlying
+  // type into account.
+  unsigned HOST_WIDE_INT max_size;
+  if (is_vla)
+    max_size = (unsigned HOST_WIDE_INT) warn_vla_limit;
+  else
+    max_size = (unsigned HOST_WIDE_INT) warn_alloca_limit;
+
+  // Check for the obviously bounded case.
+  if (TREE_CODE (len) == INTEGER_CST)
+    {
+      if (tree_to_uhwi (len) > max_size)
+       {
+         *assumed_limit = len;
+         return ALLOCA_BOUND_DEFINITELY_LARGE;
+       }
+      if (integer_zerop (len))
+       return ALLOCA_ARG_IS_ZERO;
+      w = ALLOCA_OK;
+    }
+  else if (TREE_CODE (len) != SSA_NAME)
+    return ALLOCA_UNBOUNDED;
Hmm, other than INTEGER_CST and SSA_NAME, is there any other nodes we can get here? Perhaps we get DECLs and such, particularly when not optimizing?!?

+  // Check the range info if available.
+  else
+    {
+      if (value_range_type range_type = get_range_info (len, &min, &max))
+       {
+         if (range_type == VR_RANGE)
+           {
+             if (wi::leu_p (max, max_size))
+               w = ALLOCA_OK;
+             else if (is_max (len, max))
+               {
+                 // A cast may have created a range we don't care
+                 // about.  For instance, a cast from 16-bit to
+                 // 32-bit creates a range of 0..65535, even if there
+                 // is not really a determinable range in the
+                 // underlying code.  In this case, look through the
+                 // cast at the original argument, and fall through
+                 // to look at other alternatives.
+                 gimple *def = SSA_NAME_DEF_STMT (len);
+                 if (gimple_assign_cast_p (def))
+                   len = gimple_assign_rhs1 (def);
+               }
+             else
+               {
+                 /* If `len' is merely a cast that is being
+                    calculated right before the call to alloca, look
+                    at the range for the original value.
Yea, this is similar to my comment earlier that the RHS of the cast may have a known range (non-negative) that allows us to not worry about the cast creating a huge integer. I think you can add handling that case here without too much trouble. Though you might consider pulling all the casting bits into a separate function.

+
+                    This avoids the cast creating a range where the
+                    original expression did not have a range:
+
+                    # RANGE [0, 18446744073709551615] NONZERO 4294967295
+                    _2 = (long unsigned int) n_7(D);
+                    p_9 = __builtin_alloca (_2);
Note this example would make more sense if the type of n_7 was explicit.


+         else if (range_type == VR_ANTI_RANGE)
+           {
+             // There may be some wrapping around going on.  Catch it
+             // with this heuristic.  Hopefully, this VR_ANTI_RANGE
+             // nonsense will go away, and we won't have to catch the
+             // sign conversion problems with this crap.
+             if (cast_from_signed_p (len, invalid_casted_type))
+               return ALLOCA_CAST_FROM_SIGNED;
Another place where casting from an object with a non-negative range ought to be filtered out as not problematical.


+
+  // If we couldn't find anything, try a few heuristics for things we
+  // can easily determine.  Check these misc cases but only accept
+  // them if all predecessors have a known bound.
+  basic_block bb = gimple_bb (stmt);
+  if (w == ALLOCA_UNBOUNDED)
+    {
+      w = ALLOCA_OK;
+      for (unsigned ix = 0; ix < EDGE_COUNT (bb->preds); ix++)
+       {
+         enum alloca_type w
+           = alloca_call_type_by_arg (len, EDGE_PRED (bb, ix), max_size,
+                                      assumed_limit);
+         if (w != ALLOCA_OK)
+           return w;
+       }
So this is an interesting tidbit that answers my questions about how we use alloca_call_type_by_arg. Essentially this is meant to catch the merge point for flow control and give a conservative warning. That's fine and good. But ISTM it's really a bit of a hack. What if we have something like this:

  X   Y   Z
   \  |  /
     \|/
      A
     / \
    /   \
   B     C
        / \
       /   \
      D     E


Where the alloca call is in E and the incoming edges to A actually have useful information about the argument to the alloca call.

ISTM you need to be doing something with the dominator tree here to find the merge point(s) where we might know something useful. And it's this kind of test that makes me wonder about re-purposing some of the path analysis code from tree-ssa-uninit.c. It may be the case that the path Z->A->C->E is unfeasible, but left in the CFG because duplication to expose the unfeasible path was unprofitable. If it turns out that the only argument that causes problems comes from the edge Z->A, then we wouldn't want to warn in this case.

 I don't see Andrew's work necessarily being able to solve this problem.





+    }
+
+  return w;
+}
+
+// Return TRUE if the alloca call in STMT is in a loop, otherwise
+// return FALSE. As an exception, ignore alloca calls for VLAs that
+// occur in a loop since those will be cleaned up when they go out of
+// scope.
+
+static bool
+in_loop_p (bool is_vla, gimple *stmt)
+{
+  basic_block bb = gimple_bb (stmt);
+  if (bb->loop_father
+      // ?? Functions with no loops get a loop_father?  I
+      // don't get it.  The following conditional seems to do
+      // the trick to exclude such nonsense.
+      && bb->loop_father->header != ENTRY_BLOCK_PTR_FOR_FN (cfun))
I believe there is a "loop" that encompasses the whole function.

I'll probably have more comments after the next iteration. I'm also real curious what you've found poking around at GDB with this warning :-)
Jeff

Reply via email to