diff mbox series

Do not set nothrow flag on recursive function with stack checking

Message ID 2604930.7AH0sRlHEb@polaris
State New
Headers show
Series Do not set nothrow flag on recursive function with stack checking | expand

Commit Message

Eric Botcazou April 26, 2018, 2:29 p.m. UTC
Hi,

when stack checking is enabled in Ada, it is supposed to be able to handle the 
case of a recursive function that does essentially nothing.  But in this case 
the IPA machinery will compute that the function is nothrow, which means that 
the Storage_Error exception cannot be caught by the caller.

Tested on x86-64/Linux, OK for the mainline?


2018-04-26  Eric Botcazou  <ebotcazou@adacore.com>

	* ipa-pure-const.c (check_call): In non-IPA mode, set can_throw flag
	for a recursive call that can throw if stack checking is enabled.
	(check_stmt): Fix formatting.
	(propagate_nothrow): Set can_throw for a recursive call if exceptions
	and stack checking are enabled.
	(pass_nothrow::execute): Do not set the nothrow flag if there is a
	recursive call that can throw and stack checking is enabled.


2018-04-26  Eric Botcazou  <ebotcazou@adacore.com>

	* gnat.dg/stack_check4.adb: New test.

Comments

Richard Biener April 27, 2018, 9:09 a.m. UTC | #1
On Thu, Apr 26, 2018 at 4:29 PM, Eric Botcazou <ebotcazou@adacore.com> wrote:
> Hi,
>
> when stack checking is enabled in Ada, it is supposed to be able to handle the
> case of a recursive function that does essentially nothing.  But in this case
> the IPA machinery will compute that the function is nothrow, which means that
> the Storage_Error exception cannot be caught by the caller.
>
> Tested on x86-64/Linux, OK for the mainline?

This looks not a generic enough fix to me - consider

void foo(void) { int a[100000]; a[0] = 1; a[99999] = 1; }
int main() { try { foo (); } catch (...) {} }

with -fnon-call-exceptions.  If we'd like to catch the SEGV from stack overflows
then your fix doesn't handle the non-recursive case nor the case where
-fstack-check
is not supplied.  But the compiler - when the accesses are in-range - will not
consider the accesses trapping.

So to me your attempt in fixing this isn't complete but a complete fix would
be quite pessimizing :/ (for -fnon-call-exceptions)

At least all this should be documented somewhere, that is, what to expect
when trying to catch stack faults in general with -fnon-call-exceptions
[-fstack-check].

Richard.

>
> 2018-04-26  Eric Botcazou  <ebotcazou@adacore.com>
>
>         * ipa-pure-const.c (check_call): In non-IPA mode, set can_throw flag
>         for a recursive call that can throw if stack checking is enabled.
>         (check_stmt): Fix formatting.
>         (propagate_nothrow): Set can_throw for a recursive call if exceptions
>         and stack checking are enabled.
>         (pass_nothrow::execute): Do not set the nothrow flag if there is a
>         recursive call that can throw and stack checking is enabled.
>
>
> 2018-04-26  Eric Botcazou  <ebotcazou@adacore.com>
>
>         * gnat.dg/stack_check4.adb: New test.
>
> --
> Eric Botcazou
Eric Botcazou April 28, 2018, 9:20 a.m. UTC | #2
> This looks not a generic enough fix to me - consider
> 
> void foo(void) { int a[100000]; a[0] = 1; a[99999] = 1; }
> int main() { try { foo (); } catch (...) {} }
> 
> with -fnon-call-exceptions.  If we'd like to catch the SEGV from stack
> overflows then your fix doesn't handle the non-recursive case nor the case
> where -fstack-check is not supplied.

But -fstack-check is required to detect stack overflows...  Moreover the above 
testcase will be entirely optimized at -O so there is no stack overflow at -O.

> So to me your attempt in fixing this isn't complete but a complete fix would
> be quite pessimizing :/ (for -fnon-call-exceptions)

Then let's at least fix the recursive case since it's simple and cheap.  But I 
can indeed key this on -fnon-call-exceptions explicitly.

> At least all this should be documented somewhere, that is, what to expect
> when trying to catch stack faults in general with -fnon-call-exceptions
> [-fstack-check].

It's already documented that you need -fstack-check to detect stack overflows.
But I can add a blurb to the -fnon-call-exceptions entry about it.

Revised patch attached.
Richard Biener April 30, 2018, 10:56 a.m. UTC | #3
On Sat, Apr 28, 2018 at 11:20 AM, Eric Botcazou <ebotcazou@adacore.com> wrote:
>> This looks not a generic enough fix to me - consider
>>
>> void foo(void) { int a[100000]; a[0] = 1; a[99999] = 1; }
>> int main() { try { foo (); } catch (...) {} }
>>
>> with -fnon-call-exceptions.  If we'd like to catch the SEGV from stack
>> overflows then your fix doesn't handle the non-recursive case nor the case
>> where -fstack-check is not supplied.
>
> But -fstack-check is required to detect stack overflows...  Moreover the above
> testcase will be entirely optimized at -O so there is no stack overflow at -O.
>
>> So to me your attempt in fixing this isn't complete but a complete fix would
>> be quite pessimizing :/ (for -fnon-call-exceptions)
>
> Then let's at least fix the recursive case since it's simple and cheap.  But I
> can indeed key this on -fnon-call-exceptions explicitly.
>
>> At least all this should be documented somewhere, that is, what to expect
>> when trying to catch stack faults in general with -fnon-call-exceptions
>> [-fstack-check].
>
> It's already documented that you need -fstack-check to detect stack overflows.
> But I can add a blurb to the -fnon-call-exceptions entry about it.
>
> Revised patch attached.

Is the propagate_nothrow hunk really necessary since you set ->can_throw = true
in local analysis?  Again this not only handles only recursion but also only
direct recursion.  I wonder if there's a way to prove (or at least estimate with
good confidence) that a function does _not_ need local stack space?  Thus
for -fstack-check -fnon-call-exceptions consider all functions not
marked explicitely
as can_throw unless we "prove" the opposite?  Or consider all non-leaf functions
as possibly throwing that way?

I'm not against your patch but I think that this kind of limitations
need to be documented.

Index: doc/invoke.texi
===================================================================
--- doc/invoke.texi     (revision 259642)
+++ doc/invoke.texi     (working copy)
@@ -12812,6 +12812,9 @@ not exist everywhere.  Moreover, it only
 instructions to throw exceptions, i.e.@: memory references or floating-point
 instructions.  It does not allow exceptions to be thrown from
 arbitrary signal handlers such as @code{SIGALRM}.
+This option must be specified if you enable @option{-fstack-check} and
+want stack overflows to throw exceptions.  Note that this again requires
+platform-specific runtime support.


I'd phrase it as "If you want to handle stack overflows as exceptions
you need to
enable @option{-fstack-check} in addition to this option.  Support for this is
experimental, some stack overflows might not be catchable."

Honza, do you have any suggestions here for the issue of detecting possible
recursion as opposted to just direct recursion?

Richard.

> --
> Eric Botcazou
Jan Hubicka April 30, 2018, 11:37 a.m. UTC | #4
> 
> Honza, do you have any suggestions here for the issue of detecting possible
> recursion as opposted to just direct recursion?

Well, you can look for strongly connected components but you must assume that
every call out of the current unit that is not leaf may call any function that
is externally visible or has address taken. So basically every function that may
lead to external call is possibly recursive because you can assume that every
function is reachable (with exception of main).

This will rule out many nothrow candidates but I do not see how that can be
strenghtened.

Honza
> 
> Richard.
> 
> > --
> > Eric Botcazou
diff mbox series

Patch

Index: ipa-pure-const.c
===================================================================
--- ipa-pure-const.c	(revision 259642)
+++ ipa-pure-const.c	(working copy)
@@ -674,12 +674,19 @@  check_call (funct_state local, gcall *ca
     local->can_free = true;
 
   /* When not in IPA mode, we can still handle self recursion.  */
-  if (!ipa && callee_t
+  if (!ipa
+      && callee_t
       && recursive_call_p (current_function_decl, callee_t))
     {
       if (dump_file)
-        fprintf (dump_file, "    Recursive call can loop.\n");
+        fprintf (dump_file, "    recursive call can loop\n");
       local->looping = true;
+      if (possibly_throws_externally && flag_stack_check)
+	{
+	  if (dump_file)
+	    fprintf (dump_file, "    recursive call can throw\n");
+	  local->can_throw = true;
+	}
     }
   /* Either callee is unknown or we are doing local analysis.
      Look to see if there are any bits available for the callee (such as by
@@ -795,8 +802,7 @@  check_stmt (gimple_stmt_iterator *gsip,
       print_gimple_stmt (dump_file, stmt, 0);
     }
 
-  if (gimple_has_volatile_ops (stmt)
-      && !gimple_clobber_p (stmt))
+  if (gimple_has_volatile_ops (stmt) && !gimple_clobber_p (stmt))
     {
       local->pure_const_state = IPA_NEITHER;
       if (dump_file)
@@ -808,8 +814,7 @@  check_stmt (gimple_stmt_iterator *gsip,
 			    ipa ? check_ipa_load : check_load,
 			    ipa ? check_ipa_store :  check_store);
 
-  if (gimple_code (stmt) != GIMPLE_CALL
-      && stmt_could_throw_p (stmt))
+  if (gimple_code (stmt) != GIMPLE_CALL && stmt_could_throw_p (stmt))
     {
       if (cfun->can_throw_non_call_exceptions)
 	{
@@ -1822,13 +1827,17 @@  propagate_nothrow (void)
 		     not be interposed.
 		     When callee is compiled with non-call exceptions we also
 		     must check that the declaration is bound to current
-		     body as other semantically equivalent body may still
-		     throw.  */
+		     body as other semantically equivalent body may throw.
+		     Moreover, if the call is recursive, we must consider that
+		     the function may throw a stack overflow exception.  */
 		  if (avail <= AVAIL_INTERPOSABLE
 		      || (!TREE_NOTHROW (y->decl)
 			  && (get_function_state (y)->can_throw
 			      || (opt_for_fn (y->decl, flag_non_call_exceptions)
-				  && !e->callee->binds_to_current_def_p (w)))))
+				  && !e->callee->binds_to_current_def_p (w))
+			      || (y == w
+				  && flag_exceptions
+				  && flag_stack_check))))
 		    can_throw = true;
 		}
 	      for (ie = w->indirect_calls; ie && !can_throw;
@@ -2288,7 +2297,7 @@  make_pass_warn_function_noreturn (gcc::c
   return new pass_warn_function_noreturn (ctxt);
 }
 
-/* Simple local pass for pure const discovery reusing the analysis from
+/* Simple local pass for nothrow discovery reusing the analysis from
    ipa_pure_const.   This pass is effective when executed together with
    other optimization passes in early optimization pass queue.  */
 
@@ -2352,8 +2361,9 @@  pass_nothrow::execute (function *)
 	    if (is_gimple_call (gsi_stmt (gsi)))
 	      {
 		tree callee_t = gimple_call_fndecl (gsi_stmt (gsi));
-		if (callee_t && recursive_call_p (current_function_decl,
-						  callee_t))
+		if (callee_t
+		    && recursive_call_p (current_function_decl, callee_t)
+		    && !flag_stack_check)
 		  continue;
 	      }