5. The GNAT Configurable Run-Time Facility

This chapter documents the configurable GNAT run-time facility. This facility enables versions of the GNAT run-time library to be created that only provide support for subsets of the Ada language for applications where run-time support for the full language is not appropriate or possible. This chapter is of interest to users who need to port the GNAT run-time to a new operating system or need to tailor one of the existing run-times to better suit their requirements.

5.1. Standard Run-Time

The standard GNAT run-time library provides all the features specified by the Ada Reference Manual and described in the GNAT Reference Manual. This run-time library is included on all native and most cross targets, and cannot be modified. If the compiler detects that the run-time library lacks interfaces for required language features, then the run-time library is considered to be improperly configured or installed, and an error message is given.

Do note that when a program is statically linked against the standard GNAT run-time library, only the sections of the library required by that program will be included. Nevertheless, keep in mind that these sections may contain a significant amount of code that may not be directly required by the applications.

5.2. The Configurable Run-Time

The Configurable Run-Time capability allows the creation of customized run-time libraries that support only a subset of the full Ada language. There are several reasons for providing the ability to configure the GNAT run-time library:

  • The underlying operating system may not provide the necessary features to support particular aspects of the language.
  • For embedded systems with tight memory constraints, it permits finer control on the size of the run-time library than what the linker can achieve. This is achieved by removing components that are not used by that application or replacing them with components that are better suited for the target environment.
  • For certification applications where the run-time units are required to be certifiable, since the certification process may require significant resources, it is desirable to reduce this certification effort by minimizing the run-time.

Using the configurable run-time capability, you can choose any level of support from the full run-time library to a minimal Zero Footprint Profile that requires no run-time code at all. The units included in the library may be either a subset of the standard units provided with GNAT Pro, or they may be specially tailored to the application.

Note that if you only require a subset of the Ada language and do not fit into the above scenarios, you can restrict the features of the language available to your application through the Restrictions pragma defined in the Ada Reference Manual.

5.3. Run-Time Libraries and Objects

The GNAT run-time library implements the semantics of Ada that cannot be generated by the compiler alone or where it would not be feasible to. The complexity of the run-time library depends on features used and the operating capabilities.

In addition to the GNAT run-time library, an Ada program may also link with following additional libraries:

  • C library
  • Math library
  • Internal GCC library
  • Startup code

GPRbuild and gnatlink automatically link these libraries and objects with the final executable, statically or dynamically depending on the target and compilation options. This behavior may be suppressed by specifying the -nodefaultlibs or -nostdlib linker options. The former switch causes the linker to not use the above libraries, while the latter switch will also not use the startup code. Be aware when either switch is used the compiler will still generate calls to functions in those libraries: it is your responsibility to provide implementations of these functions in this case.

GNAT attempts to find these libraries and objects in several standard system directories plus any that are specified with the -L option or the LIBRARY_PATH environment variable. The gcc --print-search-dirs command prints the name of the configured installation directory and a list of program and library directories where gcc will search.

The following sections define the contents and purpose of the various elements potentially included in an application’s executable.

5.3.1. GNAT Run-Time Library

The high abstraction level and expressiveness provided by the full Ada language requires a large run-time library. This library bridges the semantic gap between the high-level Ada constructs and the low-level C functions and representations available in the target system. Hence, the semantics of Ada constructs are expanded into calls to a collection of lower-level run-time constructions. An example of this is the implementation of Ada tasking.

The GNAT run-time library comprises both C and Ada files. Ada packages within the GNAT run-time library implement the required Ada semantics. For native and cross targets, these Ada packages are built on top of the operating system services, typically provided through a C interface.

The GNAT run-time library and user applications typically depend on the following set of libraries:

  • C Library (libc.a) for interfacing to operating system provided services such as the input/output system, memory management, etc.
  • Math Library (libm.a) for everything related to the functionality specified in the Ada Numerics Annex.
  • Internal GCC Library (libgcc.a) for features such as integer and floating-point operations, and exception handling.

5.3.2. C Library

The C library is provided by the operating system and provides standard ANSI C functionality that is used by the GNAT run-time library:

  • Standard Utility Functions (stdlib.h)
  • Character Type Macros and Functions (ctype.h)
  • Input and Output (stdio.h)
  • Strings and Memory (string.h)
  • Wide Character Strings (wchar.h)
  • Signal Handling (signal.h)
  • Time Functions (time.h)
  • Locale (locale.h)

For bareboard targets, there is an assumption that libc is not present. Consequently, they use modified run-time Ada packages that implemented the required functionality directly in Ada.

Adapting GNAT to an operating systems that is missing any of the above will require you to either provide your own implementation of the missing routines or to modify the GNAT run-time library. This run-time modification may see the Ada packages relying on that function removed from the run-time library or replaced with an alternative non-libc-based implementation. The bareboard run-time libraries are an example of this.

Note that if you chose not to link the GNAT run-time library and the C library (through the -nodefaultlibs or -nostdlib linker options) you may still need to provide implementations of the following routines:

Functionality Routines
Basic memory operations memcpy, bcopy, memmove, memcmp
Dynamic memory (heap) management malloc, free

5.3.3. Math Library

A complete IEEE math library is usually provided by libm.a, which includes functions that take float, double, and long-double parameters. Depending on the type used the function has a different extension. These extensions are named after their full-precision equivalents; i.e., sinf() is the single-precision version of the sin() function, and sinl() is the long-double variant. The reduced-precision functions run much faster than their IEEE-compliant double-precision counterparts, which can make some floating-point operations practical on hardware that is too weak for full double-precision computations.

5.3.4. Internal GCC Library

Internal GCC Library is a collection of internal subroutines that GCC uses to overcome shortcomings of particular machines, or to satisfy the special needs of some languages. For GNAT it provides support for numerical operations that cannot be performed natively in hardware (for example 64-bit integer arithmetic on a 32-bit processor) and exception handling. Refer to the GCC Internals manual for more information.

5.3.5. Startup and Cleanup Code

The startup and cleanup code are linked at the beginning and at the end of the executable. Their purpose is:

  • to provide the entry point to the program.
  • to perform required program initialization (e.g., initialize hardware, reserve space for stack, zeroing the .bss section),
  • to bootstrap the rest of the application, and
  • to arrange the necessary ‘cleanup’ / finalization after program execution completes.

For native and cross targets, the startup and cleanup code is typically provided by the operating system and are typically consist of assembly files named crt*.S (crt stands for ‘C Run Time’). For bareboard targets, the startup code is provided by the run-time library and may consist of a collection of assembly and Ada code. Refer to the target specific section for more details.

5.4. Run-Time Dependencies in User Code

5.4.1. Explicit with Clauses

The use of with clauses creates a dependence relationship between Ada units. This relationship is computed at compilation time and recorded in the ali file produced for each object. The final executable will contain all the objects corresponding to the units in the dependence closure of the main unit.

This is the simplest and most common way of determining the required set of objects in the final application.

5.4.2. Compiler-Generated Calls to the GNAT Run-Time Library

When an Ada source file is compiled, the GNAT compiler expands high-level Ada abstractions into low-level primitives that can be converted directly into assembly. As part of this expansion, the compiler will generate calls to run-time routines that implement semantics that either rely on operating system support or that are more efficiently implemented via a run-time library. The expanded low-level representation of the original source code can be displayed in an Ada-like format using the -gnatD or -gnatG compiler switches.

5.4.3. Pragma Import

The Import aspect specifies that the designated entity is defined externally. This will cause the object file (and its dependencies) that contains the symbol to be incorporated with the program object file at link time.

Be aware that using the Import aspect is not advised to import an Ada subprogram as the binder will not be able to know where this symbol comes from, and the elaboration code that the imported routine may require will not be called.

5.4.4. Back-End Generated Calls to Library Functions

The GCC back-end may generate ‘implicit’ calls to library subprograms for various reasons. Such calls are said to be implicit because they do not directly correspond to explicit subprogram invocations in the application source code.

Implicit calls to library subprograms occur for several reasons:

  1. Integer and floating-point operations. Some source operations require arithmetic support not available on the target hardware.
  2. Run-time support for exception handling and trampolines. Some high-level constructs require low-level data structure management too complex to emit inline code for.
  3. Basic memory operations. Some basic memory operations are too expensive to expand inline, e.g. large block copies or comparisons.

For (a), what the compiler knows about the target hardware may depend on compilation options. For instance, -msoft-float triggers calls to library functions for floating-point operations even if the hardware has the necessary instructions available. Similarly, the -mcpu switch allows modifying the compiler’s default view of the target hardware.

The functions to support (a) and (b) are located in libgcc.a, the GCC low-level run-time library built together with the compiler itself. The basic memory operations on the other hand are provided by the C library.

Note that each toolchain is configured for a particular set of core cpus, and not all combinations of -mcpu or -msoft-float switches are supported. For instance, support for the e500v2 powerpc core requires a different toolchain than the default powerpc one.

5.5. How The Run Time Library Is Configured

There are three major mechanisms for tailoring the run-time library.

  • Use of Configuration Pragmas
  • Specification of Configuration Parameters
  • Restricting the Set of Run-Time Units

These three mechanisms work together to provide a coherent run-time library that provides a well-defined subset. They allow the compiler to properly enforce the corresponding language subset, providing informative and appropriate messages if features not supported by the subset are used.

5.5.1. Configuration Pragmas

The Ada language provides configuration pragmas to constrain the language features available to a program or to specify a particular semantic behavior. While these pragmas can be specified on a per-program basis either in the program sources or in a *.adc file, they can also be specified on a run-time library basis if the run-time only supports a particular set of language features.

This is achieved by placing the configuration pragmas at the start of the System package. The System package is implicitly with‘ed by all packages, thus all units compiled against this run-time library will have these restrictions.

The following list of configuration pragmas may be used:

pragma Detect_Blocking;
pragma Discard_Names;
pragma Locking_Policy (name);
pragma Normalize_Scalars;
pragma Polling (On);
pragma Queuing_Policy (name);
pragma Task_Dispatching_Policy (name);

In addition, Restrictions pragmas may be used for all simple restrictions that are required to be applied consistently throughout a partition. GNAT supports all restrictions defined in the Ada Reference Manual and the implementation-defined restrictions contained within the GNAT Reference Manual. Refer to the run-time specification package s-rident.ads for a definitive list of supported restrictions.

No other pragmas are allowed in package System (other than the pragma Pure for System itself which is always present).

5.5.2. Specification of Configuration Parameters

The private part of the System package defines a number of Boolean configuration switches, which control the support of specific language features. This section documents these switches.

-- Run-Time Library Configuration --

--  In configurable run-time mode, the system run-time may not support
--  the full Ada language. The effect of setting this switch is to let
--  the compiler know that it is not surprising (i.e. the system is not
--  misconfigured) if run-time library units or entities within units are
--  not present in the run-time.

Configurable_Run_Time : Boolean;
--  Indicates that the system.ads file is for a configurable run-time
--  This has some specific effects as follows
--    The binder generates the gnat_argc/argv/envp variables in the
--    binder file instead of being imported from the run-time library.
--    If Command_Line_Args is set to False, then the
--    generation of these variables is suppressed completely.
--    The binder generates the gnat_exit_status variable in the binder
--    file instead of being imported from the run-time library. If
--    Exit_Status_Supported is set to False, then the
--    generation of this variable is suppressed entirely.
--    The routine __gnat_break_start is defined within the binder file
--    instead of being imported from the run-time library.
--    The variable __gnat_exit_status is generated within the binder file
--    instead of being imported from the run-time library.

Suppress_Standard_Library : Boolean;
--  If this flag is True, then the standard library is not included by
--  default in the executable (see unit System.Standard_Library in file
--  s-stalib.ads for details of what this includes). This is for example
--  set True for the zero foot print case, where these files should not
--  be included by default.
--  This flag has some other related effects:
--    The generation of global variables in the bind file is suppressed,
--    with the exception of the priority of the environment task, which
--    is needed by the Ravenscar run-time.
--    The calls to __gnat_initialize and __gnat_finalize are omitted
--    All finalization and initialization (controlled types) is omitted

Preallocated_Stacks : Boolean;
--  If this flag is True, then the expander preallocates all task stacks
--  at compile time. If the flag is False, then task stacks are not pre-
--  allocated, and task stack allocation is the responsibility of the
--  run-time (which typically delegates the task to the underlying
--  operating system environment).

-- Backend Arithmetic Checks --

--  Divide and overflow checks are either done in the front end or
--  back end. The front end will generate checks when required unless
--  the corresponding parameter here is set to indicate that the back
--  end will generate the required checks (or that the checks are
--  automatically performed by the hardware in an appropriate form).

Backend_Divide_Checks : Boolean;
--  Set True if the back end generates divide checks, or if the hardware
--  checks automatically. Set False if the front end must generate the
--  required tests using explicit expanded code.

Backend_Overflow_Checks : Boolean;
--  Set True if the back end generates arithmetic overflow checks, or if
--  the hardware checks automatically. Set False if the front end must
--  generate the required tests using explicit expanded code.

-- Control of Exception Handling --

--  GNAT implements three methods of implementing exceptions:

--    Front-End Longjmp/Setjmp Exceptions

--      This approach uses longjmp/setjmp to handle exceptions. It
--      uses less storage, and can often propagate exceptions faster,
--      at the expense of (sometimes considerable) overhead in setting
--      up an exception handler.

--      The generation of the setjmp and longjmp calls is handled by
--      the front end of the compiler (this includes gigi in the case
--      of the standard GCC back end). It does not use any back end
--      support (such as the GCC3 exception handling mechanism). When
--      this approach is used, the compiler generates special exception
--      handlers for handling cleanups (AT-END actions) when an exception
--      is raised.

--    Back-End Zero Cost Exceptions

--      With this approach, the back end handles the generation and
--      handling of exceptions. For example, the GCC3 exception handling
--      mechanisms are used in this mode. The front end simply generates
--      code for explicit exception handlers, and AT-END cleanup handlers
--      are simply passed unchanged to the backend for generating cleanups
--      both in the exceptional and non-exceptional cases.

--      As the name implies, this approach uses a table-based mechanism,
--      which incurs no setup when entering a region covered by handlers
--      but requires complex unwinding to walk up the call chain and search
--      for handlers at propagation time.

--    Back-End Setjmp/Longjmp Exceptions

--      With this approach, the back end also handles the generation and
--      handling of exceptions, using setjmp/longjmp to set up receivers and
--      propagate. AT-END actions on exceptional paths are also taken care
--      of by the back end and the front end doesn't need to generate
--      explicit exception handlers for these.

--    Control of Available Methods and Defaults

--      The following switches specify whether we're using a front-end or a
--      back-end mechanism and whether this is a zero-cost or a sjlj scheme.

--      The per-switch default values correspond to the default value of
--      Opt.Exception_Mechanism.

ZCX_By_Default : Boolean;
--  Indicates if zero cost scheme for exceptions

Frontend_Exceptions : Boolean;
--  Indicates if we're using a front-end scheme for exceptions

-- Duration Format --

--  By default, type Duration is a 64-bit fixed-point type with a delta
--  and small of 10**(-9) (i.e. it is a count in nanoseconds). This flag
--  allows that standard format to be modified.

Duration_32_Bits : Boolean;
--  If True, then Duration is represented in 32 bits and the delta and
--  small values are set to 20.0*(10**(-3)) (i.e. it is a count in units
--  of 20 milliseconds).

-- Back-End Code Generation Flags --

--  These flags indicate possible limitations in what the code generator
--  can handle. They will all be True for a full run-time, but one or more
--  of these may be false for a configurable run-time, and if a feature is
--  used at the source level, and the corresponding flag is false, then an
--  error message will be issued saying the feature is not supported.

Support_Aggregates : Boolean;
--  In the general case, the use of aggregates may generate calls
--  to run-time routines in the C library, including memset, memcpy,
--  memmove, and bcopy. This flag is set to True if these routines
--  are available. If any of these routines is not available, then
--  this flag is False, and the use of aggregates is not permitted.

Support_Atomic_Primitives : Boolean;
--  If this flag is True, then the back-end support GCC built-in atomic
--  operations for memory model such as atomic load or atomic compare
--  exchange (see the GCC manual for more information). If the flag is
--  False, then the back-end doesn't provide this support. Note this flag is
--  set to True only if the target supports all atomic primitives up to 64
--  bits. ??? To be modified.

Support_Composite_Assign : Boolean;
--  The assignment of composite objects other than small records and
--  arrays whose size is 64-bits or less and is set by an explicit
--  size clause may generate calls to memcpy, memmove, and bcopy.
--  If versions of all these routines are available, then this flag
--  is set to True. If any of these routines is not available, then
--  the flag is set False, and composite assignments are not allowed.

