1. Getting Started¶
1.1. Introduction¶
GNAT Pro Common Code Generator (also known as GNAT Pro CCG) is a compiler based on the GNAT Pro technology that takes a subset of Ada source code as input and generates corresponding C source code with the same semantics, suitable for compilation by any target C compiler. In other words, a subset of C is used as a high-level and portable assembly to compile Ada source code.
Note that this manual comes as a complement to the GNAT, GPRbuild, and GNAT Studio documentation (User’s Guides and Reference Manuals).
1.2. How to Use GNAT Pro CCG¶
In order to use the GNAT Pro CCG compiler, you first need to create a project
file (.gpr
) as described in the CodePeer User’s Guide, section
Setting Up a Project File or in the GPRbuild User’s Guide, section
GNAT Project Manager.
In a nutshell, you need to at least specify the source directories where your Ada source files are located, and it is recommended, although not mandatory, to specify an object directory where the generated C code will be created. Here is an example of a project file:
project My_Project is
for Source_Dirs use (".", "src1", "src2"); -- where source files are
for Object_Dir use "obj"; -- where compilation artifacts will be
for Main use ("main_unit.adb"); -- the name of the main file
package Compiler is
for Switches ("Ada") use
("-gnatp"); -- switches used for compiling Ada files
end Compiler;
end My_Project;
You need to run using gprbuild and specify the special “c” target, either
via the --target
command line switch, or via the Target
project file
attribute:
$ gprbuild -p --target=c -Pmy_project
or:
project My_Project is
for Target use "c";
for Source_Dirs use ...;
-- other project properties
end My_Project;
$ gprbuild -p -Pmy_project
gprbuild
knows which files need to be (re)compiled and will generate a
.c
file for each Ada unit in your project under the object
directory of your project and a bind file which takes care of initializing
the runtime and invoking the main unit (for a commented example see
http://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ugn/example_of_binder_output.html).
Calling the target C compiler on the generated C files and performing the final link is left as the responsibility of the user, since it depends on the compiler environment used.
For example, assuming a GCC C compiler, you can do:
$ cd obj
$ gcc *.c -o main
2. Supported Constructs and Limitations¶
2.1. Supported Constructs¶
Here is a list of constructs supported and not supported by this technology:
Support for constructs that do not require runtime support:
packages (including child, separate, and generic packages)
subprograms (including separate, generic, nested, and overloaded subprograms)
most Ada types and subtypes, including:
scalar types (integer, enumeration)
floating point types
fixed point types
access types
constrained and unconstrained arrays
Note that support for multidimensional unconstrained arrays requires a C99 compatible C compiler. If the target C compiler only supports earlier versions of the C standard, then only one-dimensional unconstrained arrays can be used.
Array indices have to be within the bounds of the size_t C type, in other words you cannot use e.g. array indices of more than 2**31-1 on 32-bit targets.
record types
tagged types: Objects of tagged and class-wide types may be declared. Dispatching and class-wide subprograms are supported (including the object.operation notation).
private types
limited support for dynamically sized record types: records with a single (last) field whose size depends on a discriminant. Note that this feature builds on top of the C idiom “field[1];” where field is actually a larger array (allocated explicitly with sufficient memory). This idiom was replaced by flexible array declaration: “field[];” in C99.
Support for a minimal standard library only
delay until statement
Representation clauses that map to C bitfields only (records containing integer types only, of 1 to 64 bits)
Unsupported record representation clauses will generate a warning (unsupported representation clause, assuming confirming) and the generated code will ignore the unsupported representation clause, assuming the clause is simply confirming the default layout. This assumption (and this warning) need to be verified manually.
Support for packed arrays (e.g. arrays of booleans)
Support for assertions/preconditions/postconditions
Raising an exception (explicitly or implicitly via a runtime check or an assertion failure) is mapped to a call to a last chance handler subprogram. See Exception Handling for more details.
Support for most runtime checks: range, access, index, divide-by-zero checks.
Limited support for ‘Size:
Reading ‘Size is implemented via the C sizeof() built-in
Setting the Size or Object_Size of types and objects is ignored in most cases, the size of the underlying type is left to the underlying C compiler.
Support for ‘Valid on floating point requires the C99 isfinite() function.
No support for overflow checks
No support for subprograms in generic packages instantiated inside a subprogram, or for runtime pre/postconditions on subprograms in generic packages.
No support for the following constructs requiring runtime:
tasking
controlled types
interface types
exception handling
storage pools
functions returning unconstrained arrays or dynamically sized records
No support for ‘Image on floating point and fixed point types, only scalars
No support for the following attributes:
Alignment
Component_Size
Rounding
Bit
Bit_Position
First_Bit
Last_Bit
Position
Constrained
Mechanism_Code
Null_Parameter
Passed_By_Reference
No support for assembly insertion. If you need to include assembly code, you can do so by putting the assembly in a separate assembly file.
2.2. Minimal Runtime¶
GNAT Pro CCG comes with a minimal runtime including the following packages:
Ada
Ada.Assertions
Ada.Numerics
Ada.Numerics.Elementary_Functions
This is a simplified version of this package which maps directly to the underlying <math.h> C library.
Ada.Numerics.Long_Elementary_Functions
This is a simplified version of this package which maps directly to the underlying <math.h> C library.
Ada.Real_Time
Support for Ada.Real_Time.Clock requires the following two functions to be provided and tailored for the underlying target:
unsigned long rts_monotonic_clock (void); /* Returns time from the board/OS */ int rts_rt_resolution (void); /* Returns resolution of the underlying clock used to implement */ /* Monotonic_Clock. */
Support for the delay until statement requires the following external function:
void rts_delay_until (unsigned long t); /* Delay until a given time as returned by rts_monotonic_clock */
Ada.Text_IO and Text_IO
This is a simplified version for doing simple output of characters and strings, and no input. If you want to output scalar types, you can use the ‘Image attribute to transform a scalar value into a string. This package depends on the C standard function putchar().
Ada.Unchecked_Conversion and Unchecked_Conversion
Ada.Unchecked_Deallocation and Unchecked_Deallocation
Interfaces
Interfaces.C
Interfaces.C.Extensions
System
System.Storage_Elements
GNAT
GNAT.Source_Info
standard.h
This C header provides predefined Ada definitions as part of package Standard as well as some declarations needed by the generated code. It provides default settings suitable for most C99-compatible compilers (e.g. assumes that long long is supported by the compiler). Depending on your target C compiler, you might need to customize this file. You can either modify the file in the installation directory directly, or you can make a copy in one of your source directories, and then set the ADA_INCLUDE_PATH environment variable to point to this source directory, e.g:
export ADA_INCLUDE_PATH=<directory containing standard.h>
If your target C compiler does not provide any standard include and your Ada code does not depend on any C standard library, then you can compile the target C code with the
-DCCG_NO_STD_INCLUDE
which will disable the use of any include directive, unless your C compiler supports ISO C99 and sets the corresponding macro__STDC_VERSION__
to a value greater or equal to199901L
in which case,stdint.h
andstdbool.h
are referenced.If you want to override the definition of the
GNAT_INLINE
macro without modifying standard.h, you can do so when compiling the target C code by redefining this macro via e.g.-DGNAT_INLINE=inline
.
This runtime can be found under the directory listed by the following command:
$ c-gnatls -v | grep adainclude
2.3. Use of GNAT runtime files¶
Some constructs (in particular the Image attribute and the “**” operator)
require support from GNAT runtime units. These runtime units can be found
under the adainclude
directory, as given by the c-gnatls -v command.
The corresponding .c
and .h
files for these units can be
found under the adalib
directory. You will need to include these
files as part of your C build if and only if you use these Ada constructs.
To do so, one option is to copy the needed C files to your C build directory
and compile these files with your target C compiler, along with the C files
generated by GNAT Pro CCG. The C linker will typically tell you that some
symbols are missing, so if you can successfully and fully link your Ada
application then it means that you do not need to link with GNAT runtime files.
See also Composite Assignment and Comparison for other external dependencies generated directly by GNAT Pro CCG.
2.4. Composite Assignment and Comparison¶
In order to implement support for composite (records and arrays) assignments, The compiler may generate calls to the standard C routines memcpy() and memmove() that need to be provided either by your target C compiler, or as part of your sources.
Similarly for composite comparison, calls to memcmp() may be generated.
If your target C compiler does not provide these routines then you have several options:
manually replace composite assignments or comparisons in the code, for example by using loops (in the case of arrays).
or provide your own implementation of these routines.
For example, here is a simple implementation of memcpy() that you can add as part of your project:
void *memcpy(void *dest, const void *src, size_t n)
{
char *src_p = (char *)src;
char *dest_p = (char *)dest;
size_t i;
if (n == 0)
return dest;
/* Copy contents of src[] to dest[] backwards. This loop in particular */
/* properly handles the case of n == SIZE_MAX. */
i = n - 1;
do {
dest_p[i] = src_p[i];
} while (i-- > 0);
return dest;
}
Similarly for memmove():
void *memmove(void *dest, const void *src, size_t n)
{
char *src_p = (char *)src;
char *dest_p = (char *)dest;
size_t i;
if (n == 0)
return dest;
/* This function must handle overlapping memory regions */
/* for the source and destination. If the dest buffer is */
/* located in the middle of the src buffer then we use */
/* backward copying, and forward copying otherwise. */
if (dest > src && dest < src + n)
{
i = n - 1;
do {
dest_p[i] = src_p[i];
} while (i-- > 0);
}
else
{
/* Copy in two parts to properly handle the case of n == SIZE_MAX */
for (i = 0; i < n - 1; i++)
dest_p[i] = src_p[i];
/* i == n - 1 at this stage */
dest_p[i] = src_p[i];
}
return dest;
}
And memcmp():
int memcmp(const void *s1, const void *s2, size_t n)
{
char *s1_p = (char *)s1;
char *s2_p = (char *)s2;
size_t i;
if (n == 0)
return 0;
for (i = 0; i < n - 1; i++)
{
if (s1_p[i] < s2_p[i])
return -1;
else if (s1_p[i] > s2_p[i])
return 1;
}
/* i == n - 1 at this stage */
if (s1_p[i] < s2_p[i])
return -1;
else if (s1_p[i] > s2_p[i])
return 1;
else
return 0;
}
3. Configuration of the Compiler¶
3.1. Relevant Switches¶
Here are some switches which influence the generation of C code:
-gnatceg
This is the main switch to enable generation of C code. This switch is enabled implicitly when using
gprbuild --target=c
as described in the previous section, so you do not need to specify it explicitly.-g
Enable debug information. In this context, debug information means generating #line directives, see Debugging & Code Generation Strategy.
-gnatL
Generate in the C code the original Ada code interspersed as C comments. This option can help trace or debug the C code. See also Debugging & Code Generation Strategy.
-gnatp
Suppress all runtime checks. See Enabling/Disabling Runtime Checks.
-gnata
Enable all assertions. See Enabling/Disabling Runtime Checks.
-gnateT
Specify a target configuration file. See Target Configuration File.
As well as some advanced switches:
-gnatd.4
This switch forces the generation of a C file even in the case of errors (e.g. unsupported constructs) emitted by the code generator. The resulting file will likely be incomplete and uncompilable. Note that this switch has no effect in the case of illegal Ada code.
-gnatd.5
This switch may optionally be used to stop the generation of C profiles for subprograms imported with a C convention. This switch can be useful in case the Ada profile for an imported C subprogram generates a conflict with the actual C declaration. When using this switch you will need to provide the C declaration by another means, for example by adding the needed #include directive in the
standard.h
file that comes with the runtime.-gnatd.6
By default the back-end avoids declaring types that are not referenced by the generated C code; these types may be internal types generated by the front-end or types defined in the sources passed to the compiler that are not used to declare other types or variables. This switch may be used to force the output of all the types.
3.2. Target Configuration File¶
In order to best describe your actual target, you may need to define a target configuration file describing e.g. the endianness of the target, the size of pointer and integer types, etc. In particular, the compiler is assuming by default a 32-bit little-endian target. So if your target is different, you will need to create a target configuration file by specifying this appropriately in the project file:
package Builder is
for Global_Compilation_Switches ("Ada") use
("-gnateT=" & project'Project_Dir & "/target.atp");
end Builder;
where target.atp
is a file stored here in the same directory as
your project file, which contains the target parameterization.
The format of this file is described in the GNAT User’s Guide as part
of the -gnateT switch description.
Here is an example of a configuration file for a bare board PowerPC 750 processor configured as big-endian:
Bits_BE 1
Bits_Per_Unit 8
Bits_Per_Word 32
Bytes_BE 1
Char_Size 8
Double_Float_Alignment 0
Double_Scalar_Alignment 0
Double_Size 64
Float_Size 32
Float_Words_BE 1
Int_Size 32
Long_Double_Size 64
Long_Long_Size 64
Long_Size 32
Maximum_Alignment 16
Max_Unaligned_Field 64
Pointer_Size 32
Short_Enums 0
Short_Size 16
Strict_Alignment 1
System_Allocator_Alignment 8
Wchar_T_Size 32
Words_BE 1
float 6 I 32 32
double 15 I 64 64
long double 15 I 64 64
3.3. Relevant Pragmas¶
Most Ada and GNAT pragmas are supported.
The following are particularly relevant in the context of GNAT Pro CCG. You will find more details about these pragmas in the GNAT Reference Manual.
pragma Restrictions (No_Dynamic_Sized_Objects)
Ensure that the Ada code will not contain objects of dynamic size which require the use of alloca() or dynamic sized C objects, which are not supported by all C compilers. This pragma cannot be used when the sources have tagged types since dispatch tables require dynamically sized record types.
pragma Restrictions (No_Multiple_Elaboration)
When this restriction is active, the compiler is allowed to suppress the elaboration counter normally associated with the unit, even if the unit has elaboration code. This counter is typically used to check for access before elaboration and to control multiple elaboration attempts. This is useful in the context of CCG to reduce the size of the generated code to a minimum and control more finely the global symbols in C files.
pragma Discard_Names
Removes generation of internal strings in particular for enumeration types, generating simpler and smaller C code. This pragma also disables the support for ‘Image on enumeration types.
pragma Suppress_Exception_Locations
Suppress messages associated with exceptions (in particular assertions, preconditions, postconditions and predicates), to reduce the memory footprint.
3.4. Dynamic Memory Handling¶
The use of dynamic memory (access types, aka pointers) is supported by the GNAT Pro CCG compiler, and will generate calls to the standard C functions malloc() (for memory allocation) and free() (for deallocation). If dynamic memory is used in the Ada sources, then malloc() and possibly free() need to be provided by the C compiler.
3.5. Exception Handling¶
If some runtime checks or assertions are enabled or if there are explicit raise statements in the source, then a call (conditional in case of a runtime check or assertion) to a subprogram with the following C profile is generated:
extern void __gnat_last_chance_handler (const char *file, int line);
This function needs to be provided as part of the user code if checks are enabled or raise statements are used. file if not NULL will represent a C string (nul terminated) with the name of the C file where the exception was raised (this is done via the C __FILE__ macro). The second parameter corresponds to the line number of the exception raise (C __LINE__ macro).
This function can perform e.g. logging activities and is then responsible for stopping the application or restarting it. If this function returns normally, then the execution becomes undefined.
Note that the call is performed via a macro called GNAT_LAST_CHANCE_HANDLER
and defined in standard.h
. If you define GNAT_LAST_CHANCE_HANDLER
before including standard.h
then you can override this macro
and e.g. ignore the extra parameters, for example:
#define GNAT_LAST_CHANCE_HANDLER(FILE,LINE) my_last_chance_handler()
3.6. Enabling/Disabling Runtime Checks¶
By default, runtime checks are enabled when generating C code (except for overflow checks which are not supported).
If you want to suppress generation of runtime checks (e.g. because they have been proven by the SPARK toolset or for efficiency), you can use one of the following options:
-gnatp compiler switch, which disables all runtime checks
specify in a configuration file (named e.g.
config.adc
):pragma Suppress (All_Checks);
In order to enable the use of
config.adc
you need to add in your project file:for Global_Configuration_Pragmas use "config.adc";
you can also selectively use pragma Suppress in source file, or in a configuration file to suppress only some checks, or checks for only certain packages or subprograms, e.g.:
pragma Suppress (Range_Checks); -- suppress range checks only
or in a source file:
procedure Proc1 is pragma Suppress (All_Checks); -- suppress all checks for this procedure begin -- ...
See the GNAT documentation for more details.
3.7. Enabling/Disabling Runtime Assertions¶
By default, assertions (including pre- and postconditions) are disabled in the generated code.
There are several ways to enable assertions in the generated code:
The -gnata compiler switch will enable all assertions.
The pragma Assertion_Policy allows selective enabling/disabling of assertions.
See the GNAT documentation for more details.
3.8. Inserting Arbitrary C Fragments in the Generated Code¶
It is sometimes useful to insert extra code fragments that are compiler
specific, such as #pragma
directives.
To insert such code fragments in the generated C files, you can use the pragma Annotate as follows:
pragma Annotate (CCG, C_Pragma, "insert pragma contents");
which when encountered will generate in the C code, starting on a new line at column 1:
#pragma insert pragma contents
A more general syntax can also be used to insert arbitrary code:
pragma Annotate (CCG, Verbatim, "any valid C code");
which will insert in a new line the given any valid C code
.
Note that this capability should be used with care and the resulting output should be manually reviewed for correctness, GNAT CCG does not make any attempt at verifying the contents of the C code provided, and this may generate invalid or incorrect C code.
GNAT CCG tries to handle and generate C code sequentially from the Ada code, so the order of insertion will in general follow the original Ada source order, but this is not always possible and in particular in the case of forward declarations of types or objects (including private definitions), this order will not be obeyed, and the location of the inserted C code is not guaranteed to be the expected one and should in any case always be manually reviewed.
4. Debugging & Code Generation Strategy¶
4.1. Debugging¶
Support for debugging the generated code comes with several options and associated limitations:
host debugging using a native Ada compiler.
This option provides a powerful debugging experience, but does not allow debugging target-specific issues.
debugging the generated C code using a C debugger.
This option requires no specific toolchain support, and allows debugging target-specific issues, but requires getting familiar with the generated C code. Using the -gnatL switch can help debugging the C code by having the Ada code also available as comments, see Relevant Switches.
debugging the Ada code on the target using a C debugger.
This hybrid solution can be enabled via the -g compiler switch and using the corresponding debug switch for the target C compiler, and the corresponding target C debugger. This switch will generate #line directives in the generated C code, so that messages (errors or warnings) from the C compiler will be redirected to the Ada source code (depending on the C compiler capabilities), and debug information will also point to the Ada code, allowing step-by-step debugging at the Ada source level. This capability depends on the ability of the target C compiler to properly handle #line directives pointing to external non-C files. Some knowledge of the generated C code is also necessary in order to display values of Ada variables properly, in particular using the proper encoding for global variables and subprograms.
4.2. Code Generation Strategy¶
The code generation strategy used by GNAT Pro CCG is to generate two files for each compiled source file:
a
.h
file (named <source basename>.h) containing first a copy ofstandard.h
, then all the needed declarations (types, object and function declarations) corresponding to the packages withed by the unit being compiled, then the declarations corresponding to the unit spec.a
.c
file (named <source basename>.c) which includes the header file and contains all the statements and declarations of the unit body.
Each set of declarations coming from specific files (Ada spec files typically) is surrounded with the following preprocessor directives:
#ifndef <FILE>
#define <FILE>
/* C declarations for <FILE> */
#endif /* <FILE> */
For example, declarations from package1.ads
will have:
#ifndef PACKAGE1_ADS
#define PACKAGE1_ADS
/* C declarations for package1.ads */
#endif /* PACKAGE1_ADS */
Each global entity (variable, constant, subprogram) will get an encoded
name which is fully documented in exp_dbug.ads
(part of the compiler
sources). In a nutshell, entities in package Pack1
will generate a C
symbol called pack1__<entity>
(all lowercase), for example subprogram
Pack1.Subprogram1
will generate a C function called
pack1__subprogram1
.
The compiler will generate C89 code as much as possible for maximum
portability, unless C89 is too restricted in which case C99 constructs will
be used (such as variable size arrays), or in rare cases GCC extensions (a
warning will always be generated when using these extensions).
GNAT CCG also relies on macros defined in standard.h
and that need to
be adapted to the target C compiler. The default macros provided are using GCC
extensions when the C compiler used is GCC, and left empty for other C
compilers.
Finally, in order to support some record representation clauses, CCG will emit
bitfield structs. Since these are not guaranteed to be portable, a warning
is always emitted when generating such bitfields.
4.3. Calling Ada Code from C code¶
In order to call Ada subprograms and use corresponding data structures from C code, you can #include the relevant .h file(s) generated by the compiler.
For example, if you want to include the definitions of package Pack1
contained in pack1.ads
, you need to do:
#include "pack1.h"
See Code Generation Strategy for more details on the contents of these header files.
4.4. Examples of Code Generation¶
If you consider the following package which uses built-in features of Ada such as exponentiation and functions returning an array:
package P is
type Int is private;
type Int_Array is array (1 .. 10) of Int;
function Square (X : Int) return Int;
function Square (X : Int_Array) return Int_Array;
private
type Int is new Integer;
end P;
package body P is
function Square (X : Int) return Int is
begin
return X ** 2;
end Square;
function Square (X : Int_Array) return Int_Array is
Result : Int_Array;
begin
for J in X'Range loop
Result (J) := X (J) ** 2;
end loop;
return Result;
end Square;
end P;
Then the compiler will generate the following C code:
#ifndef P_ADS
#define P_ADS
typedef integer p__TintB;
typedef p__TintB p__int;
typedef p__int p__int_array[10];
extern p__int p__square(p__int x);
extern void p__square__3(p__int_array x, p__int_array RESULT);
#endif /* P_ADS */
#include "p.h"
p__int p__square(p__int x) {
return ((p__int)(x * x));
}
void p__square__3(p__int_array x, p__int_array RESULT) {
p__int_array result;
{
typedef integer p__square__3__T2b;
p__square__3__T2b j;
for (j = 1; j <= 10; j++) {
result[j - 1] = (x)[j - 1] * (x)[j - 1];
}
}
memcpy(RESULT, result, sizeof(p__int_array));
return;
}