[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This chapter describes the GNAT implementation of several Ada language facilities.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Package Machine_Code
provides machine code support as described
in the Ada 95 Reference Manual in two separate forms:
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 as described in Using the GNU Compiler Collection (GCC) by Richard Stallman. The relevant section is titled "Extensions to the C Language Family" -> "Assembler Instructions with C Expression Operands".
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.
The first example given in the GCC documentation is the C asm
instruction:
asm ("fsinx %1 %0" : "=f" (result) : "f" (angle)); |
The equivalent can be written for GNAT as:
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 for the parameter (e.g. what kind of register is
required). The second argument is the variable to be 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
.
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
constant are the same as those used in the RTL, and are dependent on
the configuration file used to built the GCC back end.
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.
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 that in particular, for an instruction that has outputs,
the instruction will still be generated, even if none of the outputs are
used. See the full description in the GCC manual for further details.
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:
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
12.2.1 Mapping Ada Tasks onto the Underlying Kernel Threads 12.2.2 Ensuring Compliance with the Real-Time Annex
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GNAT's run-time support comprises two layers:
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, OS/2, Windows NT, 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:
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GNAT fully implements the pragma Shared_Passive
for
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.
However, the implementation approach used by GNAT provides for more extensive usage as follows:
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.
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.
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
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 `___lock' (three underscores) is created in the shared memory directory. This is used to provide the required locking semantics for proper protected object synchronization.
As of January 2003, GNAT supports shared passive packages on all platforms except for OpenVMS.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Aggregate 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, the user 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. The user will 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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
For the declarations:
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:
Cr1 : constant One_Dim := (4 => 16, 2 => 4, 3 => 9, 1=> 1); Cr3 : constant One_Dim := (others => 7777); |
The same is true for multidimensional constant arrays such as:
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:
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:
Zero1: constant two_dim := (1..3 => (1..3 => 0)); Zero2: constant two_dim := (others => (others => 0)); |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
type One_Unc is array (natural range <>) of integer; Cr_Unc : constant One_Unc := (12,24,36); |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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
Cr_Var1 : One_Dim := (2, 5, 7, 11); Cr_Var2 : One_Dim := (others > -1); |
generate the equivalent of
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; |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
A := (A(2), A(1)); |
This cannot be converted into:
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:
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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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:
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:
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 Ada95 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:
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.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |