Version 1.1 of ais/ai-00414.txt

Unformatted version of ais/ai-00414.txt version 1.1
Other versions for file ais/ai-00414.txt

!standard 6.05.01 (01)          05-02-02 AI95-00414/01
!standard 11.04.01 (04)
!standard 11.04.01 (14)
!class amendment 05-02-02
!status work item 05-02-02
!status received 05-02-02
!priority Medium
!difficulty Easy
!subject pragma No_Return for overriding procedures
!summary
This AI corrects some problems with AI-00329/06, which defines pragma No_Return.
!problem
The goal of pragma No_Return is to ensure that a call to a No_Return procedure will not return normally. The main problem with AI-00329 is that there is no such assurance in the case of a dispatching call. The other problems are nitpicks; they are mentioned in the discussion.
!proposal
We ensure that dispatching calls work properly by requiring pragma No_Return on a procedure that overrides a dispatching No_Return procedure.
!wording
AI-00329 adds a new clause, as follows:
6.5.1 Pragma No_Return
A pragma No_Return indicates that a procedure can return only by propagating an exception.
Syntax
The form of a pragma No_Return, which is a program unit pragma (see 10.1.5), is as follows:
pragma No_Return(local_name {, local_name});
Legality Rules
The pragma shall apply to one or more procedures or generic procedures.
If a pragma No_Return applies to a procedure or a generic procedure, there shall be no return_statements that apply to that procedure.
Static Semantics
If a pragma No_Return applies to a generic procedure, pragma No_Return applies to all instances of that generic procedure.
Dynamic Semantics
If a pragma No_Return applies to a procedure, then the exception Program_Error is raised at the point of the call of the procedure if the procedure body completes normally.
[End of old wording from AI-00329.]
----------------
This AI replaces 6.5.1 with the following:
6.5.1 Pragma No_Return
A pragma No_Return indicates that a procedure cannot return normally[; it may propagate an exception or loop forever].
Syntax
The form of a pragma No_Return, which is a representation pragma (see 13.1), is as follows:
pragma No_Return(local_name {, local_name});
Legality Rules
Each local_name shall denote one or more procedures or generic procedures; the denoted entities are "non-returning". The local_name shall not denote an instance of a generic unit.
AARM Ramification: The procedure can be abstract. The denoted declaration can be a renaming_declaration if it obeys the usual rules for representation pragmas: the renaming has to occur immediately within the same declarative_region as the renamed subprogram. If non-returning procedure is renamed (anywhere) calls through the new name still have the non-returning semantics.
A return_statement shall not apply to a non-returning procedure or generic procedure.
A procedure shall be non-returning if it overrides a dispatching non-returning procedure. In addition to the places where Legality Rules normally apply (see 12.3), this rule applies also in the private part of an instance of a generic unit.
AARM Reason: This ensures that dispatching calls to non-returning procedures will, in fact, not return.
If a renaming-as-body completes a non-returning procedure declaration, then the renamed procedure shall be non-returning.
Static Semantics
If a generic procedure is non-returning, then so are its instances. If a procedure declared within a generic unit is non-returning, then so are the corresponding copies of that procedure in instances.
Dynamic Semantics
If the body of a non-returning procedure completes normally, Program_Error is raised at the point of the call.
Erroneous Execution
If an imported non-returning procedure returns normally, execution is erroneous.
Note: AI-00329 also modifies 11.4.1; this AI retains those modifications as is.
AARM Discussion:
The primary goal of pragma No_Return is to document the fact that calls will not return normally. An example is a procedure that logs an error and then unconditionally raises an exception. Another example is a procedure that deliberately enters an infinite loop. Non-returning procedures are rare, so it is useful to document this unusual case. However, they are called often. This capability is useful for three reasons:
1. Programmers can better understand programs that have this unusual
control-flow structure.
2. Compilers can use this control-flow information to generate better warnings.
The pragma essentially removes certain control-flow arcs from the compiler's control-flow graph.
A compiler can warn if it is unable to prove that the end of the procedure body is unreachable. A compiler can warn about code following a call to a non-returning procedure; it's dead code. Most importantly, a compiler can avoid warning in cases like this:
if ... then
Fatal_Error("..."); -- non-returning
else
X := ...; -- initialize X
end if; ... X ... -- no warning
Without pragma No_Return, the compiler would spuriously warn that X is uninitialized when it is referenced after the if_statement. Warnings are most useful if there are few false alarms. The compiler can avoid the false alarm if "Fatal_Error" were "raise ..."; pragma No_Return allows the same capability in the case where the programmer has chosen to encapsulate the raise statement.
3. Compilers can use this control-flow information for optimizations.
For example:
Y := F(...); if ... then Fatal_Error("..."); -- non-returning else Y := 3; end if; ... Y ... -- Y = 3.
A compiler can prove that Y = 3 above, and take advantage of that fact.
In order to gain the above advantages, we must ensure that the property asserted by the pragma is actually true; that is, calls to non-returning procedures must not return. In order to make this work for dispatching calls, we need to ensure that if a procedure is non-returning, then all overridings of it are also non-returning. We do this by requiring an explicit pragma on the overriding. An alternative would be to say that the non-returning property is inherited (implicitly) by the overriding procedure. We didn't choose that alternative, because it seems less safe. The programmer of the overriding might not notice the pragma No_Return on the parent procedure, and be surprised by a run-time failure; better to require the pragma to be repeated locally. Of course, a good compiler can usually warn about the potential run-time failure, but this is not required.
This issue is like any other part of the contract for procedure calls. For example, if you call a procedure with an 'in' parameter, you know that the actual will not be modified. To make this work for dispatching calls, we need to disallow overridings from changing 'in' to 'in out'.
If a non-returning procedure tries to return, we raise Program_Error. This is stated as happening at the call site, because we do not wish to allow the procedure to handle the exception (and then, perhaps, try to return again!). However, the expected run-time model is that the compiler will generate "raise Program_Error" at the end of the procedure body (but not handleable by the procedure itself), as opposed to doing it at the call site. (This is just like the typical run-time model for functions that fall off the end without returning a value). The reason is indirect calls: in "P.all(...);", the compiler cannot know whether P designates a non-returning procedure or a normal one. Putting the "raise Program_Error" in the procedure's generated code solves this problem neatly.
Similarly, if one passes a non-returning procedure to a generic formal parameter, the compiler cannot know this at call sites; the raise-in-body solution deals with this neatly.
There is one exception to the principle that "we must ensure that the property asserted by the pragma is actually true". For a pragma-Import procedure, it is the responsibility of the foreign-language code to obey the stated contract. Therefore, we make violations in that case erroneous. Also, if we required Program_Error to be raised in this case, the compiler would have to generate a wrapper procedure or (in some cases) do the raise at the call site. We do not wish to require two run-time models (sometimes in body, sometimes at call site), and we do not wish to require wrappers.
Note: procedure "exit", imported from C, is a reasonable example of an imported procedure that deserves a pragma No_Return.
The rule about renamings-as-body is also there to avoid an implementation burden, given the above intended run-time model. AARM-8.5.4(5.a/1) notes that implementations should be allowed to implement renaming-as-body using jumps; that implementation would not work if the renaming-as-body has to raise Program_Error. Given the above rule, the renaming-as-body can rely on the renamed procedure to raise Program_Error if appropriate. Anyway, a renaming-as-body that violates the rule is almost certainly a bug.
It would make sense to allow pragma No_Return on generic formal procedures, access-to-procedure types, entries, and protected procedures. We choose not to allow these cases, in order to avoid implementation burden and additional language rules for little benefit.
If access-to-procedure types allowed pragma No_Return, we would need a contract model to ensure that the designated procedure is always non-returning; we would forbid 'Access and type conversions that allow a non-returning access-to-procedure to designate a normal (returning) procedure. On the other hand, it would be OK for a normal access-to-procedure value to designate a non-returning procedure.
Similarly, generic formal procedures would need a contract: it should be illegal to pass a normal procedure to a non-returning formal, but the other way around is OK.
Pragma No_Return is like pragma Convention in some ways. It could actually affect the calling convention at the generated code level; for example, a call could use a JUMP instruction instead of a CALL instruction in some environments, although that's hardly an important optimization. However, pragma Convention is different in one way: it doesn't cause an automatic Program_Error. It has been suggested that No_Return be automatically inherited by overridings, just like Convention. But given that No_Return raises Program_Error, we choose to require an explicit No_Return on overridings.
[End AARM Discussion.]
!example
Example of a dispatching call to a non-returning procedure:
package Error_Handling is type Error_Log is abstract tagged null record;
procedure Fatal_Error(Log: Error_Log; Message: String) is abstract; pragma No_Return(Fatal_Error); -- Send a message to the log and raise an exception.
end Error_Handling;
-- A client of Error_Handling: Log: Error_Log'Class := Log_Factory.Create(...); if ... then Fatal_Error(Log, "The sky is falling!"); -- dispatching call -- Can't get here. else X := ...; -- initialize X end if; ... X ... -- no warning; if we get here, X is well-defined.
The idea is that there are different kinds of Error_Logs, which do what they like with the Message, and then raise an exception. It is natural to represent these log types using a hierarchy of tagged types, and to use dispatching. The above dispatching call to Fatal_Error is guaranteed not to return. The various extensions of Error_Log are required to override Fatal_Error with a non-returning procedure.
Clearly, if we are going to allow dispatching calls to non-returning procedures, then they need to work right -- the call must not return.
!discussion
The primary change to AI-00329 is to require dispatching calls to work properly. The other wording changes are just minor fixes to nitpicking problems:
Miscellaneous wording changes. The introductory paragraph is changed; I think the part bracketed in the AARM clarifies the two main purposes of the pragma. (A non-returning procedure could also presumably call a never-triggered entry, or delay until the year 2099; we don't mention these oddities). We can no longer use the "apply to" definition for program unit pragmas, since No_Return is no longer a program unit pragma; we define the term "non-returning procedure" instead.
The pragma is not a program unit pragma. It is now a representation pragma. This better matches the intended semantics.
The "local_name shall denote" wording better specifies which entities can have the pragma. AI-00329 forbade abstract procedures, but there's no reason to forbid them. AI-00329 apparently intended to allow renamings (in the usual "local_name" way), but it wasn't clear; now it's clear that they're allowed.
"The local_name shall not denote an instance of a generic unit." is added, because the legality rule "A return_statement shall not apply to a non-returning procedure or generic procedure." cannot be reasonably checked on instances.
AI-00329 did not consider renamings-as-body nor pragma Import.
The rule "If a procedure declared within a generic unit is non-returning, then so are the corresponding copies of that procedure in instances." was apparently missing from AI-00329.
The rule, "A return_statement shall not apply to a non-returning procedure or generic procedure." does not strictly need the "or generic procedure" part, because inside the procedure body, it's a procedure (the current instance). Nonetheless, the rule seems clearer with the meaningless "or generic procedure" part.
!appendix

*************************************************************

Questions? Ask the ACAA Technical Agent