Next: Elaboration for Access-to-Subprogram Values, 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 -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.
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 (Elaboration_Check)
to suppress all such checks. For
example this pragma could be placed in the gnat.adc file.
Suppress (Elaboration_Check)
can
be used with different granularity to suppress warnings and break
elaboration circularities:
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 -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.)