Utilisation du compilateur Ada GNAT
Ce document s’applique autant à l’ancien GNAT « 3.15p » qu’au nouveau GNAT GPL. Il est un abrégé du guide d’utilisation pour la plus grande part, et du manuel de référence, pour les « pragmas ». Ce document n’est pas une traduction du document original, mais un abrégé ne retenant que le plus pertinent pour la plupart des usages. Cette reprise se veut plus concise et plus aisée à lire que l’original qui est un bien long document. Un bon nombres de commentaires personnels ont été ajouté.
La version originale, in English, peut être entre autres ( il en existe plusieurs versions ) consultée dans GNAT User Guide et dans GNAT Reference Manual .
Syntaxe d’invocation : « gnatmake [options] fichier1 [fichier-n] [options-de-mode] »
Compile toutes les dépendances ( d’où le nom de gnatmake )
et exécute la liaison des fichiers objet produit. Si l’option « -c » est employée, alors
gnatmake n’effectue pas l’étape de liaison, mais seulement
la compilation. Ceci ne signifie pourtant pas qu’il produit de fichier
objets. Au lieu de fichiers objets, GNAT produit des
fichiers ALI.
Les fichiers ADB
et ADS
produisent donc des fichiers ALI, qui eux même produisent
des fichiers objets par l’intermédiaire de gnatbind,
qui est automatiquement invoqué par gnatmake si la
liaison est requise.
Note inclassable : GNAT interprète l’attribut de protection en écriture des fichiers ALI. Il considère alors que le package ( « paquet » ) correspondant est verrouillé, et ne tiens alors pas compte de la date du fichier source, qui peut même ne pas être présent.
Les options sont présentés sous le format suivant
xxx » de
gnatmake
N.B. les titres résumés sont donnés parce que les noms d’options sont le plus souvent cabalistiques.
-a » de
gnatmake
-f » ), mais indique seulement de tenir compte de
tous les fichiers, et concrètement, la date de tous les fichiers
dont GNAT tiendra compte, restera vérifiée.
-c » de
gnatmake
-l ».
-f » de
gnatmake
-a » ).
-a » et
« -f » de gnatmake
-i » de
gnatmake
-l » de
gnatmake
-M » de
gnatmake
-a », car là aussi les fichiers propres à GNAT
seront ignorés par ce processus, à moins que l’option « -a » ne soit donnée,
si nécessaire.
-q » de
gnatmake
-z » de
gnatmake
-aI{dir} »
de gnatmake
-aL{dir} », qui diffère dans son fonctionnement, et dont
la documentation en dit plus, par contrario.
-aL{dir} »
de gnatmake
-aI{dir} »,
qui diffère dans son fonctionnement ). A la différence
« -aI{dir} », cette
option à pour effet que si un fichier ALI est trouvé,
alors il est utilisé, sans re-compilation. Ceci permet d’utilisé
des fichiers ALI dont on ne posséderait pas les
sources ( d’où le « L », qui signifie que c’est un chemin
pour les librairies, et non pas un chemin pour les sources ).
Malgré cela, si le code source est trouvé dans le chemin, et
pas le fichier ALI, alors le code source est bien
sûre tout de même compilé. Avec cette option, le fichier n’est
pas nécessaire, mais il est tout de même utilisé s’il est trouvé.
Voir aussi l’option « -aO{dir} », dans la même catégorie, mais qui fonctionne
différemment. Notez qu’il est recommandé, si on ne souhaite
pas de re-compilation automatique des fichiers ALI,
de leur donner un attribut de protection en écriture. Cette
option ne devrait donc normalement pas être utilisé. Mais elle
peut être considérée comme plus pratique que la protection en
écriture pour certaines personnes ou pour certains projets,
et elle est de toute manière entièrement disponible pour raison
de compatibilité, et peu donc être utilisé normalement.
-aO{dir} »
de gnatmake
-aL{dir} », dans la même catégorie, mais qui fonctionne
différemment.
-gnatp »
de gnatmake
-I{dir} »
de gnatmake
-aI{dir} »
et « -aO{dir} ».
Fourni donc en fait un chemin de recherche tant pour les sources
Ada que pour les fichiers objets.
-L{dir} »
de gnatmake
-nostdlib »
de gnatmake
-cargs {option} »
de gnatmake
-cargs »
pour chaque option, et il ne devra pas y avoir d’espaces dans
le texte de l’option. Si un ou des espaces sont requis par l’option,
alors il est possible d’obtenir cet effet en utilisant deux
ou plusieurs « -cargs »
qui se suivront. Notez bien l’espace entre « -cargs » et l’option à passer.
-largs {option} »
de gnatmake
-cargs » pour
chaque option, et il ne devra pas y avoir d’espaces dans le
texte de l’option. Si un ou des espaces sont requis par l’option,
alors il est possible d’obtenir cet effet en utilisant deux
ou plusieurs « -cargs »
qui se suivront. Notez bien l’espace entre « -largs » et l’option à passer.
Initialement, la section 8 ne traite que des pragmas de configuration, les autres ont été ajoutées. Les descriptions des pragmas sont issues du manuel de référence, et non pas du guide d’utilisation.
Les pragmas de configuration sont écrites
par le programmeur dans un fichier « gnat.adc »,
qui au moment de la compilation, doit se trouver systématiquement
dans le répertoire depuis lequel est invoqué gnatmake.
Ce fichier est automatiquement utilisé s’il est présent à l’invocation
de gnatmake, mais il n’est pas obligatoire. On peut
spécifier à gnatmake d’ignorer ce fichier, même s’il
est présent, en lui donnant l’option « -gnatA ». Ce fichier est d’abord utile pour faire du code finalisé,
pour réduire la taille finale de l’exécutable ( en dehors de
toute configuration spécifique dans ce sens, les exécutables produits
par gnatmake sont malheureusement plus gros que l’on
ne pourrait l’espérer, de 20% à 50% plus gros qu’un programme Pascal
environ équivalent par exemple ). Il est possible d’utiliser
un autre fichier que « gnat.adc »
ou même en complément de « gnat.adc »
( dans ce cas, « gnat.adc » reste
lut en premier ). Pour cela, on passe à gnatmake
l’option « -gnatec{path} ».
Path désigne le fichier de configuration
à employer, en supplément de « gnat.adc »
qui est toujours utilisé, sauf si on utilise l’option « -gnatA », qui elle- même d’ailleurs n’empêche pas l’utilisation
du fichier spécifié par « -gnatec{path} ». Attention : si gnatmake reçoit
plusieurs options « -gnatec{path} »
à sa ligne de commande, alors il n’utilise que la dernière option,
en ignorant totalement les autres. Donc bien qu’il ne soit syntaxiquement
valide d’en spécifié plusieurs, dans les faits on ne peut en donner
qu’une seule.
Pour la finalisation du code, voir aussi l’abrégé de la section 26 ( considération sur la performance ).
Dans un fichier de configuration par pragmas
( que ce soit « gnat.adc » ou
un autre fichier ), chaque ligne est constituée du mot clé
pragma, suivit du nom de la pragma, puis des éventuels paramètres entre
parenthèses, et d’un point virgule final ( par exemple : « pragma Ada_95; » ).
Les pragmas sont présentées avec une pseudo signature, et son suivies de leurs descriptions.
pragma Ada_83; »
pragma Ada_95; »
pragma Ada_2005; »
Ada_83 » ni « Ada_95 » ; par
défaut GNAT accepte certaines extensions de Ada
2005, mais ni ne les implémente ni ne les reconnaît toutes.
pragma Comment (string); »
pragma Eliminate (*); »
pragmas Eliminate » sont de
préférence produite automatiquement par gnatelim
( il est possible de les écrire manuellement, mais c’est
totalement inefficace, car nombreux oublis nécessairement probables
et important risques d’erreur ). Le paramètre, de format
assez complexe, n’est pas décrit ici, car pour la génération
des « pragmas Eliminate », il est
préférable de passer par gnatelim.
pragma External (*); »
pragma Export » de la spécification de
Ada. Elle est présente pour la compatibilité avec
certains anciens compilateurs Ada 83. Pour plus
d’information, referez-vous au manuel de référence Ada.
pragma Finalize_Storage_Only (Nom); »
pragma Initialize_Scalars; »
Normalize_Scalars »
( voir le manuel de référence Ada ),
mais diffère de celle-ci en deux points. La première différence
est que contrairement à « Normalize_Scalars »
qui est globale est s’applique à toutes les unités ( package et programme principal ),
« Initialize_Scalars » s’applique
localement à chaque unité. En conséquence, il n’y a pas nécessairement
de re-compilation de la runtime. La
deuxième différence est qu’il est possible de choisir n’importe
quelle valeur pour l’initialisation des scalaires. La valeur
est introduite dans le binaire par bind ( référez-vous
donc à la documentation de bind si avez à employer
cette pragma ). Cette pragma est utile à fin de teste. Vous pouvez
ainsi tester le comportement de votre application avec différentes
valeurs initiales pour les scalaires. Si l’application se comporte
de la même manière quelles que soient les valeurs d’initialisation
des scalaires, c’est que votre application est fiable. Dans
le cas contraire, il vous faudra déterminer soigneusement la
cause de ces différences de comportement, et le corriger. Il
y aura également sûrement beaucoup à gagner à effectuer les
mêmes testes en variants l’initialisation des structures plus
complexes, mais ceci devra obligatoirement passer par une modification
du code source de l’application à tester.
pragma Interface (*); »
pragma Import » de la spécification de
Ada. Elle est reconnue pour raison de compatibilité
avec certains anciens compilateurs Ada 83. Pour
plus d’information, vous pouvez vous référer au manuel de référence
Ada.
pragma License (Type_de_Licence); »
Restricted », « Unrestricted », « GPL » et « Modified_GPL ». Cette pragma peut vous aider à éviter de mauvaise surprise.
pragma Link_With; »
pragma Linker_Options » de la spécification
de Ada. Elle est reconnue pour raison de compatibilité
avec certains anciens compilateurs Ada 83. Pour
en savoir plus, referez vous à « Linker_Options »
dans le manuel de référence Ada.
pragma Linker_Alias (Nom_Entité, Alias); »
pragma Linker_Section (Nom_Entité, Nom_Section); »
pragma No_Run_Time; »
pragma Restricted_Run_Time ».
pragma Pure_Function (Nom_Fonction); »
pragma Ravenscar; »
pragma Restricted_Run_Time; »
pragma No_Run_Time ».
pragma Unreferenced (Nom_Entité); »
pragma Validity_Checks (string | All_Checks | On | Off); »
gnat.adc »
ou dans tout autre fichier de configuration de pragma désigné avec l’option de ligne de commande appropriée
( en l’occurrence, l’option « -gnatec{path} » ). La pragma
peut également être employée en tous points du code. Si l’argument
chaîne est employé pour cette pragma,
alors son format est le même que celui de l’option de ligne
de commande « -gnatV »
( pour plus de détail sur cette option, référez-vous au
guide d’utilisation de GNAT ).
Les pragmas qui suivent sont issues de la spécification du langage Ada, et sont reconnues par GNAT. Pour en savoir plus, referez-vous au manuel de référence de Ada.
C_Pass_By_Copy »
Component_Alignment »
Discard_Names »
Elaboration_Checks »
Eliminate »
Extend_System »
Extensions_Allowed »
External_Name_Casing »
Float_Representation »
Initialize_Scalars »
License »
Locking_Policy »
Long_Float »
No_Run_Time »
Normalize_Scalars »
Polling »
Propagate_Exceptions »
Queuing_Policy »
Ravenscar »
Restricted_Run_Time »
Restrictions »
Reviewable »
Source_File_Name »
Style_Checks »
Suppress »
Task_Dispatching_Policy »
Unsuppress »
Use_VADS_Size »
Volatile »
Warnings »
Validity_Checks »
Cette section fera d’une page à elle seule ( à venir pour une date indéterminée ). C’est une toute autre manière d’utiliser GNAT, en s’épargnant le passage options sur la ligne de commande. Plus pratique, c’est vrai, mais l’utilisation directe de la ligne de commande est bien pratique elle aussi pour les petites compilations de petits programme… ou encore si on préfère utiliser une autre méthode de gestion avec un programme fait maison, qui se charge d’appeler gnatmake en générant automatiquement les options… ce qui peut éventuellement permettre de répondre à des exigences spécifiques. Sachez tout de même que ce service est fourni par GNAT.
Cette utilitaire fourni par la suite GNAT, vous facilite la tâche fastidieuse d’écrire un corps initial correspond à une spécification.
Gnatstub crée un corps ( body ) à partir d’un fichier de spécification ( spec. ). On peut en pratique, travailler de deux manières. La première manière et de créer un body, pour développement incrémental, et créer une spécification à partir du body lorsque celui-ci est bien avancé. On peut travailler à l’opposé, dans l’autre sens également, et créer une spécification et créer le body seulement ensuite.
Gnatstub permet justement de créer automatiquement
un body initial à partir d’un fichier
de spécification. Il ne reste alors plus qu’à éditer le body pour en écrire l’implémentation effective.
Le body est néanmoins compilable, même
sans avoir à l’éditer : les corps de fonctions et procédures sont
initialisées avec une instruction « null »,
ce qui permet d’avoir un body valide,
même sans l’éditer au préalable. Il sera bien sûre tout de même
nécessaire d’éditer le fichier body si
on souhaite avoir un body véritablement
concret.
Syntaxe d’invocation : « gnatstub [options] fichier [répertoire] »
-f » : remplace
le body même s’il existe déjà et même
s’il est à jour.
-hs » : recopie
le commentaire d’en-tête depuis le fichier de spécification
vers le corps.
-hd » : crée un
commentaire d’en-tête vide.
-q » : mode musaraigne
( cherchez le point commun entre une carpe et une musaraigne ).
-v » : mode verbeux
( je ne vous parle même pas du caniche de la voisine du
dessous ).
Cette section fait l’objet d’une page à elle toute seule : Réduire la taille des exécutables produits par GNAT .
Il n’est pas possible d’utiliser « GetLastError »
et « SetLastError » avec un programme
qui emploie des tâches, des enregistrements protégés ( mécanisme
de synchronisation ) ou des exceptions ( c’est-à-dire
pratiquement 100% des programmes Ada ). GNAT
utilise ces deux procédures de l’API Windows, et sa runtime
peut donc modifier la valeur de l’indicateur de code d’erreur de
l’API Windows dans le dos de votre application.
On pourra utiliser « GetLastError »
et « SetLastError » si on est dans
aucun de ces cas de contre-indication, mais même alors, le résultat
n’est pourtant même pas garanti. En bref, même si c’est ennuyeux,
il faut apprendre à se débrouiller sans ou alors appelé « GetLastError » immédiatement après l’appel
à chaque routines de l’API pour lesquelles cela est
souhaité, et de le faire dans une portion non interruptible du programme.
Les options de ligne de commande spécifiques à Windows sont les suivantes…
-mwindows » : crée
un exécutable avec le sous système graphique de Windows.
-mconsole » : cette
option n’existe pas, mais on obtient ce résultat en ne spécifiant
pas « -mwindows »
( le mode console est le mode par défaut, et à défaut d’indication
du contraire ).
La question de la création de l’utilisation et création de DLL Windows fera l’objet d’une page à elle toute seule ( à venir ).
Pour produire du bon code bien finalisé ( et surtout réduire la taille finale de l’exécutable, qui est par défaut un peu trop importante avec GNAT ).
Pour la finalisation du code, voir aussi la section 8 ( les pragmas de configuration ).
Les options de ligne de commande spécifiques à l’optimisation, sont les suivantes…
-O0 » : aucune
d’optimisation ( état par défaut ).
-O1 » : optimisation
moyenne.
-O2 » : optimisation
élevée.
-O3 » : optimisation
élevée avec inlining ( instanciation
en lieu et place de l’invocation ) des petites fonctions
de procédures.
L’inlining peut être produit également
avec « -O1 » et « -O2 » pour les procédures
ou fonctions explicitement déclarées inline
dans le code. Notez bien à ce sujet, que même si une procédure est
explicitement déclarée inline dans le
code, elle ne sera pourtant pas compilé inline
si le niveau d’optimisation n’est pas au moins égal à 1. Avec « -O0 », une procédure est
toujours compilée et invoquée comme un sous programme ordinaire,
même si elle est déclarée inline dans
le code ( ceci correspond d’ailleurs au comportement qu’avais
les compilateurs Borland Turbo Pascal et Borland
Turbo C++ ).
Comprenez bien que « -O3 »
ne donne pas nécessairement les meilleurs résultats, et que le meilleur
résultat dépend de l’environnement global ( matériel et logiciel ),
et de l’application elle-même. Le mieux est donc de tester les trois
niveaux d’optimisation de 1 à 3, et de sélectionner le meilleur
résultat parmi les trois.
Un moyen simple et assez fiable de déterminer le meilleur résultat, consiste en voir la taille du code produit. En effet, bien que l’on argue parfois qu’une augmentation de la taille du code binaire soit souvent le prix à payer pour un code plus rapide, ceci est loin d’être toujours le cas, et ne peut surtout être maîtriser que par les algorithmes de l’application, c’est-à-dire au niveau conception et développement, et non pas au niveau de l’optimisation automatique de code par un compilateur. On constate souvent avec GCC et G++ parce exemple, qu’avec les niveaux d’optimisation élevés, non seulement on augmente de beaucoup la taille du code pour un gain de vitesse d’exécution souvent dérisoire, qui plus est, il arrive même que bien au contraire, le code ainsi optimisé soit en fait plus lent qu’avec un niveau d’optimisation inférieur ( un code plus lent, et qui est encore plus volumineux est vraiment sans intérêt ).
Un autre fait concret qui justifie de s’intéresser d’abord à la taille de l’exécutable, est qu’un code de petite taille, contient des sections souvent de petites taille également, qui tiendront mieux dans le cache des processeurs. Un contre exemple pour vous faire comprendre : le développement en ligne d’une boucle à nombre d’itérations statiquement déterminé n’est pas toujours une bonne idée. En effet, sous forme de boucle, la totalité du code peut assez aisément se trouver entièrement dans le cache du processeur, ceci pendant toute la durée d’exécution de toutes les itérations de la boucle. Au contraire, quand la boucle est développée, le processeur doit faire de plus fréquentes requêtes mémoire pour récupérer la suite du code. Le résultat est un code nettement plus gros et aussi tristement nettement plus lent ( phénomène plusieurs fois constaté ).
Et c’est aussi en règle générale que l’on constate que les binaire les plus petits sont aussi souvent les plus rapides. Tout ceci pour vous dire que pour choisir le meilleur résultat parmi les 3 niveaux d’optimisation disponible, le critère à considérer en priorité est celui de la taille du binaire produit… seulement ensuite vienne les testes effectifs de la vitesse d’exécution sur le plus petit binaire ( pour s’assurer au moins de la conformité avec les attentes ).
Par ailleurs et pour finir avec l’optimisation : Il est bien connu que certains compilateurs introduisent des bugs dans les codes optimisés. GCC semble avoir été assez bien testé à ce niveau, et il faut savoir que même certains rare bugs, n’apparaissent que dans le code non- optimisé. En conséquence, vouloir ne pas optimiser au prétexte que cela permettrait d’obtenir un code plus fiable, serait une erreur. Attention : la fiabilité du code optimisé dépend malgré tout de la version de GCC employé ( certaines versions sont tout de même bien connues pour êtres très buggés ).
À propos d’un usage spécifique de certaines options et techniques : Réduire la taille des exécutables produits par GNAT