[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C. Elaboration Order Handling in GNAT

C.1 Elaboration Code in Ada 95  
C.2 Checking the Elaboration Order in Ada 95  
C.3 Controlling the Elaboration Order in Ada 95  
C.4 Controlling Elaboration in GNAT - Internal Calls  
C.5 Controlling Elaboration in GNAT - External Calls  
C.6 Default Behavior in GNAT - Ensuring Safety  
C.7 Treatment of Pragma Elaborate  
C.8 Elaboration Issues for Library Tasks  
C.9 Mixing Elaboration Models  
C.10 What to Do If the Default Elaboration Behavior Fails  
C.11 Elaboration for Access-to-Subprogram Values  
C.12 Summary of Procedures for Elaboration Control  
C.13 Other Elaboration Order Considerations  

This chapter describes the handling of elaboration code in Ada 95 and in GNAT, and discusses how the order of elaboration of program units can be controlled in GNAT, either automatically or with explicit programming features.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.1 Elaboration Code in Ada 95

Ada 95 provides rather general mechanisms for executing code at elaboration time, that is to say before the main program starts executing. Such code arises in three contexts:

Initializers for variables.
Variables declared at the library level, in package specs or bodies, can require initialization that is performed at elaboration time, as in:
 
Sqrt_Half : Float := Sqrt (0.5);

Package initialization code
Code in a BEGIN-END section at the outer level of a package body is executed as part of the package body elaboration code.

Library level task allocators
Tasks that are declared using task allocators at the library level start executing immediately and hence can execute at elaboration time.

Subprogram calls are possible in any of these contexts, which means that any arbitrary part of the program may be executed as part of the elaboration code. It is even possible to write a program which does all its work at elaboration time, with a null main program, although stylistically this would usually be considered an inappropriate way to structure a program.

An important concern arises in the context of elaboration code: we have to be sure that it is executed in an appropriate order. What we have is a series of elaboration code sections, potentially one section for each unit in the program. It is important that these execute in the correct order. Correctness here means that, taking the above example of the declaration of Sqrt_Half, if some other piece of elaboration code references Sqrt_Half, then it must run after the section of elaboration code that contains the declaration of Sqrt_Half.

There would never be any order of elaboration problem if we made a rule that whenever you with a unit, you must elaborate both the spec and body of that unit before elaborating the unit doing the with'ing:

 
with Unit_1;
package Unit_2 is ...

would require that both the body and spec of Unit_1 be elaborated before the spec of Unit_2. However, a rule like that would be far too restrictive. In particular, it would make it impossible to have routines in separate packages that were mutually recursive.

You might think that a clever enough compiler could look at the actual elaboration code and determine an appropriate correct order of elaboration, but in the general case, this is not possible. Consider the following example.

In the body of Unit_1, we have a procedure Func_1 that references the variable Sqrt_1, which is declared in the elaboration code of the body of Unit_1:

 
Sqrt_1 : Float := Sqrt (0.1);

The elaboration code of the body of Unit_1 also contains:

 
if expression_1 = 1 then
   Q := Unit_2.Func_2;
end if;

Unit_2 is exactly parallel, it has a procedure Func_2 that references the variable Sqrt_2, which is declared in the elaboration code of the body Unit_2:

 
Sqrt_2 : Float := Sqrt (0.1);

The elaboration code of the body of Unit_2 also contains:

 
if expression_2 = 2 then
   Q := Unit_1.Func_1;
end if;

Now the question is, which of the following orders of elaboration is acceptable:

 
Spec of Unit_1
Spec of Unit_2
Body of Unit_1
Body of Unit_2

or

 
Spec of Unit_2
Spec of Unit_1
Body of Unit_2
Body of Unit_1

If you carefully analyze the flow here, you will see that you cannot tell at compile time the answer to this question. If expression_1 is not equal to 1, and expression_2 is not equal to 2, then either order is acceptable, because neither of the function calls is executed. If both tests evaluate to true, then neither order is acceptable and in fact there is no correct order.

If one of the two expressions is true, and the other is false, then one of the above orders is correct, and the other is incorrect. For example, if expression_1 = 1 and expression_2 /= 2, then the call to Func_2 will occur, but not the call to Func_1. This means that it is essential to elaborate the body of Unit_1 before the body of Unit_2, so the first order of elaboration is correct and the second is wrong.

By making expression_1 and expression_2 depend on input data, or perhaps the time of day, we can make it impossible for the compiler or binder to figure out which of these expressions will be true, and hence it is impossible to guarantee a safe order of elaboration at run time.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.2 Checking the Elaboration Order in Ada 95

In some languages that involve the same kind of elaboration problems, e.g. Java and C++, the programmer is expected to worry about these ordering problems himself, and it is common to write a program in which an incorrect elaboration order gives surprising results, because it references variables before they are initialized. Ada 95 is designed to be a safe language, and a programmer-beware approach is clearly not sufficient. Consequently, the language provides three lines of defense:

Standard rules
Some standard rules restrict the possible choice of elaboration order. In particular, if you with a unit, then its spec is always elaborated before the unit doing the with. Similarly, a parent spec is always elaborated before the child spec, and finally a spec is always elaborated before its corresponding body.

Dynamic elaboration checks
Dynamic checks are made at run time, so that if some entity is accessed before it is elaborated (typically by means of a subprogram call) then the exception (Program_Error) is raised.

Elaboration control
Facilities are provided for the programmer to specify the desired order of elaboration.

Let's look at these facilities in more detail. First, the rules for dynamic checking. One possible rule would be simply to say that the exception is raised if you access a variable which has not yet been elaborated. The trouble with this approach is that it could require expensive checks on every variable reference. Instead Ada 95 has two rules which are a little more restrictive, but easier to check, and easier to state:

Restrictions on calls
A subprogram can only be called at elaboration time if its body has been elaborated. The rules for elaboration given above guarantee that the spec of the subprogram has been elaborated before the call, but not the body. If this rule is violated, then the exception Program_Error is raised.

Restrictions on instantiations
A generic unit can only be instantiated if the body of the generic unit has been elaborated. Again, the rules for elaboration given above guarantee that the spec of the generic unit has been elaborated before the instantiation, but not the body. If this rule is violated, then the exception Program_Error is raised.

The idea is that if the body has been elaborated, then any variables it references must have been elaborated; by checking for the body being elaborated we guarantee that none of its references causes any trouble. As we noted above, this is a little too restrictive, because a subprogram that has no non-local references in its body may in fact be safe to call. However, it really would be unsafe to rely on this, because it would mean that the caller was aware of details of the implementation in the body. This goes against the basic tenets of Ada.

A plausible implementation can be described as follows. A Boolean variable is associated with each subprogram and each generic unit. This variable is initialized to False, and is set to True at the point body is elaborated. Every call or instantiation checks the variable, and raises Program_Error if the variable is False.

Note that one might think that it would be good enough to have one Boolean variable for each package, but that would not deal with cases of trying to call a body in the same package as the call that has not been elaborated yet. Of course a compiler may be able to do enough analysis to optimize away some of the Boolean variables as unnecessary, and GNAT indeed does such optimizations, but still the easiest conceptual model is to think of there being one variable per subprogram.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.3 Controlling the Elaboration Order in Ada 95

In the previous section we discussed the rules in Ada 95 which ensure that Program_Error is raised if an incorrect elaboration order is chosen. This prevents erroneous executions, but we need mechanisms to specify a correct execution and avoid the exception altogether. To achieve this, Ada 95 provides a number of features for controlling the order of elaboration. We discuss these features in this section.

First, there are several ways of indicating to the compiler that a given unit has no elaboration problems:

packages that do not require a body
In Ada 95, a library package that does not require a body does not permit a body. This means that if we have a such a package, as in:

 
package Definitions is
   generic
      type m is new integer;
   package Subp is
      type a is array (1 .. 10) of m;
      type b is array (1 .. 20) of m;
   end Subp;
end Definitions;

A package that with's Definitions may safely instantiate Definitions.Subp because the compiler can determine that there definitely is no package body to worry about in this case

pragma Pure
Places sufficient restrictions on a unit to guarantee that no call to any subprogram in the unit can result in an elaboration problem. This means that the compiler does not need to worry about the point of elaboration of such units, and in particular, does not need to check any calls to any subprograms in this unit.

pragma Preelaborate
This pragma places slightly less stringent restrictions on a unit than does pragma Pure, but these restrictions are still sufficient to ensure that there are no elaboration problems with any calls to the unit.

pragma Elaborate_Body
This pragma requires that the body of a unit be elaborated immediately after its spec. Suppose a unit A has such a pragma, and unit B does a with of unit A. Recall that the standard rules require the spec of unit A to be elaborated before the with'ing unit; given the pragma in A, we also know that the body of A will be elaborated before B, so that calls to A are safe and do not need a check.

Note that, unlike pragma Pure and pragma Preelaborate, the use of Elaborate_Body does not guarantee that the program is free of elaboration problems, because it may not be possible to satisfy the requested elaboration order. Let's go back to the example with Unit_1 and Unit_2. If a programmer marks Unit_1 as Elaborate_Body, and not Unit_2, then the order of elaboration will be:

 
Spec of Unit_2
Spec of Unit_1
Body of Unit_1
Body of Unit_2

Now that means that the call to Func_1 in Unit_2 need not be checked, it must be safe. But the call to Func_2 in Unit_1 may still fail if Expression_1 is equal to 1, and the programmer must still take responsibility for this not being the case.

If all units carry a pragma Elaborate_Body, then all problems are eliminated, except for calls entirely within a body, which are in any case fully under programmer control. However, using the pragma everywhere is not always possible. In particular, for our Unit_1/Unit_2 example, if we marked both of them as having pragma Elaborate_Body, then clearly there would be no possible elaboration order.

The above pragmas allow a server to guarantee safe use by clients, and clearly this is the preferable approach. Consequently a good rule in Ada 95 is to mark units as Pure or Preelaborate if possible, and if this is not possible, mark them as Elaborate_Body if possible. As we have seen, there are situations where neither of these three pragmas can be used. So we also provide methods for clients to control the order of elaboration of the servers on which they depend:

pragma Elaborate (unit)
This pragma is placed in the context clause, after a with clause, and it requires that the body of the named unit be elaborated before the unit in which the pragma occurs. The idea is to use this pragma if the current unit calls at elaboration time, directly or indirectly, some subprogram in the named unit.

pragma Elaborate_All (unit)
This is a stronger version of the Elaborate pragma. Consider the following example:

 
Unit A with's unit B and calls B.Func in elab code
Unit B with's unit C, and B.Func calls C.Func

Now if we put a pragma Elaborate (B) in unit A, this ensures that the body of B is elaborated before the call, but not the body of C, so the call to C.Func could still cause Program_Error to be raised.

The effect of a pragma Elaborate_All is stronger, it requires not only that the body of the named unit be elaborated before the unit doing the with, but also the bodies of all units that the named unit uses, following with links transitively. For example, if we put a pragma Elaborate_All (B) in unit A, then it requires not only that the body of B be elaborated before A, but also the body of C, because B with's C.

We are now in a position to give a usage rule in Ada 95 for avoiding elaboration problems, at least if dynamic dispatching and access to subprogram values are not used. We will handle these cases separately later.

The rule is simple. If a unit has elaboration code that can directly or indirectly make a call to a subprogram in a with'ed unit, or instantiate a generic unit in a with'ed unit, then if the with'ed unit does not have pragma Pure or Preelaborate, then the client should have a pragma Elaborate_All for the with'ed unit. By following this rule a client is assured that calls can be made without risk of an exception. If this rule is not followed, then a program may be in one of four states:

No order exists
No order of elaboration exists which follows the rules, taking into account any Elaborate, Elaborate_All, or Elaborate_Body pragmas. In this case, an Ada 95 compiler must diagnose the situation at bind time, and refuse to build an executable program.

One or more orders exist, all incorrect
One or more acceptable elaboration orders exists, and all of them generate an elaboration order problem. In this case, the binder can build an executable program, but Program_Error will be raised when the program is run.

Several orders exist, some right, some incorrect
One or more acceptable elaboration orders exists, and some of them work, and some do not. The programmer has not controlled the order of elaboration, so the binder may or may not pick one of the correct orders, and the program may or may not raise an exception when it is run. This is the worst case, because it means that the program may fail when moved to another compiler, or even another version of the same compiler.

One or more orders exists, all correct
One ore more acceptable elaboration orders exist, and all of them work. In this case the program runs successfully. This state of affairs can be guaranteed by following the rule we gave above, but may be true even if the rule is not followed.

Note that one additional advantage of following our Elaborate_All rule is that the program continues to stay in the ideal (all orders OK) state even if maintenance changes some bodies of some subprograms. Conversely, if a program that does not follow this rule happens to be safe at some point, this state of affairs may deteriorate silently as a result of maintenance changes.

You may have noticed that the above discussion did not mention the use of Elaborate_Body. This was a deliberate omission. If you with an Elaborate_Body unit, it still may be the case that code in the body makes calls to some other unit, so it is still necessary to use Elaborate_All on such units.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.4 Controlling Elaboration in GNAT - Internal Calls

In the case of internal calls, i.e. calls within a single package, the programmer has full control over the order of elaboration, and it is up to the programmer to elaborate declarations in an appropriate order. For example writing:

 
function One return Float;

Q : Float := One;

function One return Float is
begin
     return 1.0;
end One;

will obviously raise Program_Error at run time, because function One will be called before its body is elaborated. In this case GNAT will generate a warning that the call will raise Program_Error:

 
 1. procedure y is
 2.    function One return Float;
 3.
 4.    Q : Float := One;
                    |
    >>> warning: cannot call "One" before body is elaborated
    >>> warning: Program_Error will be raised at run time

 5.
 6.    function One return Float is
 7.    begin
 8.         return 1.0;
 9.    end One;
10.
11. begin
12.    null;
13. end;

Note that in this particular case, it is likely that the call is safe, because the function One does not access any global variables. Nevertheless in Ada 95, we do not want the validity of the check to depend on the contents of the body (think about the separate compilation case), so this is still wrong, as we discussed in the previous sections.

The error is easily corrected by rearranging the declarations so that the body of One appears before the declaration containing the call (note that in Ada 95, declarations can appear in any order, so there is no restriction that would prevent this reordering, and if we write:

 
function One return Float;

function One return Float is
begin
     return 1.0;
end One;

Q : Float := One;

then all is well, no warning is generated, and no Program_Error exception will be raised. Things are more complicated when a chain of subprograms is executed:

 
function A return Integer;
function B return Integer;
function C return Integer;

function B return Integer is begin return A; end;
function C return Integer is begin return B; end;

X : Integer := C;

function A return Integer is begin return 1; end;

Now the call to C at elaboration time in the declaration of X is correct, because the body of C is already elaborated, and the call to B within the body of C is correct, but the call to A within the body of B is incorrect, because the body of A has not been elaborated, so Program_Error will be raised on the call to A. In this case GNAT will generate a warning that Program_Error may be raised at the point of the call. Let's look at the warning:

 
 1. procedure x is
 2.    function A return Integer;
 3.    function B return Integer;
 4.    function C return Integer;
 5.
 6.    function B return Integer is begin return A; end;
                                                    |
    >>> warning: call to "A" before body is elaborated may
                 raise Program_Error
    >>> warning: "B" called at line 7
    >>> warning: "C" called at line 9

 7.    function C return Integer is begin return B; end;
 8.
 9.    X : Integer := C;
10.
11.    function A return Integer is begin return 1; end;
12.
13. begin
14.    null;
15. end;

Note that the message here says "may raise", instead of the direct case, where the message says "will be raised". That's because whether A is actually called depends in general on run-time flow of control. For example, if the body of B said

 
function B return Integer is
begin
   if some-condition-depending-on-input-data then
      return A;
   else
      return 1;
   end if;
end B;

then we could not know until run time whether the incorrect call to A would actually occur, so Program_Error might or might not be raised. It is possible for a compiler to do a better job of analyzing bodies, to determine whether or not Program_Error might be raised, but it certainly couldn't do a perfect job (that would require solving the halting problem and is provably impossible), and because this is a warning anyway, it does not seem worth the effort to do the analysis. Cases in which it would be relevant are rare.

In practice, warnings of either of the forms given above will usually correspond to real errors, and should be examined carefully and eliminated. In the rare case where a warning is bogus, it can be suppressed by any of the following methods:

For the internal elaboration check case, GNAT by default generates the necessary run-time checks to ensure that Program_Error is raised if any call fails an elaboration check. Of course this can only happen if a warning has been issued as described above. The use of pragma Suppress (Elaboration_Check) may (but is not guaranteed to) suppress some of these checks, meaning that it may be possible (but is not guaranteed) for a program to be able to call a subprogram whose body is not yet elaborated, without raising a Program_Error exception.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.5 Controlling Elaboration in GNAT - External Calls

The previous section discussed the case in which the execution of a particular thread of elaboration code occurred entirely within a single unit. This is the easy case to handle, because a programmer has direct and total control over the order of elaboration, and furthermore, checks need only be generated in cases which are rare and which the compiler can easily detect. The situation is more complex when separate compilation is taken into account. Consider the following:

 
package Math is
   function Sqrt (Arg : Float) return Float;
end Math;

package body Math is
   function Sqrt (Arg : Float) return Float is
   begin
         ...
   end Sqrt;
end Math;
with Math;
package Stuff is
   X : Float := Math.Sqrt (0.5);
end Stuff;

with Stuff;
procedure Main is
begin
   ...
end Main;

where Main is the main program. When this program is executed, the elaboration code must first be executed, and one of the jobs of the binder is to determine the order in which the units of a program are to be elaborated. In this case we have four units: the spec and body of Math, the spec of Stuff and the body of Main). In what order should the four separate sections of elaboration code be executed?

There are some restrictions in the order of elaboration that the binder can choose. In particular, if unit U has a with for a package X, then you are assured that the spec of X is elaborated before U , but you are not assured that the body of X is elaborated before U. This means that in the above case, the binder is allowed to choose the order:

 
spec of Math
spec of Stuff
body of Math
body of Main

but that's not good, because now the call to Math.Sqrt that happens during the elaboration of the Stuff spec happens before the body of Math.Sqrt is elaborated, and hence causes Program_Error exception to be raised. At first glance, one might say that the binder is misbehaving, because obviously you want to elaborate the body of something you with first, but that is not a general rule that can be followed in all cases. Consider

 
package X is ...

package Y is ...

with X;
package body Y is ...

with Y;
package body X is ...

This is a common arrangement, and, apart from the order of elaboration problems that might arise in connection with elaboration code, this works fine. A rule that says that you must first elaborate the body of anything you with cannot work in this case: the body of X with's Y, which means you would have to elaborate the body of Y first, but that with's X, which means you have to elaborate the body of X first, but ... and we have a loop that cannot be broken.

It is true that the binder can in many cases guess an order of elaboration that is unlikely to cause a Program_Error exception to be raised, and it tries to do so (in the above example of Math/Stuff/Spec, the GNAT binder will by default elaborate the body of Math right after its spec, so all will be well).

However, a program that blindly relies on the binder to be helpful can get into trouble, as we discussed in the previous sections, so GNAT provides a number of facilities for assisting the programmer in developing programs that are robust with respect to elaboration order.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.6 Default Behavior in GNAT - Ensuring Safety

The default behavior in GNAT ensures elaboration safety. In its default mode GNAT implements the rule we previously described as the right approach. Let's restate it:

By following this rule a client is assured that calls and instantiations can be made without risk of an exception.

In this mode GNAT traces all calls that are potentially made from elaboration code, and puts in any missing implicit Elaborate_All pragmas. The advantage of this approach is that no elaboration problems are possible if the binder can find an elaboration order that is consistent with these implicit Elaborate_All pragmas. The disadvantage of this approach is that no such order may exist.

If the binder does not generate any diagnostics, then it means that it has found an elaboration order that is guaranteed to be safe. However, the binder may still be relying on implicitly generated Elaborate_All pragmas so portability to other compilers than GNAT is not guaranteed.

If it is important to guarantee portability, then the compilations should use the `-gnatwl' (warn on elaboration problems) switch. This will cause warning messages to be generated indicating the missing Elaborate_All pragmas. Consider the following source program:

 
with k;
package j is
  m : integer := k.r;
end;

where it is clear that there should be a pragma Elaborate_All for unit k. An implicit pragma will be generated, and it is likely that the binder will be able to honor it. However, if you want to port this program to some other Ada compiler than GNAT. it is safer to include the pragma explicitly in the source. If this unit is compiled with the `-gnatwl' switch, then the compiler outputs a warning:

 
1. with k;
2. package j is
3.   m : integer := k.r;
                     |
   >>> warning: call to "r" may raise Program_Error
   >>> warning: missing pragma Elaborate_All for "k"

4. end;

and these warnings can be used as a guide for supplying manually the missing pragmas. It is usually a bad idea to use this warning option during development. That's because it will warn you when you need to put in a pragma, but cannot warn you when it is time to take it out. So the use of pragma Elaborate_All may lead to unnecessary dependencies and even false circularities.

This default mode is more restrictive than the Ada Reference Manual, and it is possible to construct programs which will compile using the dynamic model described there, but will run into a circularity using the safer static model we have described.

Of course any Ada compiler must be able to operate in a mode consistent with the requirements of the Ada Reference Manual, and in particular must have the capability of implementing the standard dynamic model of elaboration with run-time checks.

In GNAT, this standard mode can be achieved either by the use of the `-gnatE' switch on the compiler (gcc or gnatmake) command, or by the use of the configuration pragma:

 
pragma Elaboration_Checks (RM);

Either approach will cause the unit affected to be compiled using the standard dynamic run-time elaboration checks described in the Ada Reference Manual. The static model is generally preferable, since it is clearly safer to rely on compile and link time checks rather than run-time checks. However, in the case of legacy code, it may be difficult to meet the requirements of the static model. This issue is further discussed in C.10 What to Do If the Default Elaboration Behavior Fails.

Note that the static model provides a strict subset of the allowed behavior and programs of the Ada Reference Manual, so if you do adhere to the static model and no circularities exist, then you are assured that your program will work using the dynamic model, providing that you remove any pragma Elaborate statements from the source.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.7 Treatment of Pragma Elaborate

The use of pragma Elaborate should generally be avoided in Ada 95 programs. The reason for this is that there is no guarantee that transitive calls will be properly handled. Indeed at one point, this pragma was placed in Annex J (Obsolescent Features), on the grounds that it is never useful.

Now that's a bit restrictive. In practice, the case in which pragma Elaborate is useful is when the caller knows that there are no transitive calls, or that the called unit contains all necessary transitive pragma Elaborate statements, and legacy code often contains such uses.

Strictly speaking the static mode in GNAT should ignore such pragmas, since there is no assurance at compile time that the necessary safety conditions are met. In practice, this would cause GNAT to be incompatible with correctly written Ada 83 code that had all necessary pragma Elaborate statements in place. Consequently, we made the decision that GNAT in its default mode will believe that if it encounters a pragma Elaborate then the programmer knows what they are doing, and it will trust that no elaboration errors can occur.

The result of this decision is two-fold. First to be safe using the static mode, you should remove all pragma Elaborate statements. Second, when fixing circularities in existing code, you can selectively use pragma Elaborate statements to convince the static mode of GNAT that it need not generate an implicit pragma Elaborate_All statement.

When using the static mode with `-gnatwl', any use of pragma Elaborate will generate a warning about possible problems.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.8 Elaboration Issues for Library Tasks

In this section we examine special elaboration issues that arise for programs that declare library level tasks.

Generally the model of execution of an Ada program is that all units are elaborated, and then execution of the program starts. However, the declaration of library tasks definitely does not fit this model. The reason for this is that library tasks start as soon as they are declared (more precisely, as soon as the statement part of the enclosing package body is reached), that is to say before elaboration of the program is complete. This means that if such a task calls a subprogram, or an entry in another task, the callee may or may not be elaborated yet, and in the standard Reference Manual model of dynamic elaboration checks, you can even get timing dependent Program_Error exceptions, since there can be a race between the elaboration code and the task code.

The static model of elaboration in GNAT seeks to avoid all such dynamic behavior, by being conservative, and the conservative approach in this particular case is to assume that all the code in a task body is potentially executed at elaboration time if a task is declared at the library level.

This can definitely result in unexpected circularities. Consider the following example

 
package Decls is
  task Lib_Task is
     entry Start;
  end Lib_Task;

  type My_Int is new Integer;

  function Ident (M : My_Int) return My_Int;
end Decls;

with Utils;
package body Decls is
  task body Lib_Task is
  begin
     accept Start;
     Utils.Put_Val (2);
  end Lib_Task;

  function Ident (M : My_Int) return My_Int is
  begin
     return M;
  end Ident;
end Decls;

with Decls;
package Utils is
  procedure Put_Val (Arg : Decls.My_Int);
end Utils;

with Text_IO;
package body Utils is
  procedure Put_Val (Arg : Decls.My_Int) is
  begin
     Text_IO.Put_Line (Decls.My_Int'Image (Decls.Ident (Arg)));
  end Put_Val;
end Utils;

with Decls;
procedure Main is
begin
   Decls.Lib_Task.Start;
end;

If the above example is compiled in the default static elaboration mode, then a circularity occurs. The circularity comes from the call Utils.Put_Val in the task body of Decls.Lib_Task. Since this call occurs in elaboration code, we need an implicit pragma Elaborate_All for Utils. This means that not only must the spec and body of Utils be elaborated before the body of Decls, but also the spec and body of any unit that is with'ed by the body of Utils must also be elaborated before the body of Decls. This is the transitive implication of pragma Elaborate_All and it makes sense, because in general the body of Put_Val might have a call to something in a with'ed unit.

In this case, the body of Utils (actually its spec) with's Decls. Unfortunately this means that the body of Decls must be elaborated before itself, in case there is a call from the body of Utils.

Here is the exact chain of events we are worrying about:

  1. In the body of Decls a call is made from within the body of a library task to a subprogram in the package Utils. Since this call may occur at elaboration time (given that the task is activated at elaboration time), we have to assume the worst, i.e. that the call does happen at elaboration time.

  2. This means that the body and spec of Util must be elaborated before the body of Decls so that this call does not cause an access before elaboration.

  3. Within the body of Util, specifically within the body of Util.Put_Val there may be calls to any unit with'ed by this package.

  4. One such with'ed package is package Decls, so there might be a call to a subprogram in Decls in Put_Val. In fact there is such a call in this example, but we would have to assume that there was such a call even if it were not there, since we are not supposed to write the body of Decls knowing what is in the body of Utils; certainly in the case of the static elaboration model, the compiler does not know what is in other bodies and must assume the worst.

  5. This means that the spec and body of Decls must also be elaborated before we elaborate the unit containing the call, but that unit is Decls! This means that the body of Decls must be elaborated before itself, and that's a circularity.

Indeed, if you add an explicit pragma Elaborate_All for Utils in the body of Decls you will get a true Ada Reference Manual circularity that makes the program illegal.

In practice, we have found that problems with the static model of elaboration in existing code often arise from library tasks, so we must address this particular situation.

Note that if we compile and run the program above, using the dynamic model of elaboration (that is to say use the `-gnatE' switch), then it compiles, binds, links, and runs, printing the expected result of 2. Therefore in some sense the circularity here is only apparent, and we need to capture the properties of this program that distinguish it from other library-level tasks that have real elaboration problems.

We have four possible answers to this question:


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.9 Mixing Elaboration Models

So far, we have assumed that the entire program is either compiled using the dynamic model or static model, ensuring consistency. It is possible to mix the two models, but rules have to be followed if this mixing is done to ensure that elaboration checks are not omitted.

The basic rule is that a unit compiled with the static model cannot be with'ed by a unit compiled with the dynamic model. The reason for this is that in the static model, a unit assumes that its clients guarantee to use (the equivalent of) pragma Elaborate_All so that no elaboration checks are required in inner subprograms, and this assumption is violated if the client is compiled with dynamic checks.

The precise rule is as follows. A unit that is compiled with dynamic checks can only with a unit that meets at least one of the following criteria:

If this rule is violated, that is if a unit with dynamic elaboration checks with's a unit that does not meet one of the above four criteria, then the binder (gnatbind) will issue a warning similar to that in the following example:

 
warning: "x.ads" has dynamic elaboration checks and with's
warning:   "y.ads" which has static elaboration checks

These warnings indicate that the rule has been violated, and that as a result elaboration checks may be missed in the resulting executable file. This warning may be suppressed using the `-ws' binder switch in the usual manner.

One useful application of this mixing rule is in the case of a subsystem which does not itself with units from the remainder of the application. In this case, the entire subsystem can be compiled with dynamic checks to resolve a circularity in the subsystem, while allowing the main application that uses this subsystem to be compiled using the more reliable default static model.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.10 What to Do If the Default Elaboration Behavior Fails

If the binder cannot find an acceptable order, it outputs detailed diagnostics. For example:
 
error: elaboration circularity detected
info:   "proc (body)" must be elaborated before "pack (body)"
info:     reason: Elaborate_All probably needed in unit "pack (body)"
info:     recompile "pack (body)" with -gnatwl
info:                             for full details
info:       "proc (body)"
info:         is needed by its spec:
info:       "proc (spec)"
info:         which is withed by:
info:       "pack (body)"
info:  "pack (body)" must be elaborated before "proc (body)"
info:     reason: pragma Elaborate in unit "proc (body)"

In this case we have a cycle that the binder cannot break. On the one hand, there is an explicit pragma Elaborate in proc for pack. This means that the body of pack must be elaborated before the body of proc. On the other hand, there is elaboration code in pack that calls a subprogram in proc. This means that for maximum safety, there should really be a pragma Elaborate_All in pack for proc which would require that the body of proc be elaborated before the body of pack. Clearly both requirements cannot be satisfied. Faced with a circularity of this kind, you have three different options.

Fix the program
The most desirable option from the point of view of long-term maintenance is to rearrange the program so that the elaboration problems are avoided. One useful technique is to place the elaboration code into separate child packages. Another is to move some of the initialization code to explicitly called subprograms, where the program controls the order of initialization explicitly. Although this is the most desirable option, it may be impractical and involve too much modification, especially in the case of complex legacy code.

Perform dynamic checks
If the compilations are done using the `-gnatE' (dynamic elaboration check) switch, then GNAT behaves in a quite different manner. Dynamic checks are generated for all calls that could possibly result in raising an exception. With this switch, the compiler does not generate implicit Elaborate_All pragmas. The behavior then is exactly as specified in the Ada 95 Reference Manual. The binder will generate an executable program that may or may not raise Program_Error, and then it is the programmer's job to ensure that it does not raise an exception. Note that it is important to compile all units with the switch, it cannot be used selectively.

Suppress checks
The drawback of dynamic checks is that they generate a significant overhead at run time, both in space and time. If you are absolutely sure that your program cannot raise any elaboration exceptions, and you still want to use the dynamic elaboration model, then you can use the configuration pragma Suppress (Elaboration_Check) to suppress all such checks. For example this pragma could be placed in the `gnat.adc' file.

Suppress checks selectively
When you know that certain calls in elaboration code cannot possibly lead to an elaboration error, and the binder nevertheless generates warnings on those calls and inserts Elaborate_All pragmas that lead to elaboration circularities, it is possible to remove those warnings locally and obtain a program that will bind. Clearly this can be unsafe, and it is the responsibility of the programmer to make sure that the resulting program has no elaboration anomalies. The pragma Suppress (Elaboration_Check) can be used with different granularity to suppress warnings and break elaboration circularities:

These five cases are listed in order of decreasing safety, and therefore require increasing programmer care in their application. Consider the following program:

 
package Pack1 is
  function F1 return Integer;
  X1 : Integer;
end Pack1;

package Pack2 is
  function F2 return Integer;
  function Pure (x : integer) return integer;
  --  pragma Suppress (Elaboration_Check, On => Pure);  -- (3)
  --  pragma Suppress (Elaboration_Check);              -- (4)
end Pack2;

with Pack2;
package body Pack1 is
  function F1 return Integer is
  begin
    return 100;
  end F1;
  Val : integer := Pack2.Pure (11);    --  Elab. call (1)
begin
  declare
    --  pragma Suppress(Elaboration_Check, Pack2.F2);   -- (1)
    --  pragma Suppress(Elaboration_Check);             -- (2)
  begin
    X1 := Pack2.F2 + 1;                --  Elab. call (2)
  end;
end Pack1;

with Pack1;
package body Pack2 is
  function F2 return Integer is
  begin
     return Pack1.F1;
  end F2;
  function Pure (x : integer) return integer is
  begin
     return x ** 3 - 3 * x;
  end;
end Pack2;

with Pack1, Ada.Text_IO;
procedure Proc3 is
begin
  Ada.Text_IO.Put_Line(Pack1.X1'Img); -- 101
end Proc3;
In the absence of any pragmas, an attempt to bind this program produces the following diagnostics:
 
error: elaboration circularity detected
info:    "pack1 (body)" must be elaborated before "pack1 (body)"
info:       reason: Elaborate_All probably needed in unit "pack1 (body)"
info:       recompile "pack1 (body)" with -gnatwl for full details
info:          "pack1 (body)"
info:             must be elaborated along with its spec:
info:          "pack1 (spec)"
info:             which is withed by:
info:          "pack2 (body)"
info:             which must be elaborated along with its spec:
info:          "pack2 (spec)"
info:             which is withed by:
info:          "pack1 (body)"
The sources of the circularity are the two calls to Pack2.Pure and Pack2.F2 in the body of Pack1. We can see that the call to F2 is safe, even though F2 calls F1, because the call appears after the elaboration of the body of F1. Therefore the pragma (1) is safe, and will remove the warning on the call. It is also possible to use pragma (2) because there are no other potentially unsafe calls in the block.

The call to Pure is safe because this function does not depend on the state of Pack2. Therefore any call to this function is safe, and it is correct to place pragma (3) in the corresponding package spec.

Finally, we could place pragma (4) in the spec of Pack2 to disable warnings on all calls to functions declared therein. Note that this is not necessarily safe, and requires more detailed examination of the subprogram bodies involved. In particular, a call to F2 requires that F1 be already elaborated.

It is hard to generalize on which of these four approaches should be taken. Obviously if it is possible to fix the program so that the default treatment works, this is preferable, but this may not always be practical. It is certainly simple enough to use `-gnatE' but the danger in this case is that, even if the GNAT binder finds a correct elaboration order, it may not always do so, and certainly a binder from another Ada compiler might not. A combination of testing and analysis (for which the warnings generated with the `-gnatwl' switch can be useful) must be used to ensure that the program is free of errors. One switch that is useful in this testing is the `-p (pessimistic elaboration order)' switch for gnatbind. Normally the binder tries to find an order that has the best chance of of avoiding elaboration problems. With this switch, the binder plays a devil's advocate role, and tries to choose the order that has the best chance of failing. If your program works even with this switch, then it has a better chance of being error free, but this is still not a guarantee.

For an example of this approach in action, consider the C-tests (executable tests) from the ACVC suite. If these are compiled and run with the default treatment, then all but one of them succeed without generating any error diagnostics from the binder. However, there is one test that fails, and this is not surprising, because the whole point of this test is to ensure that the compiler can handle cases where it is impossible to determine a correct order statically, and it checks that an exception is indeed raised at run time.

This one test must be compiled and run using the `-gnatE' switch, and then it passes. Alternatively, the entire suite can be run using this switch. It is never wrong to run with the dynamic elaboration switch if your code is correct, and we assume that the C-tests are indeed correct (it is less efficient, but efficiency is not a factor in running the ACVC tests.)


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.11 Elaboration for Access-to-Subprogram Values

The introduction of access-to-subprogram types in Ada 95 complicates the handling of elaboration. The trouble is that it becomes impossible to tell at compile time which procedure is being called. This means that it is not possible for the binder to analyze the elaboration requirements in this case.

If at the point at which the access value is created (i.e., the evaluation of P'Access for a subprogram P), the body of the subprogram is known to have been elaborated, then the access value is safe, and its use does not require a check. This may be achieved by appropriate arrangement of the order of declarations if the subprogram is in the current unit, or, if the subprogram is in another unit, by using pragma Pure, Preelaborate, or Elaborate_Body on the referenced unit.

If the referenced body is not known to have been elaborated at the point the access value is created, then any use of the access value must do a dynamic check, and this dynamic check will fail and raise a Program_Error exception if the body has not been elaborated yet. GNAT will generate the necessary checks, and in addition, if the `-gnatwl' switch is set, will generate warnings that such checks are required.

The use of dynamic dispatching for tagged types similarly generates a requirement for dynamic checks, and premature calls to any primitive operation of a tagged type before the body of the operation has been elaborated, will result in the raising of Program_Error.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.12 Summary of Procedures for Elaboration Control

First, compile your program with the default options, using none of the special elaboration control switches. If the binder successfully binds your program, then you can be confident that, apart from issues raised by the use of access-to-subprogram types and dynamic dispatching, the program is free of elaboration errors. If it is important that the program be portable, then use the `-gnatwl' switch to generate warnings about missing Elaborate_All pragmas, and supply the missing pragmas.

If the program fails to bind using the default static elaboration handling, then you can fix the program to eliminate the binder message, or recompile the entire program with the `-gnatE' switch to generate dynamic elaboration checks, and, if you are sure there really are no elaboration problems, use a global pragma Suppress (Elaboration_Check).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

C.13 Other Elaboration Order Considerations

This section has been entirely concerned with the issue of finding a valid elaboration order, as defined by the Ada Reference Manual. In a case where several elaboration orders are valid, the task is to find one of the possible valid elaboration orders (and the static model in GNAT will ensure that this is achieved).

The purpose of the elaboration rules in the Ada Reference Manual is to make sure that no entity is accessed before it has been elaborated. For a subprogram, this means that the spec and body must have been elaborated before the subprogram is called. For an object, this means that the object must have been elaborated before its value is read or written. A violation of either of these two requirements is an access before elaboration order, and this section has been all about avoiding such errors.

In the case where more than one order of elaboration is possible, in the sense that access before elaboration errors are avoided, then any one of the orders is "correct" in the sense that it meets the requirements of the Ada Reference Manual, and no such error occurs.

However, it may be the case for a given program, that there are constraints on the order of elaboration that come not from consideration of avoiding elaboration errors, but rather from extra-lingual logic requirements. Consider this example:

 
with Init_Constants;
package Constants is
   X : Integer := 0;
   Y : Integer := 0;
end Constants;

package Init_Constants is
   procedure P; -- require a body
end Init_Constants;

with Constants;
package body Init_Constants is
   procedure P is begin null; end;
begin
   Constants.X := 3;
   Constants.Y := 4;
end Init_Constants;

with Constants;
package Calc is
   Z : Integer := Constants.X + Constants.Y;
end Calc;

with Calc;
with Text_IO; use Text_IO;
procedure Main is
begin
   Put_Line (Calc.Z'Img);
end Main;

In this example, there is more than one valid order of elaboration. For example both the following are correct orders:

 
Init_Constants spec
Constants spec
Calc spec
Init_Constants body
Main body

  and

Init_Constants spec
Init_Constants body
Constants spec
Calc spec
Main body

There is no language rule to prefer one or the other, both are correct from an order of elaboration point of view. But the programmatic effects of the two orders are very different. In the first, the elaboration routine of Calc initializes Z to zero, and then the main program runs with this value of zero. But in the second order, the elaboration routine of Calc runs after the body of Init_Constants has set X and Y and thus Z is set to 7 before Main runs.

One could perhaps by applying pretty clever non-artificial intelligence to the situation guess that it is more likely that the second order of elaboration is the one desired, but there is no formal linguistic reason to prefer one over the other. In fact in this particular case, GNAT will prefer the second order, because of the rule that bodies are elaborated as soon as possible, but it's just luck that this is what was wanted (if indeed the second order was preferred).

If the program cares about the order of elaboration routines in a case like this, it is important to specify the order required. In this particular case, that could have been achieved by adding to the spec of Calc:

 
pragma Elaborate_All (Constants);

which requires that the body (if any) and spec of Constants, as well as the body and spec of any unit with'ed by Constants be elaborated before Calc is elaborated.

Clearly no automatic method can always guess which alternative you require, and if you are working with legacy code that had constraints of this kind which were not properly specified by adding Elaborate or Elaborate_All pragmas, then indeed it is possible that two different compilers can choose different orders.

The gnatbind `-p' switch may be useful in smoking out problems. This switch causes bodies to be elaborated as late as possible instead of as early as possible. In the example above, it would have forced the choice of the first elaboration order. If you get different results when using this switch, and particularly if one set of results is right, and one is wrong as far as you are concerned, it shows that you have some missing Elaborate pragmas. For the example above, we have the following output:

 
gnatmake -f -q main
main
 7
gnatmake -f -q main -bargs -p
main
 0

It is of course quite unlikely that both these results are correct, so it is up to you in a case like this to investigate the source of the difference, by looking at the two elaboration orders that are chosen, and figuring out which is correct, and then adding the necessary Elaborate_All pragmas to ensure the desired order.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Mail Server on June, 15 2005 using texi2html