C.7. What to do if the Default Elaboration Behavior Fails

If the binder cannot find an acceptable order, it outputs quite 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 separate off 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 large complex legacy codes.

Perform dynamic checks

If the compilations are done using the -gnatE (dynamic elaboration check) switch, then XGC Ada 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 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

One difficulty with generating dynamic checks is that they generate a significant extra overhead at run time, both in space and time. If you are absolutely sure that your program cannot raise any elaboration exceptions, then you can use the -f switch for the gnatbind step, or -bargs -f if you are using gnatmake. This switch tells the binder to ignore any implicit Elaborate_All pragmas that were generated by the compiler, and suppresses any circularity messages that they cause. The resulting executable will work fine if there are no elaboration problems, but if there are some order of elaboration problems, then they will not be detected, and unexpected results may occur.

It is hard to generalize on which of these three 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 or -f but the danger in either case is that, even if the XGC Ada binder finds a correct elaboration error free order, it may not always do so, and certainly a binder from another Ada compiler may not do so. 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 -h (horrible elaboration order) switch for gnatbind. Normally the binder tries to find an order that has the best chance of succeeding in avoiding elaboration problems. With this switch, the binder plays a kind of 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 succeeds 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 passes fine. 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.