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 <ebotca...@adacore.com> * 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 <ebotca...@adacore.com> * 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;