blob: 5788929bedca7f025d315d5690d8f8d8958ca85b [file] [log] [blame]
.. _Implementation_of_Specific_Ada_Features:
***************************************
Implementation of Specific Ada Features
***************************************
This chapter describes the GNAT implementation of several Ada language
facilities.
.. _Machine_Code_Insertions:
Machine Code Insertions
=======================
.. index:: Machine Code insertions
Package `Machine_Code` provides machine code support as described
in the Ada Reference Manual in two separate forms:
*
Machine code statements, consisting of qualified expressions that
fit the requirements of RM section 13.8.
*
An intrinsic callable procedure, providing an alternative mechanism of
including machine instructions in a subprogram.
The two features are similar, and both are closely related to the mechanism
provided by the asm instruction in the GNU C compiler. Full understanding
and use of the facilities in this package requires understanding the asm
instruction, see the section on Extended Asm in
:title:`Using_the_GNU_Compiler_Collection_(GCC)`.
Calls to the function `Asm` and the procedure `Asm` have identical
semantic restrictions and effects as described below. Both are provided so
that the procedure call can be used as a statement, and the function call
can be used to form a code_statement.
Consider this C `asm` instruction:
::
asm ("fsinx %1 %0" : "=f" (result) : "f" (angle));
The equivalent can be written for GNAT as:
.. code-block:: ada
Asm ("fsinx %1 %0",
My_Float'Asm_Output ("=f", result),
My_Float'Asm_Input ("f", angle));
The first argument to `Asm` is the assembler template, and is
identical to what is used in GNU C. This string must be a static
expression. The second argument is the output operand list. It is
either a single `Asm_Output` attribute reference, or a list of such
references enclosed in parentheses (technically an array aggregate of
such references).
The `Asm_Output` attribute denotes a function that takes two
parameters. The first is a string, the second is the name of a variable
of the type designated by the attribute prefix. The first (string)
argument is required to be a static expression and designates the
constraint (see the section on Constraints in
:title:`Using_the_GNU_Compiler_Collection_(GCC)`)
for the parameter; e.g., what kind of register is required. The second
argument is the variable to be written or updated with the
result. The possible values for constraint are the same as those used in
the RTL, and are dependent on the configuration file used to build the
GCC back end. If there are no output operands, then this argument may
either be omitted, or explicitly given as `No_Output_Operands`.
No support is provided for GNU C's symbolic names for output parameters.
The second argument of ``my_float'Asm_Output`` functions as
though it were an `out` parameter, which is a little curious, but
all names have the form of expressions, so there is no syntactic
irregularity, even though normally functions would not be permitted
`out` parameters. The third argument is the list of input
operands. It is either a single `Asm_Input` attribute reference, or
a list of such references enclosed in parentheses (technically an array
aggregate of such references).
The `Asm_Input` attribute denotes a function that takes two
parameters. The first is a string, the second is an expression of the
type designated by the prefix. The first (string) argument is required
to be a static expression, and is the constraint for the parameter,
(e.g., what kind of register is required). The second argument is the
value to be used as the input argument. The possible values for the
constraint are the same as those used in the RTL, and are dependent on
the configuration file used to built the GCC back end.
No support is provided for GNU C's symbolic names for input parameters.
If there are no input operands, this argument may either be omitted, or
explicitly given as `No_Input_Operands`. The fourth argument, not
present in the above example, is a list of register names, called the
*clobber* argument. This argument, if given, must be a static string
expression, and is a space or comma separated list of names of registers
that must be considered destroyed as a result of the `Asm` call. If
this argument is the null string (the default value), then the code
generator assumes that no additional registers are destroyed.
In addition to registers, the special clobbers `memory` and
`cc` as described in the GNU C docs are both supported.
The fifth argument, not present in the above example, called the
*volatile* argument, is by default `False`. It can be set to
the literal value `True` to indicate to the code generator that all
optimizations with respect to the instruction specified should be
suppressed, and in particular an instruction that has outputs
will still be generated, even if none of the outputs are
used. See :title:`Using_the_GNU_Compiler_Collection_(GCC)`
for the full description.
Generally it is strongly advisable to use Volatile for any ASM statement
that is missing either input or output operands or to avoid unwanted
optimizations. A warning is generated if this advice is not followed.
No support is provided for GNU C's `asm goto` feature.
The `Asm` subprograms may be used in two ways. First the procedure
forms can be used anywhere a procedure call would be valid, and
correspond to what the RM calls 'intrinsic' routines. Such calls can
be used to intersperse machine instructions with other Ada statements.
Second, the function forms, which return a dummy value of the limited
private type `Asm_Insn`, can be used in code statements, and indeed
this is the only context where such calls are allowed. Code statements
appear as aggregates of the form:
.. code-block:: ada
Asm_Insn'(Asm (...));
Asm_Insn'(Asm_Volatile (...));
In accordance with RM rules, such code statements are allowed only
within subprograms whose entire body consists of such statements. It is
not permissible to intermix such statements with other Ada statements.
Typically the form using intrinsic procedure calls is more convenient
and more flexible. The code statement form is provided to meet the RM
suggestion that such a facility should be made available. The following
is the exact syntax of the call to `Asm`. As usual, if named notation
is used, the arguments may be given in arbitrary order, following the
normal rules for use of positional and named arguments:
::
ASM_CALL ::= Asm (
[Template =>] static_string_EXPRESSION
[,[Outputs =>] OUTPUT_OPERAND_LIST ]
[,[Inputs =>] INPUT_OPERAND_LIST ]
[,[Clobber =>] static_string_EXPRESSION ]
[,[Volatile =>] static_boolean_EXPRESSION] )
OUTPUT_OPERAND_LIST ::=
[PREFIX.]No_Output_Operands
| OUTPUT_OPERAND_ATTRIBUTE
| (OUTPUT_OPERAND_ATTRIBUTE {,OUTPUT_OPERAND_ATTRIBUTE})
OUTPUT_OPERAND_ATTRIBUTE ::=
SUBTYPE_MARK'Asm_Output (static_string_EXPRESSION, NAME)
INPUT_OPERAND_LIST ::=
[PREFIX.]No_Input_Operands
| INPUT_OPERAND_ATTRIBUTE
| (INPUT_OPERAND_ATTRIBUTE {,INPUT_OPERAND_ATTRIBUTE})
INPUT_OPERAND_ATTRIBUTE ::=
SUBTYPE_MARK'Asm_Input (static_string_EXPRESSION, EXPRESSION)
The identifiers `No_Input_Operands` and `No_Output_Operands`
are declared in the package `Machine_Code` and must be referenced
according to normal visibility rules. In particular if there is no
`use` clause for this package, then appropriate package name
qualification is required.
.. _GNAT_Implementation_of_Tasking:
GNAT Implementation of Tasking
==============================
This chapter outlines the basic GNAT approach to tasking (in particular,
a multi-layered library for portability) and discusses issues related
to compliance with the Real-Time Systems Annex.
.. _Mapping_Ada_Tasks_onto_the_Underlying_Kernel_Threads:
Mapping Ada Tasks onto the Underlying Kernel Threads
----------------------------------------------------
GNAT's run-time support comprises two layers:
* GNARL (GNAT Run-time Layer)
* GNULL (GNAT Low-level Library)
In GNAT, Ada's tasking services rely on a platform and OS independent
layer known as GNARL. This code is responsible for implementing the
correct semantics of Ada's task creation, rendezvous, protected
operations etc.
GNARL decomposes Ada's tasking semantics into simpler lower level
operations such as create a thread, set the priority of a thread,
yield, create a lock, lock/unlock, etc. The spec for these low-level
operations constitutes GNULLI, the GNULL Interface. This interface is
directly inspired from the POSIX real-time API.
If the underlying executive or OS implements the POSIX standard
faithfully, the GNULL Interface maps as is to the services offered by
the underlying kernel. Otherwise, some target dependent glue code maps
the services offered by the underlying kernel to the semantics expected
by GNARL.
Whatever the underlying OS (VxWorks, UNIX, Windows, etc.) the
key point is that each Ada task is mapped on a thread in the underlying
kernel. For example, in the case of VxWorks, one Ada task = one VxWorks task.
In addition Ada task priorities map onto the underlying thread priorities.
Mapping Ada tasks onto the underlying kernel threads has several advantages:
*
The underlying scheduler is used to schedule the Ada tasks. This
makes Ada tasks as efficient as kernel threads from a scheduling
standpoint.
*
Interaction with code written in C containing threads is eased
since at the lowest level Ada tasks and C threads map onto the same
underlying kernel concept.
*
When an Ada task is blocked during I/O the remaining Ada tasks are
able to proceed.
*
On multiprocessor systems Ada tasks can execute in parallel.
Some threads libraries offer a mechanism to fork a new process, with the
child process duplicating the threads from the parent.
GNAT does not
support this functionality when the parent contains more than one task.
.. index:: Forking a new process
.. _Ensuring_Compliance_with_the_Real-Time_Annex:
Ensuring Compliance with the Real-Time Annex
--------------------------------------------
.. index:: Real-Time Systems Annex compliance
Although mapping Ada tasks onto
the underlying threads has significant advantages, it does create some
complications when it comes to respecting the scheduling semantics
specified in the real-time annex (Annex D).
For instance the Annex D requirement for the `FIFO_Within_Priorities`
scheduling policy states:
*When the active priority of a ready task that is not running
changes, or the setting of its base priority takes effect, the
task is removed from the ready queue for its old active priority
and is added at the tail of the ready queue for its new active
priority, except in the case where the active priority is lowered
due to the loss of inherited priority, in which case the task is
added at the head of the ready queue for its new active priority.*
While most kernels do put tasks at the end of the priority queue when
a task changes its priority, (which respects the main
FIFO_Within_Priorities requirement), almost none keep a thread at the
beginning of its priority queue when its priority drops from the loss
of inherited priority.
As a result most vendors have provided incomplete Annex D implementations.
The GNAT run-time, has a nice cooperative solution to this problem
which ensures that accurate FIFO_Within_Priorities semantics are
respected.
The principle is as follows. When an Ada task T is about to start
running, it checks whether some other Ada task R with the same
priority as T has been suspended due to the loss of priority
inheritance. If this is the case, T yields and is placed at the end of
its priority queue. When R arrives at the front of the queue it
executes.
Note that this simple scheme preserves the relative order of the tasks
that were ready to execute in the priority queue where R has been
placed at the end.
.. _GNAT_Implementation_of_Shared_Passive_Packages:
GNAT Implementation of Shared Passive Packages
==============================================
.. index:: Shared passive packages
GNAT fully implements the pragma `Shared_Passive` for
.. index:: pragma `Shared_Passive`
the purpose of designating shared passive packages.
This allows the use of passive partitions in the
context described in the Ada Reference Manual; i.e., for communication
between separate partitions of a distributed application using the
features in Annex E.
.. index:: Annex E
.. index:: Distribution Systems Annex
However, the implementation approach used by GNAT provides for more
extensive usage as follows:
*Communication between separate programs*
This allows separate programs to access the data in passive
partitions, using protected objects for synchronization where
needed. The only requirement is that the two programs have a
common shared file system. It is even possible for programs
running on different machines with different architectures
(e.g., different endianness) to communicate via the data in
a passive partition.
*Persistence between program runs*
The data in a passive package can persist from one run of a
program to another, so that a later program sees the final
values stored by a previous run of the same program.
The implementation approach used is to store the data in files. A
separate stream file is created for each object in the package, and
an access to an object causes the corresponding file to be read or
written.
.. index:: SHARED_MEMORY_DIRECTORY environment variable
The environment variable `SHARED_MEMORY_DIRECTORY` should be
set to the directory to be used for these files.
The files in this directory
have names that correspond to their fully qualified names. For
example, if we have the package
.. code-block:: ada
package X is
pragma Shared_Passive (X);
Y : Integer;
Z : Float;
end X;
and the environment variable is set to `/stemp/`, then the files created
will have the names:
::
/stemp/x.y
/stemp/x.z
These files are created when a value is initially written to the object, and
the files are retained until manually deleted. This provides the persistence
semantics. If no file exists, it means that no partition has assigned a value
to the variable; in this case the initial value declared in the package
will be used. This model ensures that there are no issues in synchronizing
the elaboration process, since elaboration of passive packages elaborates the
initial values, but does not create the files.
The files are written using normal `Stream_IO` access.
If you want to be able
to communicate between programs or partitions running on different
architectures, then you should use the XDR versions of the stream attribute
routines, since these are architecture independent.
If active synchronization is required for access to the variables in the
shared passive package, then as described in the Ada Reference Manual, the
package may contain protected objects used for this purpose. In this case
a lock file (whose name is :file:`___lock` (three underscores)
is created in the shared memory directory.
.. index:: ___lock file (for shared passive packages)
This is used to provide the required locking
semantics for proper protected object synchronization.
GNAT supports shared passive packages on all platforms
except for OpenVMS.
.. _Code_Generation_for_Array_Aggregates:
Code Generation for Array Aggregates
====================================
Aggregates have a rich syntax and allow the user to specify the values of
complex data structures by means of a single construct. As a result, the
code generated for aggregates can be quite complex and involve loops, case
statements and multiple assignments. In the simplest cases, however, the
compiler will recognize aggregates whose components and constraints are
fully static, and in those cases the compiler will generate little or no
executable code. The following is an outline of the code that GNAT generates
for various aggregate constructs. For further details, you will find it
useful to examine the output produced by the -gnatG flag to see the expanded
source that is input to the code generator. You may also want to examine
the assembly code generated at various levels of optimization.
The code generated for aggregates depends on the context, the component values,
and the type. In the context of an object declaration the code generated is
generally simpler than in the case of an assignment. As a general rule, static
component values and static subtypes also lead to simpler code.
.. _Static_constant_aggregates_with_static_bounds:
Static constant aggregates with static bounds
---------------------------------------------
For the declarations:
.. code-block:: ada
type One_Dim is array (1..10) of integer;
ar0 : constant One_Dim := (1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
GNAT generates no executable code: the constant ar0 is placed in static memory.
The same is true for constant aggregates with named associations:
.. code-block:: ada
Cr1 : constant One_Dim := (4 => 16, 2 => 4, 3 => 9, 1 => 1, 5 .. 10 => 0);
Cr3 : constant One_Dim := (others => 7777);
The same is true for multidimensional constant arrays such as:
.. code-block:: ada
type two_dim is array (1..3, 1..3) of integer;
Unit : constant two_dim := ( (1,0,0), (0,1,0), (0,0,1));
The same is true for arrays of one-dimensional arrays: the following are
static:
.. code-block:: ada
type ar1b is array (1..3) of boolean;
type ar_ar is array (1..3) of ar1b;
None : constant ar1b := (others => false); -- fully static
None2 : constant ar_ar := (1..3 => None); -- fully static
However, for multidimensional aggregates with named associations, GNAT will
generate assignments and loops, even if all associations are static. The
following two declarations generate a loop for the first dimension, and
individual component assignments for the second dimension:
.. code-block:: ada
Zero1: constant two_dim := (1..3 => (1..3 => 0));
Zero2: constant two_dim := (others => (others => 0));
.. _Constant_aggregates_with_unconstrained_nominal_types:
Constant aggregates with unconstrained nominal types
----------------------------------------------------
In such cases the aggregate itself establishes the subtype, so that
associations with `others` cannot be used. GNAT determines the
bounds for the actual subtype of the aggregate, and allocates the
aggregate statically as well. No code is generated for the following:
.. code-block:: ada
type One_Unc is array (natural range <>) of integer;
Cr_Unc : constant One_Unc := (12,24,36);
.. _Aggregates_with_static_bounds:
Aggregates with static bounds
-----------------------------
In all previous examples the aggregate was the initial (and immutable) value
of a constant. If the aggregate initializes a variable, then code is generated
for it as a combination of individual assignments and loops over the target
object. The declarations
.. code-block:: ada
Cr_Var1 : One_Dim := (2, 5, 7, 11, 0, 0, 0, 0, 0, 0);
Cr_Var2 : One_Dim := (others > -1);
generate the equivalent of
.. code-block:: ada
Cr_Var1 (1) := 2;
Cr_Var1 (2) := 3;
Cr_Var1 (3) := 5;
Cr_Var1 (4) := 11;
for I in Cr_Var2'range loop
Cr_Var2 (I) := -1;
end loop;
.. _Aggregates_with_non-static_bounds:
Aggregates with non-static bounds
---------------------------------
If the bounds of the aggregate are not statically compatible with the bounds
of the nominal subtype of the target, then constraint checks have to be
generated on the bounds. For a multidimensional array, constraint checks may
have to be applied to sub-arrays individually, if they do not have statically
compatible subtypes.
.. _Aggregates_in_assignment_statements:
Aggregates in assignment statements
-----------------------------------
In general, aggregate assignment requires the construction of a temporary,
and a copy from the temporary to the target of the assignment. This is because
it is not always possible to convert the assignment into a series of individual
component assignments. For example, consider the simple case:
.. code-block:: ada
A := (A(2), A(1));
This cannot be converted into:
.. code-block:: ada
A(1) := A(2);
A(2) := A(1);
So the aggregate has to be built first in a separate location, and then
copied into the target. GNAT recognizes simple cases where this intermediate
step is not required, and the assignments can be performed in place, directly
into the target. The following sufficient criteria are applied:
*
The bounds of the aggregate are static, and the associations are static.
*
The components of the aggregate are static constants, names of
simple variables that are not renamings, or expressions not involving
indexed components whose operands obey these rules.
If any of these conditions are violated, the aggregate will be built in
a temporary (created either by the front-end or the code generator) and then
that temporary will be copied onto the target.
.. _The_Size_of_Discriminated_Records_with_Default_Discriminants:
The Size of Discriminated Records with Default Discriminants
============================================================
If a discriminated type `T` has discriminants with default values, it is
possible to declare an object of this type without providing an explicit
constraint:
.. code-block:: ada
type Size is range 1..100;
type Rec (D : Size := 15) is record
Name : String (1..D);
end T;
Word : Rec;
Such an object is said to be *unconstrained*.
The discriminant of the object
can be modified by a full assignment to the object, as long as it preserves the
relation between the value of the discriminant, and the value of the components
that depend on it:
.. code-block:: ada
Word := (3, "yes");
Word := (5, "maybe");
Word := (5, "no"); -- raises Constraint_Error
In order to support this behavior efficiently, an unconstrained object is
given the maximum size that any value of the type requires. In the case
above, `Word` has storage for the discriminant and for
a `String` of length 100.
It is important to note that unconstrained objects do not require dynamic
allocation. It would be an improper implementation to place on the heap those
components whose size depends on discriminants. (This improper implementation
was used by some Ada83 compilers, where the `Name` component above
would have
been stored as a pointer to a dynamic string). Following the principle that
dynamic storage management should never be introduced implicitly,
an Ada compiler should reserve the full size for an unconstrained declared
object, and place it on the stack.
This maximum size approach
has been a source of surprise to some users, who expect the default
values of the discriminants to determine the size reserved for an
unconstrained object: "If the default is 15, why should the object occupy
a larger size?"
The answer, of course, is that the discriminant may be later modified,
and its full range of values must be taken into account. This is why the
declaration:
.. code-block:: ada
type Rec (D : Positive := 15) is record
Name : String (1..D);
end record;
Too_Large : Rec;
is flagged by the compiler with a warning:
an attempt to create `Too_Large` will raise `Storage_Error`,
because the required size includes `Positive'Last`
bytes. As the first example indicates, the proper approach is to declare an
index type of 'reasonable' range so that unconstrained objects are not too
large.
One final wrinkle: if the object is declared to be `aliased`, or if it is
created in the heap by means of an allocator, then it is *not*
unconstrained:
it is constrained by the default values of the discriminants, and those values
cannot be modified by full assignment. This is because in the presence of
aliasing all views of the object (which may be manipulated by different tasks,
say) must be consistent, so it is imperative that the object, once created,
remain invariant.
.. _Strict_Conformance_to_the_Ada_Reference_Manual:
Strict Conformance to the Ada Reference Manual
==============================================
The dynamic semantics defined by the Ada Reference Manual impose a set of
run-time checks to be generated. By default, the GNAT compiler will insert many
run-time checks into the compiled code, including most of those required by the
Ada Reference Manual. However, there are three checks that are not enabled
in the default mode for efficiency reasons: arithmetic overflow checking for
integer operations (including division by zero), checks for access before
elaboration on subprogram calls, and stack overflow checking (most operating
systems do not perform this check by default).
Strict conformance to the Ada Reference Manual can be achieved by adding
three compiler options for overflow checking for integer operations
(*-gnato*), dynamic checks for access-before-elaboration on subprogram
calls and generic instantiations (*-gnatE*), and stack overflow
checking (*-fstack-check*).
Note that the result of a floating point arithmetic operation in overflow and
invalid situations, when the `Machine_Overflows` attribute of the result
type is `False`, is to generate IEEE NaN and infinite values. This is the
case for machines compliant with the IEEE floating-point standard, but on
machines that are not fully compliant with this standard, such as Alpha, the
*-mieee* compiler flag must be used for achieving IEEE confirming
behavior (although at the cost of a significant performance penalty), so
infinite and NaN values are properly generated.