Problèmes de code
Cette page donne des informations détaillées sur le code généré par Free Pascal. Il peut être utile d'écrire des fichiers objets externes étant liés aux blocs de code créés par Free Pascal.
Les conventions de registre
Le compilateur a différentes conventions de registre, selon le processeur cible utilisé ; certains registres ont des utilisations spécifiques lors de la génération de code. La section suivante décrit les noms génériques des registres plate-forme par plate-forme. Il indique également quels registres sont utilisés comme registres de travail et lesquels peuvent être librement utilisés dans les blocs assembleur.
Registre d'accumulateur
Le registre d'accumulateur est un registre matériel d'entiers d'au moins 32 bits et est utilisé pour renvoyer les résultats des appels de fonction renvoyant des valeurs intégrales.
Registre d'accumulateur 64 bits
Le registre 64 bits de l'accumulateur est utilisé dans les environnements 32 bits et est défini comme le groupe de registres étant utilisé lors du retour des résultats intégraux 64 bits dans les appels de fonction. Il s'agit d'une paire de registres.
Registre de résultat flottant
Ce registre est utilisé pour renvoyer des valeurs à virgule flottante à partir de fonctions.
Registre self
Le registre self contient un pointeur vers l'objet ou la classe réelle. Ce registre donne accès aux données de l'objet ou de la classe, et au pointeur VMT de cet objet ou de cette classe.
Registre de pointeur de cadre
Le registre de pointeur de trame est utilisé pour accéder aux paramètres des sous-programmes, ainsi qu'aux variables locales. Les références aux paramètres poussés et aux variables locales sont construites à l'aide du pointeur de cadre.
Registre de pointeur de pile
Le pointeur de pile est utilisé pour donner l'adresse de la zone de pile, où les variables locales et les paramètres des sous-programmes sont entreposés.
Registres de travail
Les registres de travail sont ceux pouvant être utilisés dans des blocs assembleur ou dans des fichiers objets externes sans nécessiter de sauvegarde avant utilisation.
Cartographie des registres du processeur
Cela indique quels registres sont utilisés à quelles fins sur chacun des microprocesseurs pris en charge par Free Pascal. Il indique également quels registres peuvent être utilisés comme registres de travail.
Version Intel 80x86 :
Nom de registre générique | Nom du registre du microprocesseur |
---|---|
Accumulateur | EAX |
Accumulateur (64 bits) haut / bas | EDX:EAX |
Résultat flottant | FP(0) |
Self | ESI |
Pointeur de cadre | EBP |
Pointeur de pile | ESP |
Registres de travail | N/A |
Type OS_128 (entier 128 bits) | RDX:RAX |
Version Motorola 680x0 :
Nom de registre générique | Nom du registre du microprocesseur |
---|---|
Accumulateur | D0 |
Accumulateur (64 bits) haut / bas | D0:D1 |
Résultat flottant | FP0 |
Self | A5 |
Pointeur de cadre | A6 |
Pointeur de pile | A7 |
Registres de travail | D0, D1, A0, A1, FP0, FP1 |
Nom mutilé
Contrairement à la plupart des compilateurs et assembleurs C, toutes les étiquettes générées pour les variables pascales et les routines ont des noms mutilés. Ceci est fait pour que le compilateur puisse faire une vérification de type plus forte lors de l'analyse du code Pascal. Il permet également la surcharge de fonctions et de procédures.
Noms mutilés pour les blocs de données
Les règles pour les noms mutilés pour les variables et les constantes typées sont les suivantes :
- Tous les noms de variables sont convertis en majuscules
- Les variables du programme principal ou privées d'une unité ont un trait de soulignement (_) devant leur nom.
- Les constantes tapées dans le programme principal ont un TC__ ajouté à leur nom.
- Les variables publiques d'une unité sont précédées de leur nom d'unité : U_UNITNAME_.
- Les constantes typées publiques et privées d'une unité ont leur nom d'unité en préfixe :TC__UNITNAME$$
- N'utilisez pas de noms publics commençant par FPC_, car FPC les utilise pour les routines et constantes système internes.
Exemples :
Se traduira par le code assembleur suivant pour l'assembleur GNU Assembler :
- .file "testvars.pas"
- .text
- .data
- #[6] publictypedconst : integer = 0;
- .globl TC__TESTVARS$$_PUBLICTYPEDCONST
- TC__TESTVARS$$_PUBLICTYPEDCONST:
- .short 0
- #[12] privatetypedconst : integer = 1;
- TC__TESTVARS$$_PRIVATETYPEDCONST:
- .short 1
- .bss
- #[8] publicvar : integer;
- .comm U_TESTVARS_PUBLICVAR,2
- #[14] privatevar : integer;
- .lcomm _PRIVATEVAR,2
Noms mutilés pour les blocs de code
Les règles pour les noms mutilés des routines sont les suivantes :
- Tous les noms de routine sont convertis en majuscules.
- Les routines d'une unité ont leur nom d'unité en préfixe : _UNITNAME$$_
- Toutes les routines du programme principal sont précédées d'un _.
- Tous les paramètres d'une routine sont mutilés en utilisant le type du paramètre (en majuscule) précédé du caractère $. Ceci est fait dans l'ordre de gauche à droite pour chaque paramètre de la routine.
- Les objets et les classes utilisent un mangling spécial : le type de classe ou le type d'objet est donné dans le nom mangled ; Le nom mutilé est le suivant : _$$_TYPEDECL_$$ éventuellement précédé du nom mutilé de l'unité et se terminant par le nom de la méthode.
- N'utilisez pas de noms publics commençant par FPC_, car FPC les utilise pour les routines et constantes système internes.
Les constructions suivantes :
se traduira par le fichier assembleur suivant pour la cible Intel 80x86 :
- .file "testman.pas"
- .text
- .balign 16
- .globl _TESTMAN$$_$$_MYOBJECT_$$_INIT
- _TESTMAN$$_$$_MYOBJECT_$$_INIT:
- pushl %ebp
- movl %esp,%ebp
- subl $4,%esp
- movl $0,%edi
- call FPC_HELP_CONSTRUCTOR
- jz .L5
- jmp .L7
- .L5:
- movl 12(%ebp),%esi
- movl $0,%edi
- call FPC_HELP_FAIL
- .L7:
- movl %esi,%eax
- testl %esi,%esi
- leave
- ret $8
- .balign 16
- .globl _TESTMAN$$_$$_MYOBJECT_$$_MYMETHOD
- _TESTMAN$$_$$_MYOBJECT_$$_MYMETHOD:
- pushl %ebp
- movl %esp,%ebp
- leave
- ret $4
- .balign 16
- _TESTMAN$$_MYFUNC:
- pushl %ebp
- movl %esp,%ebp
- subl $4,%esp
- movl -4(%ebp),%eax
- leave
- ret
- .balign 16
- _TESTMAN$$_MYPROCEDURE$INTEGER$LONGINT$PCHAR:
- pushl %ebp
- movl %esp,%ebp
- leave
- ret $12
Modification des noms mutilés
Pour rendre les symboles accessibles de l'extérieur, il est possible de donner des surnoms aux noms mutilés, ou de changer directement le nom mutilé. Deux modificateurs peuvent être utilisés :
Modificateur | Description |
---|---|
public | Pour une fonction ayant un modificateur public, le nom mutilé sera le nom exactement tel qu'il est déclaré. |
alias | Le modificateur d'alias peut être utilisé pour attribuer une deuxième étiquette d'assembleur à votre fonction. Ce libellé porte le même nom que le nom d'alias que
vous avez déclaré. Cela ne modifie pas les conventions d'appel de la fonction. En d'autres termes, le modificateur d'alias vous permet de spécifier un autre nom (un surnom)
pour votre fonction ou procédure. Le prototype d'une fonction ou d'une procédure aliasée est le suivant :
La procédure AliasedProc sera également appelée AliasName. Attention, le nom que vous spécifiez est sensible à la casse (comme C l'est). |
De plus, la section exports d'une bibliothèque sert aussi à déclarer les noms qui seront exportés par la bibliothèque partagée. Les noms dans la section exports sont sensibles à la casse (alors que la déclaration réelle de la routine ne l'est pas).
Mécanisme d'appel
Par défaut, le mécanisme d'appel que le compilateur utilise sur I386 est le registre, c'est-à-dire que le compilateur essaiera de passer autant de paramètres que possible en les entreposant dans un registre libre. Tous les registres ne sont pas utilisés, car certains registres ont une signification particulière, mais cela dépend du microprocesseur.
Les résultats de la fonction sont renvoyés dans l'accumulateur (premier registre), s'ils tiennent dans le registre. Les appels de méthode (à partir d'objets ou de classes) ont un paramètre invisible supplémentaire étant self.
Lorsque la procédure ou la fonction se termine, elle efface la pile.
D'autres méthodes d'appel sont disponibles pour la liaison avec des fichiers d'objets externes et des bibliothèques, celles-ci sont résumées dans le tableau suivant :
Modificateur | Ordre pour empiler | Pile nettoyée par | Alignement |
---|---|---|---|
aucun | De gauche à droite | Appelé | Par défaut |
register | De gauche à droite | Appelé | Par défaut |
cdecl | De droite à gauche | Appelé | Alignement du GCC |
interrupt | De droite à gauche | Appelé | Par défaut |
pascal | De gauche à droite | Appelé | Par défaut |
safecall | De droite à gauche | Appelé | Par défaut |
stdcall | De droite à gauche | Appelé | Alignement du GCC |
oldfpccall | De droite à gauche | Appelé | Par défaut |
La première colonne répertorie le modificateur que vous spécifiez pour une déclaration de procédure. Le second répertorie l'ordre dans lequel les paramètres sont poussés sur la pile. La troisième colonne précise qui est responsable du nettoyage de la pile : l'appelant ou la fonction appelée. La colonne d'alignement indique l'alignement des paramètres envoyés à la zone de pile.
Les sous-programmes modifieront un certain nombre de registres (les registres volatils). La liste des registres modifiés dépend fortement du microprocesseur, de la convention d'appel et de l'ABI de la plate-forme cible.
Notez que la convention d'appel oldfpccall est égale à la convention d'appel par défaut sur les microprocesseurs autres que Intel 32 bits 386 ou supérieur.
Depuis la version 2.0 (en fait, dans 1.9.x quelque part), le modificateur de registre est la convention d'appel par défaut, avant cela, c'était la convention oldfpccall.
La convention d'appel par défaut, c'est-à-dire la convention d'appel utilisée lorsqu'aucune n'est spécifiée explicitement, peut être définie à l'aide de la directive {$calling}. La convention d'appel par défaut pour la plate-forme actuelle peut être spécifiée avec :
- {$CALLING DEFAULT}
Remarque : Le modificateur popstack n'est plus supporté depuis la version 2.0, mais a été renommé en oldfpccall. Le modificateur saveregisters ne peut plus être utilisé.
Remarque : Il existe plus de conventions d'appel que celles répertoriées ici, veuillez consulter {$calling}.
Procédure et fonctions imbriquées
Lorsqu'une routine est déclarée dans le cadre d'une procédure ou d'une fonction, on dit qu'elle est imbriquée. Dans ce cas, un paramètre invisible supplémentaire est passé à la routine imbriquée. Ce paramètre supplémentaire est l'adresse du pointeur de trame de la routine parente. Cela permet à la routine imbriquée d'accéder aux variables locales et aux paramètres de la routine appelante.
Le cadre de pile résultant après l'exécution du code d'entrée d'une procédure imbriquée simple est indiqué dans le tableau :
Déplacement du pointeur de cadre | Ce qui est entreposé |
---|---|
+x | Paramètres |
+8 | Pointeur de cadre de la routine parent |
+4 | Adresse de retour |
+0 | Pointeur de cadre enregistré |
Appels constructeur et destructeur
Le constructeur et les destructeurs ont des paramètres invisibles spéciaux qui leur sont transmis. Ces paramètres invisibles sont utilisés en interne pour instancier les objets et les classes.
Objets
La déclaration invisible réelle d'un constructeur d'objet est la suivante :
- Constructor Init(_vmt:Pointer; _self:Pointer ...);
Où _vmt est un pointeur vers la table des méthodes virtuelles pour cet objet. Cette valeur est NIL si un constructeur est appelé dans l'instance d'objet (comme l'appel d'un constructeur hérité).
_self vaut soit NIL si l'instance doit être allouée dynamiquement (l'objet est déclaré comme pointeur), soit l'adresse de l'instance de l'objet si l'objet est déclaré comme un objet normal (entreposé dans la zone de données) ou si l'instance de l'objet a déjà été alloué.
L'instance allouée (si allouée via new) (self) est retournée dans l'accumulateur.
La déclaration d'un destructeur est la suivante :
- Destructor Done(_vmt:Pointer; _self:Pointer ...);
Où _vmt est un pointeur vers la table des méthodes virtuelles pour cet objet. Cette valeur est NIL si un destructeur est appelé dans l'instance d'objet (comme l'appel d'un constructeur hérité), ou si l'instance d'objet est une variable et non un pointeur.
_self est l'adresse de l'instance de l'objet.
Les classes
La déclaration invisible réelle d'un constructeur de classe est la suivante :
- Constructor Init(_vmt:Pointer; Flag:LongInt; ..);
_vmt vaut NIL s'il est appelé depuis l'instance ou s'il appelle un constructeur hérité, sinon il pointe vers l'adresse de la table des méthodes virtuelles.
Où flag vaut zéro si le constructeur est appelé dans l'instance de l'objet ou avec un qualificateur d'instance, sinon ce flag vaut un.
L'instance allouée (self) est retournée dans l'accumulateur. La déclaration d'un destructeur est la suivante :
- Destructor Done(_self:Pointer; Flag:LongInt ...);
_self est l'adresse de l'instance de l'objet.
Où flag vaut zéro si le destructeur est appelé dans l'instance de l'objet ou avec un qualificateur d'instance, sinon ce flag vaut un.
Code d'entrée et de sortie
Chaque procédure et fonction Pascal commence et se termine par un code épilogue et prologue standard.
Prologue/épilogue de routine standard Intel 80x86
Le code d'entrée standard pour les procédures et les fonctions est le suivant sur l'architecture 80x86 :
- pushl %ebp
- movl %esp,%ebp
La séquence de sortie générée pour la procédure et les fonctions se présente comme suit :
Où xx est la taille totale des paramètres poussés.
Prologue / épilogue de routine standard Motorola 680x0
Le code d'entrée standard pour les procédures et les fonctions est le suivant sur l'architecture 680x0 :
La séquence de sortie générée pour la procédure et les fonctions se présente comme suit (dans le mode processeur par défaut) :
Où xx est la taille totale des paramètres poussés.
Passage de paramètre
Lorsqu'une fonction ou une procédure est appelée, les opérations suivantes sont effectuées par le compilateur :
- S'il y a des paramètres à passer à la procédure, ils sont entreposés dans des registres bien connus, et s'il y a plus de paramètres que de registres libres, ils sont poussés de gauche à droite sur la pile.
- Si une fonction est appelée et renvoie une variable de type String, Set, Record, Object ou Array, une adresse dans laquelle entreposer le résultat de la fonction est également transmise à la procédure.
- Si la procédure ou la fonction appelée est une méthode objet, le pointeur vers self est passé à la procédure.
- Si la procédure ou la fonction est imbriquée dans une autre fonction ou procédure, le pointeur de cadre de la procédure parente est transmis à la pile.
- L'adresse de retour est poussée sur la pile (Ceci est fait automatiquement par l'instruction qui appelle le sous-programme).
Le cadre de pile résultant à l'entrée ressemble au tableau :
Déplacement | Qu'est-ce qui est entreposé | Optionnel ? |
---|---|---|
+x | Paramètres supplémentaires | Oui |
+12 | Résultat de la fonction | Oui |
+8 | Self | Oui |
+4 | Adresse de retour | Non |
+0 | Pointeur de cadre de la procédure parent | Oui |
Alignement des paramètres
Chaque paramètre passé à une routine est garanti pour décrémenter le pointeur de pile d'un certain montant minimum. Ce comportement varie d'un système d'exploitation à l'autre. Par exemple, passer un octet comme paramètre de valeur à une routine pourrait décrémenter le pointeur de pile de 1, 2, 4 ou même 8 octets selon le système d'exploitation et le processeur cibles.
Par exemple, sur FreeBSD, tous les paramètres passés à une routine garantissent une diminution minimale de la pile de quatre octets par paramètre, même si le paramètre prend en fait moins de 4 octets pour être stocké sur la pile (comme passer un paramètre de valeur d'octet à la pile).
Limites de la pile
Certains processeurs ont des limitations sur la taille des paramètres et des variables locales dans les routines. Ceci est indiqué dans le tableau :
Microprocesseur | Paramètres | Variables locales |
---|---|---|
Intel 80x86 (tous) | 64 Ko | Sans limites |
Motorola 68020 (par défaut) | 32 Ko | Sans limites |
Motorola 68000 | 32 Ko | 32 Ko |
De plus, le compilateur m68k, en mode 68000, limite la taille des éléments de données à 32 Ko (tableaux, enregistrements, objets,...). Cette restriction n'existe pas en mode 68020.