Section courante

A propos

Section administrative du site

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 :

Exemples :

  1. Unit TestVars;
  2.  
  3. INTERFACE
  4.  
  5. Const
  6.  PublicTypeDConst:Integer=0;
  7.  
  8. Var
  9.  PublicVar:Integer;
  10.  
  11. IMPLEMENTATION
  12.  
  13. Const
  14.  PrivateTypeDConst:Integer=1;
  15.  
  16. Var
  17.  PrivateVar:Integer;
  18.  
  19. END.

Se traduira par le code assembleur suivant pour l'assembleur GNU Assembler :

  1. .file "testvars.pas"
  2. .text
  3. .data
  4. #[6] publictypedconst : integer = 0;        
  5. .globl TC__TESTVARS$$_PUBLICTYPEDCONST
  6. TC__TESTVARS$$_PUBLICTYPEDCONST:
  7. .short 0
  8. #[12] privatetypedconst : integer = 1;
  9. TC__TESTVARS$$_PRIVATETYPEDCONST:
  10. .short 1
  11. .bss
  12. #[8] publicvar : integer;
  13. .comm U_TESTVARS_PUBLICVAR,2
  14. #[14] privatevar : integer;
  15. .lcomm _PRIVATEVAR,2

Noms mutilés pour les blocs de code

Les règles pour les noms mutilés des routines sont les suivantes :

Les constructions suivantes :

  1. Unit TestMan;
  2.  
  3. INTERFACE
  4.  
  5. Type
  6.  MyObject=Object
  7.   Constructor Init;
  8.   Procedure MyMethod;
  9.  End;
  10.  
  11. IMPLEMENTATION
  12.  
  13. Constructor MyObject.Init;Begin
  14. End;
  15.  
  16. Procedure MyObject.MyMethod;Begin
  17. End;
  18.  
  19. Function MyFunc:Pointer;Begin
  20. End;
  21.  
  22. Procedure MyProcedure(Var x:Integer; y:LongInt; z:PChar);Begin
  23. End;
  24.  
  25. END.

se traduira par le fichier assembleur suivant pour la cible Intel 80x86 :

  1. .file "testman.pas"
  2. .text
  3. .balign 16
  4. .globl _TESTMAN$$_$$_MYOBJECT_$$_INIT
  5. _TESTMAN$$_$$_MYOBJECT_$$_INIT:
  6. pushl %ebp
  7. movl %esp,%ebp
  8. subl $4,%esp
  9. movl $0,%edi
  10. call FPC_HELP_CONSTRUCTOR
  11. jz .L5
  12. jmp .L7
  13. .L5:
  14. movl 12(%ebp),%esi
  15. movl $0,%edi
  16. call FPC_HELP_FAIL
  17. .L7:
  18. movl %esi,%eax
  19. testl %esi,%esi
  20. leave
  21. ret $8
  22. .balign 16
  23. .globl _TESTMAN$$_$$_MYOBJECT_$$_MYMETHOD
  24. _TESTMAN$$_$$_MYOBJECT_$$_MYMETHOD:
  25. pushl %ebp
  26. movl %esp,%ebp
  27. leave
  28. ret $4
  29. .balign 16
  30. _TESTMAN$$_MYFUNC:
  31. pushl %ebp
  32. movl %esp,%ebp
  33. subl $4,%esp
  34. movl -4(%ebp),%eax
  35. leave
  36. ret
  37. .balign 16
  38. _TESTMAN$$_MYPROCEDURE$INTEGER$LONGINT$PCHAR:
  39. pushl %ebp
  40. movl %esp,%ebp
  41. leave
  42. 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 :

  1. Procedure AliasedProc; alias : 'AliasName';

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 :

  1. {$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 :

  1. 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 :

  1. Destructor Done(_vmt:Pointer; _self:Pointer ...);

_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 :

  1. 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.

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 :

  1. Destructor Done(_self:Pointer; Flag:LongInt ...);

_self est l'adresse de l'instance de l'objet.

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 :

  1. pushl %ebp
  2. movl %esp,%ebp

La séquence de sortie générée pour la procédure et les fonctions se présente comme suit :

  1. leave
  2. ret $xx

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 :

  1. move.l a6,-(sp)
  2. move.l sp,a6

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) :

  1. unlk a6
  2. rtd #xx

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 :

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.



Dernière mise à jour : Mercredi, le 28 juin 2023