Next: Elaboration for Indirect Calls, Previous: Mixing Elaboration Models, Up: Elaboration Order Handling in GNAT
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 -gnatel 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.
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.
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 or Elaborate_All pragmas. The behavior then is exactly as specified in the Ada 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.
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.
When you know that certain calls or instantiations in elaboration code cannot possibly lead to an elaboration error, and the binder nevertheless complains about implicit Elaborate and 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:
As previously described in section Treatment of Pragma Elaborate, GNAT in static mode assumes that a pragma Elaborate indicates correctly that no elaboration checks are required on calls to the designated unit. There may be cases in which the caller knows that no transitive calls can occur, so that a pragma Elaborate will be sufficient in a case where pragma Elaborate_All would cause a circularity.
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 -gnatel 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 information messages generated with the `-gnatel' 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 avoiding elaboration problems. However, if this switch is used, 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 ACATS 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 ACATS tests.)