Next: , Previous: Treatment of Pragma Elaborate, Up: Elaboration Order Handling in GNAT


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: