Rationale for Ada 2005

John Barnes
Contents   Index   References   Search   Previous   Next 

6.4 Pragmas and Restrictions

Ada 2005 introduces a number of new pragmas and Restrictions identifiers. Many of these were described in Section 5.4 when discussing tasking and the Real-Time and High Integrity annexes. For convenience here is a complete list giving the annex if appropriate.
The new pragmas are
Assert
Assertion_Policy
Detect_Blocking    High-Integrity
No_Return
Preelaborable_Initialization
Profile    Real-Time
Relative_Deadline    Real-Time
Unchecked_Union    Interface
Unsuppress
The new Restrictions identifiers are 
Max_Entry_Queue_Length    Real-Time
No_Dependence
No_Dynamic_Attachment    Real-Time
No_Implementation_Attributes
No_Implementation_Pragmas
No_Local_Protected_Objects    Real-Time
No_Obsolescent_Features
No_Protected_Type_Allocators    Real-Time
No_Relative_Delay    Real-Time
No_Requeue_Statements    Real-Time
No_Select_Statements    Real-Time
No_Synchronous_Control    Real-Time
No_Task_Termination    Real-Time
Simple_Barriers    Real-Time
We will now discuss in detail the pragmas and Restrictions identifiers in the core language and so not discussed in the previous chapter.
First there is the pragma Assert and the associated pragma Assertion_Policy. Their syntax is as follows 
pragma Assert([Check =>] boolean_expression [, [Message =>] string_expression]);
pragma Assertion_Policy(policy_identifier);
The first parameter of Assert is thus a boolean expression and the second (and optional) parameter is a string. Remember that when we write Boolean we mean of the predefined type whereas boolean includes any type derived from Boolean as well.
The parameter of Assertion_Policy is an identifier which controls the behaviour of the pragma Assert. Two policies are defined by the language, namely, Check and Ignore. Further policies may be defined by the implementation.
There is also a package Ada.Assertions thus
package Ada.Assertions is
   pragma Pure(Assertions);
   Assertion_Error: exception;
   procedure Assert(Check: in Boolean);
   procedure Assert(Check: in Boolean; Message: in String);
end Ada.Assertions;
The pragma Assert can be used wherever a declaration or statement is allowed. Thus it might occur in a list of declarations such as 
N: constant Integer := ... ;
pragma Assert(N > 1);
A: Real_Matrix(1 .. N, 1 .. N);
EV: Real_Vector(1 .. N);
and in a sequence of statements such as 
pragma Assert(Transpose(A) = A, "A not symmetric");
EV := Eigenvalues(A);
If the policy set by Assertion_Policy is Check then the above pragmas are equivalent to 
if not N > 1 then
   raise Assertion_Error;
end if;
and 
if not Transpose(A) = A then
   raise Assertion_Error with "A not symmetric";
end if;
Remember from Section 6.2 that a raise statement without any explicit message is not the same as one with an explicit null message. In the former case a subsequent call of Exception_Message returns implementation defined information whereas in the latter case it returns a null string. This same behaviour thus occurs with the Assert pragma as well – providing no message is not the same as providing a null message.
If the policy set by Assertion_Policy is Ignore then the Assert pragma is ignored at execution time – but of course the syntax of the parameters is checked during compilation.
The two procedures Assert in the package Ada.Assertions have an identical effect to the corresponding Assert pragmas except that their behaviour does not depend upon the assertion policy. Thus the call 
Assert(Some_Test);
is always equivalent to 
if not Some_Test then
   raise Assertion_Error;
end if;
In other words we could define the behaviour of 
pragma Assert(Some_Test);
as equivalent to 
if policy_identifier = Check then
   Assert(Some_Test);           -- call of procedure Assert
end if;
Note again that there are two procedures Assert, one with and one without the message parameter. These correspond to raise statements with and without an explicit message.
The pragma Assertion_Policy is a configuration pragma and controls the behaviour of Assert throughout the units to which it applies. It is thus possible for different policies to be in effect in different parts of a partition.
An implementation could define other policies such as Assume which might mean that the compiler is free to do optimizations based on the assumption that the boolean expressions are true although there would be no code to check that they were true. Careless use of such a policy could lead to erroneous behaviour.
There was some concern that pragmas such as Assert might be misunderstood to imply that static analysis was being carried out. Thus in the SPARK language [9], the annotation 
--# assert N /= 0
is indeed a static assertion and the appropriate tools can be used to verify this.
However, other languages such as Eiffel have used assert in a dynamic manner as now introduced into Ada 2005 and, moreover, many implementations of Ada have already provided a pragma Assert so it is expected that there will be no confusion with its incorporation into the standard.
Another pragma with a related flavour is No_Return. This can be applied to a procedure (not to a function) and asserts that the procedure never returns in the normal sense. Control can leave the procedure only by the propagation of an exception or it might loop forever (which is common among certain real-time programs). The syntax is 
pragma No_Return(procedure_local_name {, procedure_local_name});
Thus we might have a procedure Fatal_Error which outputs some message and then propagates an exception which can be handled in the main subprogram. For example 
procedure Fatal_Error(Msg: in String) is
   pragma No_Return(Fatal_Error);
begin
   Put_Line(Msg);
   ...    -- other last wishes
   raise Death;
end Fatal_Error;
...
procedure Main is
   ...
   ...
   Put_Line("Program terminated successfully");
exception
   when Death =>
      Put_Line("Program terminated: known error");
   when others =>
      Put_Line("Program terminated: unknown error");
end Main;
There are two consequences of supplying a pragma No_Return.
The implementation checks at compile time that the procedure concerned has no explicit return statements. There is also a check at run time that it does not attempt to run into the final end – Program_Error is raised if it does as in the case of running into the end of a function.
The implementation is able to assume that calls of the procedure do not return and so various optimizations can be made.
We might then have a call of Fatal_Error as in
function Pop return Symbol is
begin
   if Top = 0 then
      Fatal_Error("Stack empty");    -- never returns
   elsif
      Top := Top – 1;
      return S(Top+1);
   end if;
end Pop;
If No_Return applies to Fatal_Error then the compiler should not compile a jump after the call of Fatal_Error and should not produce a warning that control might run into the final end of Pop.
The pragma No_Return now applies to the predefined procedure Raise_Exception. To enable this to be possible its behaviour with Null_Id has had to be changed. In Ada 95 writing 
Raise_Exception(Null_Id, "Nothing");
does nothing at all (and so does return in that case) whereas in Ada 2005 it is defined to raise Constraint_Error and so now never returns.
We could restructure the procedure Fatal_Error to use Raise_Exception thus 
procedure Fatal_Error(Msg: in String) is
   pragma No_Return(Fatal_Error);
begin
   ...    -- other last wishes
   Raise_Exception(Death'Identity, Msg);
end Fatal_Error;
Since pragma No_Return applies to Fatal_Error it is important that we also know that Raise_Exception cannot return.
The exception handler for Death in the main subprogram can now use Exception_Message to print out the message.
Remember also from Section 6.2 that we can now also write 
raise Death with Msg;
rather than call Raise_Exception.
The pragma No_Return is a representation pragma. If a subprogram has no distinct specification then the pragma No_Return is placed inside the body (as shown above). If a subprogram has a distinct specification then the pragma must follow the specification in the same compilation or declarative region. Thus one pragma No_Return could apply to several subprograms declared in the same package specification.
It is important that dispatching works correctly with procedures that do not return. A non-returning dispatching procedure can only be overridden by a non-returning procedure and so the overriding procedure must also have pragma No_Return thus 
type T is tagged ...
procedure P(X: T; ... );
pragma No_Return(P);
...
type TT is new T with ...
overriding
procedure P(X: TT; ... );
pragma No_Return(P);
The reverse is not true of course. A procedure that does return can be overridden by one that does not.
It is possible to give a pragma No_Return for an abstract procedure, but obviously not for a null procedure. A pragma No_Return can also be given for a generic procedure. It then applies to all instances.
The next new pragma is Preelaborable_Initialization. The syntax is 
pragma Preelaborable_Initialization(direct_name);
This pragma concerns the categorization of library units and is related to pragmas such as Pure and Preelaborate. It is used with a private type and promises that the full type given by the parameter will indeed have preelaborable initialization. The details of its use will be explained in the next chapter (see 7.7).
Another new pragma is Unchecked_Union. The syntax is 
pragma Unchecked_Union(first_subtype_local_name);
The parameter has to denote an unconstrained discriminated record subtype with a variant part. The purpose of the pragma is to permit interfacing to unions in C. The following example was given in the Introduction
type Number(Kind: Precision) is
   record
      case Kind is
         when Single_Precision =>
            SP_Value: Long_Float;
         when Multiple_Precision =>
            MP_Value_Length: Integer;
            MP_Value_First: access Long_Float;
      end case;
   end record;
pragma Unchecked_Union(Number);
Specifying the pragma Unchecked_Union ensures the following 
The representation of the type does not allow space for any discriminants.
There is an implicit suppression of Discriminant_Check.
There is an implicit pragma Convention(C).
The above Ada text provides a mapping of the following C union 
union {
   double spvalue;
   struct {
      int length;
      double* first;
      } mpvalue;
} number;
The general idea is that the C programmer has created a type which can be used to represent a floating point number in one of two ways according to the precision required. One way is just as a double length value (a single item) and the other way is as a number of items considered juxtaposed to create a multiple precision value. This latter is represented as a structure consisting of an integer giving the number of items followed by a pointer to the first of them. These two different forms are the two alternatives of the union.
In the Ada mapping the choice of precision is governed by the discriminant Kind which is of an enumeration type as follows 
type Precision is (Single_Precision, Multiple_Precision);
In the single precision case the component SP_Value of type Long_Float maps onto the C component spvalue of type double.
The multiple precision case is somewhat troublesome. The Ada component MP_Value_Length maps onto the C component length and the Ada component MP_Value_First of type access Long_Float maps onto the C component first of type double*.
In our Ada program we can declare a variable thus 
X: Number(Multiple_Precision);
and we then obtain a value in X by calling some C subprogram. We can then declare an array and map it onto the C sequence of double length values thus 
A: array (1 .. X.MP_Value_Length) of Long_Float;
for A'Address use X.MP_Value_First.all'Address;
pragma Import(C, A);
The elements of A are now the required values. Note that we don't use an Ada array in the declaration of Number because there might be problems with dope information.
The Ada type can also have a non-variant part preceding the variant part and variant parts can be nested. It may have several discriminants.
When an object of an unchecked union type is created, values must be supplied for all its discriminants even though they are not stored. This ensures that appropriate default values can be supplied and that an aggregate contains the correct components. However, since the discriminants are not stored, they cannot be read. So we can write 
X: Number := (Single_Precision, 45.6);
Y: Number(Single_Precision);
...
Y.SP_Value := 55.7;
The variable Y is said to have an inferable discriminant whereas X does not. Although it is clear that playing with unchecked unions is potentially dangerous, nevertheless Ada 2005 imposes certain rules that avoid some dangers. One rule is that predefined equality can only be used on operands with inferable discriminants; Program_Error is raised otherwise. So 
if Y = 55.8 then    -- OK
if X = 45.5 then    -- raises Program_Error
if X = Y then    -- raises Program_Error
It is important to be aware that unchecked union types are introduced in Ada 2005 for the sole purpose of interfacing to C programs and not for living dangerously. Thus consider 
type T(Flag: Boolean := False) is
   record
      case Flag is
         when False =>
            F1: Float := 0.0;
         when True =>
            F2: Integer := 0;
      end case;
   end record;
pragma Unchecked_Union(T);
The type T can masquerade as either type Integer or Float. But we should not use unchecked union types as an alternative to unchecked conversion. Thus consider 
X: T;    -- Float by default
Y: Integer := X.F2;    -- erroneous
The object X has discriminant False by default and thus has the value zero of type Integer. In the absence of the pragma Unchecked_Union, the attempt to read X.F2 would raise Constraint_Error because of the discriminant check. The use of Unchecked_Union suppresses the discriminant check and so the assignment will occur. But note that the ARM clearly says (11.5(26)) that if a check is suppressed and the corresponding error situation arises then the program is erroneous.
However, assigning a Float value to an Integer object using Unchecked_Conversion is not erroneous providing certain conditions hold such as that Float'Size = Integer'Size.
The final pragma to be considered is Unsuppress. Its syntax is 
pragma Unsuppress(identifier);
The identifier is that of a check or perhaps All_Checks. The pragma Unsuppress is essentially the opposite of the existing pragma Suppress and can be used in the same places with similar scoping rules.
Remember that pragma Suppress gives an implementation the permission to omit the checks but it does not require that the checks be omitted (they might be done by hardware). The pragma Unsuppress simply revokes this permission. One pragma can override the other in a nested manner. If both are given in the same region then they apply from the point where they are given and the later one thus overrides.
A likely scenario would be that Suppress applies to a large region of the program (perhaps all of it) and Unsuppress applies to a smaller region within. The reverse would also be possible but perhaps less likely.
Note that Unsuppress does not override the implicit Suppress of Discriminant_Check provided by the pragma Unchecked_Union just discussed.
A sensible application of Unsuppress would be in the fixed point operations mentioned in Section 6.3 thus 
function "*"(Left, Right: Frac) return Frac is
   pragma Unsuppress(Overflow_Check);
begin
   return Standard."*"(Left, Right);
exception
   when Constraint_Error =>
      if (Left>0.0 and Right>0.0) or (Left<0.0 and Right<0.0) then
         return Frac'Last;
      else
         return Frac'First;
      end if;
end "*";
The use of Unsuppress ensures that the overflow check is not suppressed even if there is a global Suppress for the whole program (or the user has switched checks off through the compiler command line). So Constraint_Error will be raised as necessary and the code will work correctly.
In Ada 95 the pragma Suppress has the syntax 
pragma Suppress(identifier [ , [On =>] name]);    --Ada 95
The second and optional parameter gives the name of the entity to which the permission applies. There was never any clear agreement on what this meant and implementations varied. Accordingly, in Ada 2005 the second parameter is banished to Annex J so that the syntax in the core language is similar to Unsuppress thus 
pragma Suppress(identifier);    -- Ada 2005
For symmetry, Annex J actually allows an obsolete On parameter for Unsuppress. It might seem curious that a feature should be born obsolescent.
A number of new Restrictions identifiers are added in Ada 2005. The first is No_Dependence whose syntax is 
pragma Restrictions(No_Dependence => name);
This indicates that there is no dependence on a library unit with the given name.
The name might be that of a predefined unit but it could in fact be any unit. For example, it might be helpful to know that there is no dependence on a particular implementation-defined unit such as a package Superstring thus 
pragma Restrictions(No_Dependence => Superstring);
Care needs to be taken to spell the name correctly; if we write Supperstring by mistake then the compiler will not be able to help us.
The introduction of No_Dependence means that the existing Restrictions identifier No_Asynchronous_Control is moved to Annex J since we can now write 
pragma Restrictions(No_Dependence => Ada.Asynchronous_Task_Control);
Similarly, the identifiers No_Unchecked_Conversion and No_Unchecked_Deallocation are also moved to Annex J.
Note that the identifier No_Dynamic_Attachment which refers to the use of the subprograms in the package Ada.Interrupts cannot be treated in this way because of the child package Ada.Interrupts.Names. No dependence on Ada.Interrupts would exclude the use of the child package Names as well.
The restrictions identifier No_Dynamic_Priorities cannot be treated this way either for a rather different reason. In Ada 2005 this identifier is extended so that it also excludes the use of the attribute Priority and this would not be excluded by just saying no dependence on Ada.Dynamic_Priorities.
Two further Restrictions identifiers are introduced to encourage portability. We can write 
pragma Restrictions(No_Implementation_Pragmas, No_Implementation_Attributes);
These do not apply to the whole partition but only to the compilation or environment concerned. This helps us to ensure that implementation dependent areas of a program are identified.
The final new restrictions identifier similarly prevents us from inadvertently using features in Annex J thus 
pragma Restrictions(No_Obsolescent_Features);
Again this does not apply to the whole partition but only to the compilation or environment concerned. (It is of course not itself defined in Annex J.)
The reader will recall that in Ada 83 the predefined packages had names such as Text_IO whereas in Ada 95 they are Ada.Text_IO and so on. In order to ease transition from Ada 83, a number of renamings were declared in Annex J such as 
with Ada.Text_IO;
package Text_IO renames Ada.Text_IO;
A mild problem is that the user could write these renamings anyway and we do not want the No_Obsolescent_Features restriction to prevent this. Moreover, implementations might actually implement the renamings in Annex J by just compiling them and we don't want to force implementations to use some trickery to permit the user to do it but not the implementation. Accordingly, whether the No_Obsolescent_Features restriction applies to these renamings or not is implementation defined.

Contents   Index   References   Search   Previous   Next 
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe