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


11.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:

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: