Hi,
this is a regression present on the mainline and 4.7 branch for platforms using
SJLJ exceptions, e.g. the ARM. The scenario is as follows: the main procedure
calls My_Iterators.Current which has a pragma Inline on it. The procedure
also has exceptions handlers so there is an abnormal edge from the BB
containing the call to My_Iterators.Current (which is therefore the last
statement in the BB) to the setjmp dispatcher.
The local-pure-const pass computes that My_Iterators.Current is pure and may
not terminate (DECL_PURE_P && DECL_LOOPING_CONST_OR_PURE_P) because it is pure
and contains a call to gnat_check, which is no-return, and
state_from_flags (&call_state, &call_looping, flags,
((flags & (ECF_NORETURN | ECF_NOTHROW))
== (ECF_NORETURN | ECF_NOTHROW))
|| (!flag_exceptions && (flags & ECF_NORETURN)));
considers that, with !flag_exceptions, a no-return function call can really
never return, including by exceptional means.
The early SRA pass then inserts a statement in the procedure right after the
call to My_Iterators.Current, so at the end of the BB, because stmt_ends_bb_p
has returned false on the call, in turn because the statement is ECF_PURE and
is_ctrl_altering_stmt has
/* A non-pure/const call alters flow control if the current
function has nonlocal labels. */
if (!(flags & (ECF_CONST | ECF_PURE | ECF_LEAF))
&& cfun->has_nonlocal_label)
return true;
i.e. doesn't return true if ECF_PURE. As a consequence, the abnormal edge from
the BB to the setjmp receiver is deleted.
When My_Iterators.Current gets inlined into P, the call to gnat_check is copied
into it and, since stmt_can_make_abnormal_goto returns true on it, a new
abnormal edge to the setjmp dispatcher is created. The compiler aborts in
update_ssa_across_abnormal_edges because it cannot find the original abnormal
edge that it needs to use in order to complete the new one.
The cause is the discrepancy between local-pure-const, is_ctrl_altering_stmt
and stmt_can_make_abnormal_goto (the latter two themselves disagreeing) as to
when a call can return exceptionally/make an abnormal goto. It's clear that
(!flag_exceptions && (flags & ECF_NORETURN))
overlooks the __builtin_setjmp/__builtin_longjmp constructs so is optimistic at
best. But we cannot really do better in local-pure-const, short of removing
the condition entirely.
The interesting thing is that stmt_can_make_abnormal_goto, unlike the related
is_ctrl_altering_stmt, doesn't consider that a mere ECF_PURE can change the
property of a call wrt control flow:
bool
stmt_can_make_abnormal_goto (gimple t)
{
if (computed_goto_p (t))
return true;
if (is_gimple_call (t))
return (gimple_has_side_effects (t) && cfun->has_nonlocal_label
&& !(gimple_call_flags (t) & ECF_LEAF));
return false;
}
because it tests gimple_has_side_effects, which is still true if the call may
not return:
if (is_gimple_call (s))
{
int flags = gimple_call_flags (s);
/* An infinite loop is considered a side effect. */
if (!(flags & (ECF_CONST | ECF_PURE))
|| (flags & ECF_LOOPING_CONST_OR_PURE))
return true;
return false;
}
So, in the end, a reasonable fix might be to unify the condition used by
is_ctrl_altering_stmt and stmt_can_make_abnormal_goto, by using the most
conservative one (the latter), which happens to also cover the optimistic
semantics used by local-pure-const.
Tested on x86_64-suse-linux, OK for mainline and 4.7 branch?
2012-03-28 Eric Botcazou <[email protected]>
* tree-cfg.c (call_can_make_abnormal_goto): New predicate.
(stmt_can_make_abnormal_goto): Use it.
(is_ctrl_altering_stmt): Likewise.
2012-03-28 Eric Botcazou <[email protected]>
* gnat.dg/controlled6.adb: New test.
* gnat.dg/controlled6_pkg.ads: New helper.
* gnat.dg/controlled6_pkg-iterators.ad[sb]: Likewise.
--
Eric Botcazou
Index: tree-cfg.c
===================================================================
--- tree-cfg.c (revision 185857)
+++ tree-cfg.c (working copy)
@@ -2273,6 +2273,43 @@ gimple_cfg2vcg (FILE *file)
Miscellaneous helpers
---------------------------------------------------------------------------*/
+/* Return true if T, a GIMPLE_CALL, can make an abnormal transfer of control
+ flow. Transfers of control flow associated with EH are excluded. */
+
+static bool
+call_can_make_abnormal_goto (gimple t)
+{
+ /* If the function has no non-local labels, then a call cannot make an
+ abnormal transfer of control. */
+ if (!cfun->has_nonlocal_label)
+ return false;
+
+ /* Likewise if the call has no side effects. */
+ if (!gimple_has_side_effects (t))
+ return false;
+
+ /* Likewise if the called function is leaf. */
+ if (gimple_call_flags (t) & ECF_LEAF)
+ return false;
+
+ return true;
+}
+
+
+/* Return true if T can make an abnormal transfer of control flow.
+ Transfers of control flow associated with EH are excluded. */
+
+bool
+stmt_can_make_abnormal_goto (gimple t)
+{
+ if (computed_goto_p (t))
+ return true;
+ if (is_gimple_call (t))
+ return call_can_make_abnormal_goto (t);
+ return false;
+}
+
+
/* Return true if T represents a stmt that always transfers control. */
bool
@@ -2306,10 +2343,8 @@ is_ctrl_altering_stmt (gimple t)
{
int flags = gimple_call_flags (t);
- /* A non-pure/const call alters flow control if the current
- function has nonlocal labels. */
- if (!(flags & (ECF_CONST | ECF_PURE | ECF_LEAF))
- && cfun->has_nonlocal_label)
+ /* A call alters control flow if it can make an abnormal goto. */
+ if (call_can_make_abnormal_goto (t))
return true;
/* A call also alters control flow if it does not return. */
@@ -2367,21 +2402,6 @@ simple_goto_p (gimple t)
}
-/* Return true if T can make an abnormal transfer of control flow.
- Transfers of control flow associated with EH are excluded. */
-
-bool
-stmt_can_make_abnormal_goto (gimple t)
-{
- if (computed_goto_p (t))
- return true;
- if (is_gimple_call (t))
- return (gimple_has_side_effects (t) && cfun->has_nonlocal_label
- && !(gimple_call_flags (t) & ECF_LEAF));
- return false;
-}
-
-
/* Return true if STMT should start a new basic block. PREV_STMT is
the statement preceding STMT. It is used when STMT is a label or a
case label. Labels should only start a new basic block if their
-- { dg-do compile }
-- { dg-options "-O -gnatn" }
with Ada.Text_IO; use Ada.Text_IO;
with Controlled6_Pkg;
with Controlled6_Pkg.Iterators;
procedure Controlled6 is
type String_Access is access String;
package My_Q is new Controlled6_Pkg (String_Access);
package My_Iterators is new My_Q.Iterators (0);
use My_Iterators;
Iterator : Iterator_Type := Find;
begin
loop
exit when Is_Null (Iterator);
Put (Current (Iterator).all & ' ');
Find_Next (Iterator);
end loop;
end;
package body Controlled6_Pkg.Iterators is
function Find return Iterator_Type is
Iterator : Iterator_Type;
begin
return Iterator;
end Find;
function Current (Iterator : in Iterator_Type) return T is begin
return Iterator.Current.Item;
end Current;
procedure Find_Next (Iterator : in out Iterator_Type) is begin
Iterator.Current := null;
end Find_Next;
function Is_Null (Iterator : in Iterator_Type) return Boolean is begin
return Iterator.Current = null;
end Is_Null;
end Controlled6_Pkg.Iterators;
with Ada.Finalization;
generic
type T is private;
package Controlled6_Pkg is
type Node_Type is record
Item : T;
end record;
type Node_Access_Type is access Node_Type;
end Controlled6_Pkg;
with Ada.Finalization;
generic
I : Integer;
package Controlled6_Pkg.Iterators is
type Iterator_Type is new Ada.Finalization.Controlled with record
Current : Node_Access_Type;
end record;
function Find return Iterator_Type;
function Current (Iterator : in Iterator_Type) return T;
pragma Inline (Current);
procedure Find_Next (Iterator : in out Iterator_Type);
function Is_Null (Iterator : in Iterator_Type) return Boolean;
end Controlled6_Pkg.Iterators;