Support_Composite_Compare : Boolean;
--  If this flag is True, then the back end supports bit-wise comparison
--  of composite objects for equality, either generating inline code or
--  calling appropriate (and available) run-time routines. If this flag
--  is False, then the back end does not provide this support, and the
--  front end uses component by component comparison for composites.

Support_Long_Shifts : Boolean;
--  If True, the back end supports 64-bit shift operations. If False, then
--  the source program may not contain explicit 64-bit shifts. In addition,
--  the code generated for packed arrays will avoid the use of long shifts.

-- Indirect Calls --

Always_Compatible_Rep : Boolean;
--  If True, the Can_Use_Internal_Rep flag (see Einfo) is set to False in
--  all cases. This corresponds to the traditional code generation
--  strategy. False allows the front end to choose a policy that partly or
--  entirely eliminates dynamically generated trampolines.

-- Control of Stack Checking --

--  GNAT provides three methods of implementing exceptions:

--    GCC Probing Mechanism

--      This approach uses the standard GCC mechanism for
--      stack checking. The method assumes that accessing
--      storage immediately beyond the end of the stack
--      will result in a trap that is converted to a storage
--      error by the runtime system. This mechanism has
--      minimal overhead, but requires complex hardware,
--      operating system and run-time support. Probing is
--      the default method where it is available. The stack
--      size for the environment task depends on the operating
--      system and cannot be set in a system-independent way.

--   GCC Stack-limit Mechanism

--      This approach uses the GCC stack limits mechanism.
--      It relies on comparing the stack pointer with the
--      values of a global symbol. If the check fails, a
--      trap is explicitly generated. The advantage is
--      that the mechanism requires no memory protection,
--      but operating system and run-time support are
--      needed to manage the per-task values of the symbol.
--      This is the default method after probing where it
--      is available.

--   GNAT Stack-limit Checking

--      This method relies on comparing the stack pointer
--      with per-task stack limits. If the check fails, an
--      exception is explicitly raised. The advantage is
--      that the method requires no extra system dependent
--      runtime support and can be used on systems without
--      memory protection as well, but at the cost of more
--      overhead for doing the check. This is the fallback
--      method if the above two are not supported.

Stack_Check_Probes : Boolean;
--  Indicates if the GCC probing mechanism is used

Stack_Check_Limits : Boolean;
--  Indicates if the GCC stack-limit mechanism is used

--  Both flags cannot be simultaneously set to True. If neither
--  is, the target independent fallback method is used.

Stack_Check_Default : Boolean;
--  Indicates if stack checking is on by default

-- Command Line Arguments --

--  For most ports of GNAT, command line arguments are supported. The
--  following flag is set to False for targets that do not support
--  command line arguments (VxWorks and AAMP). Note that support of
--  command line arguments is not required on such targets (RM A.15(13)).

Command_Line_Args : Boolean;
--  Set False if no command line arguments on target. Note that if this
--  is False in with Configurable_Run_Time set to True, then
--  this causes suppression of generation of the argv/argc variables
--  used to record command line arguments.

--  Similarly, most ports support the use of an exit status, but AAMP
--  is an exception (as allowed by RM A.15(18-20))

Exit_Status_Supported : Boolean;
--  Set False if returning of an exit status is not supported on target.
--  Note that if this False in with Configurable_Run_Time
--  set to True, then this causes suppression of the gnat_exit_status
--  variable used to record the exit status.

-- Main Program Name --

--  When the binder generates the main program to be used to create the
--  executable, the main program name is main by default (to match the
--  usual Unix practice). If this parameter is set to True, then the
--  name is instead by default taken from the actual Ada main program
--  name (just the name of the child if the main program is a child unit).
--  In either case, this value can be overridden using -M name.

Use_Ada_Main_Program_Name : Boolean;
--  Set True to use the Ada main program name as the main name

-- Boolean-Valued Floating-Point Attributes --

--  The constants below give the values for representation oriented
--  floating-point attributes that are the same for all float types
--  on the target. These are all boolean values.

--  A value is only True if the target reliably supports the corresponding
--  feature. Reliably here means that support is guaranteed for all
--  possible settings of the relevant compiler switches (like -mieee),
--  since we cannot control the user setting of those switches.

--  The attributes cannot dependent on the current setting of compiler
--  switches, since the values must be static and consistent throughout
--  the partition. We probably should add such consistency checks in future,
--  but for now we don't do this.

--  Note: the compiler itself does not use floating-point, so the
--  settings of the defaults here are not really relevant.

--  Note: in some cases, proper support of some of these floating point
--  features may require a specific switch (e.g. -mieee on the Alpha)
--  to be used to obtain full RM compliant support.

Denorm : Boolean;
--  Set to False on targets that do not reliably support denormals

Machine_Rounds : Boolean;
--  Set to False for targets where S'Machine_Rounds is False

Machine_Overflows : Boolean;
--  Set to True for targets where S'Machine_Overflows is True

Signed_Zeros : Boolean;
--  Set to False on targets that do not reliably support signed zeros

5.5.3. Restricting the Set of Run-Time Units

Many Ada language features generate implicit calls to the run-time library. For example, if we have the Ada procedure:

pragma Suppress (All_Checks);
function Calc (X : Integer) return Integer is
   return X ** 4 + X ** 52;
end Calc;

Then the compiler will generate the following code (this is -gnatG output):

with system.system__exn_int;

function calc (x : integer) return integer is
   E1b : constant integer := x * x;
   return integer (E1b * E1b +
                    integer(system__exn_int__exn_integer (x, 52)));
end calc;

In the generated code, the compiler generates direct inlined code for X ** 4 (by computing (X ** 2) ** 2). But the computation of X ** 52 requires a call to the run-time routine System.Exn_Int.Exn_Integer (the double underlines in the -gnatG output represent dots in the name).

The GNAT run-time library contains an appropriate package that provides this function:

--  Integer exponentiation (checks off)

package System.Exn_Int is
.. index:: ``System.Exn_Int`` package

pragma Pure (Exn_Int);

   function Exn_Integer
     (Left  : Integer;
      Right : Natural)
      return  Integer;

end System.Exn_Int;

For a configurable run-time library, the package System.Exn_Int may or may not be present. If not present, then the run-time library does not permit the use of exponentiation by large integer values, and an attempt to compile Calc will result in the following error message:

1. function Calc (X : Integer) return Integer is
2. begin
3.   return X ** 4 + X ** 52;
   >>> construct not allowed in this configuration
   >>> entity "System.Exn_Int.Exn_Integer" not defined

4. end Calc;

The first line of the error message indicates that the construct is not provided in the library. The second line shows the exact entity that is missing. In this case, it is the entity Exn_Integer in package System.Exn_Int. This package is in file s-exnint.ads (you can use the command gnatkr system.exn_int.ads to determine the file name).

If exponentiation is required, then this package must be provided, and must contain an appropriate declaration of the missing entity. There are two ways to accomplish this. Either the standard GNAT body can be copied and used in the configurable run-time, or a new body can be written that satisfies the specification. Rewriting the body may be useful either to simplify the implementation (possibly taking advantage of configuration pragmas provided in system.ads), or to meet coding requirements of some particular certification protocol.

Alternatively, the source code can be modified to call an exponentiation routine that is defined within the application:

with Exp;
function Calc (X : Integer) return Integer is
  return Exp (X, 4) + Exp (X, 52);
end Calc;

5.6. Naming the Run-Time Library

To assist in keeping track of multiple run-time configurations, GNAT Pro provide a facility for naming the run-time library. To do this, include a line with the following format (starting in column 4) in system.ads:

Run_Time_Name : constant String := "Simple Run Time 1";

The name may contain letters, digits, spaces and underlines. If such a name is provided, then error messages pertaining to the subset include the name of the library:

1. function Calc (X : Integer) return Integer is
2. begin
3.   return X ** 4 + X ** 52;
   >>> construct not allowed in this configuration (Simple Run Time 1)

4. end Calc;

5.7. Creating a Configurable Run-Time Library

As described above, the run-time library may be tailored to suit a specific application. This process can be carried out either by augmenting an existing restricted run-time library implementation or by reducing an existing library. It is of course possible to add any units. However, the configuration of a complex run-time library may be quite difficult, and is best carried out in consultation with experts who are familiar with the structure of the GNAT run-time libraries.

For details on how to customize the bareboard run-time libraries please refer to Customized Run-Time Libraries. For custom cross run-time libraries please reach out to AdaCore support.