Rationale for Ada 2005
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.
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by: