Sign in Join
Wiki copytemporary

The register allocator/fr: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11Retour au contenu [[FPC internals/fr|FPC internals]]
22
33## Introduction
44
55L'unité principale pour l'allocation de registre est `rgobj.pas`. La classe principale pour l'allocation de registre `TRgObj` qui se trouve dans `rgobj.pas`.
66
77L'allocateur de registre fournit des registres imaginaires pour les instructions en assembleur pendant la génération de code. Ensuite il calcule les registres réels pour remplacer les imaginaires.
88
99L'allocateur de registre de FPC utilise la coloration de registre pour déterminer les registres réels. Il utilise aussi une technique de débordement (*spilling*) de registre - quand il n'y a pas assez de registres, il utilise la mémoire.
1010
1111## Comment utiliser l'allocateur de registre
1212
1313Ce sujet décrit comment utiliser l'allocateur de registre pendant la génération de code. Décrit comme une boîte noire avec des méthodes publiques que vous pouvez appeler pour faire le travail.
1414
1515### Création de l'allocateur de registre
1616
1717La première étape consiste à créer l'allocateur de registre avnt de pouvoir utiliser ses fonctionnalités.
1818
1919Le générateur de code de bas niveau créée plusieurs instances de la classe `TRgObj`. Normalement, cela se produit dans :
2020
21-`tcg.init_register_allocators`
21+```text
22+tcg.init_register_allocators
23+```
2224
2325Et les instances sont normalement libérées dans :
2426
25-`tcg.done_register_allocators `
27+```text
28+tcg.done_register_allocators
29+```
2630
2731Chaque instance `TRgObj` alloue des registres d'un certain type. Par exemple, un type de registre définit les registres entiers. Un autre définit les registres en virgule flottante (FPU). C'est pourquoi nous avons quelques instances `TRgObj`, une pour chaque type de registre que la CPU supporte.
2832
2933Ils sont créés quand la génération de code d'une routine spécifique commence. Le générateur de code travaille au niveau sous-routines. L'allocateur de registre alloue aussi les registrer pour une méthode, procédure ou fonction spécifique.
3034
3135### Utilisation des registres dans la génération de code
3236
3337Pour allouer un registre, utilisez la méthode suivante :
3438
35-`TRgObj.getRegister`
39+```text
40+TRgObj.getRegister
41+```
3642
3743à partir de l'instance appropriée d'allocateur de registre (en fonction du type de registre) pour obtenir le registre d'une instruction assembleur. De cette façon, un registre **imaginaire** est alloué et peut être utilisé après cela dans une instruction d'assembleur spécifique.
3844
3945Le générateur de code l'appelle dans les fonctions suivantes :
4046
41-`tcg.GetIntRegister`\
42-`tcg.GetAddressRegister`\
43-`tcg.GetFPURegister`\
44-`tcg.GetMMRegister`
47+```text
48+tcg.GetIntRegister
49+tcg.GetAddressRegister
50+tcg.GetFPURegister
51+tcg.GetMMRegister
52+```
4553
4654En outre, les méthodes suivantes :
4755
48-`TRgObj.getCpuRegister`\
49-`TRgObj.ungetCpuRegister`\
50-`TRgObj.allocCpuRegisters`\
51-`TRgObj.deallocCpuRegisters`
56+```text
57+TRgObj.getCpuRegister
58+TRgObj.ungetCpuRegister
59+TRgObj.allocCpuRegisters
60+TRgObj.deallocCpuRegisters
61+```
5262
5363allouent ou libèrent un ou plusieurs registres **réels** (pas les imaginaires !). Ceci est généralement utilisé pour les instructions qui demandent un registre spécifique (par exemple SHL/SHR/SAR on x86, qui utilisent toujours ECX). C'est aussi utilisé quand on fait un appel de fonction pour indiquer que certains registres (ce qui dépend de la convention d'appel) peuvent être détruits par l'appel de fonction.
5464
5565Après l'allocation du registre, nous pouvons l'utiliser dans des insrtructions assembleur. Pour chaque instruction qu'il génère, le générateur de code notifie l'allocateur de registre. Il passe l'instruction, ainsi que le registre imaginaire comme paramètres à la méthode suivante.
5666
57-`         TRgObj.add_reg_instruction(instr, r, cg.executionweight);`
67+```text
68+ TRgObj.add_reg_instruction(instr, r, cg.executionweight);
69+```
5870
5971Mais pour l'instruction MOV, une méthode spécifique est utilisée
6072
61-`         TRgObj.add_move_instruction(instr:Taicpu);`
73+```text
74+ TRgObj.add_move_instruction(instr:Taicpu);
75+```
6276
6377Un poids est également fourni pour déterminer quel registre doit être débordé (*spilled*) et lequel il est préférable d'utiliser un vrai registre CPU.
6478
6579### Générer les registres réels
6680
6781A la fin, quand tous les instruction assembleur sont générées, nous appelons
6882
69-`       do_register_allocation(list: TAsmList; headerTai: TAi)`
83+```text
84+ do_register_allocation(list: TAsmList; headerTai: TAi)
85+```
7086
7187Il calcule les registres réels pour les imaginaires.
7288
7389Des instructions peuvent utiliser seulement certain types de registres. Ainsi, l'allocateur de registre ne peut pas choisir tous les registres CPU. Pour ces instructions, nous notifions l'allocateur de registre en appelant la méthode
7490
75-`TRgObj.add_Edge`
91+```text
92+TRgObj.add_Edge
93+```
7694
7795Ces interférences sont construites pendant l'allocation de registre. Chaque descendant qui veut ajouter des interférences surcharge la méthode
7896
7997``` pascal
8098procedure TRgObj.add_cpu_interferences(p: TAi); virtual;
8199```
82100
83101Nous avons des descendants de `TRgObj` pour les différents types de processeurs. Ils appellent `add_edge` dans `add_cpu_interferences` selon l'architecture CPU particulière. C'est utilisé pour les processeurs ARM, x86 etc.
84102
85103Dans la méthode `tcgprocinfo.generate_code`, qui se trouve dans l'unité `psub.pas`, est implémentée la création d'allocateurs de registre, l'obtention des registres, la génération de registres réels. Cette méthode occupe une place centrale dans la génération de code qui l'allocation de registre. Dans celle-ci, vous pouvez trouver un appel à `hlcg.init_register_allocators` pour créer les allocateurs de registre. Faites appel à `cg.do_register_allocation(aktproccode,headertai);` - pour faire l'allocation effective et aussi à `hlcg.done_register_allocators;` pour les libérer les allocateurs.
86104
87105## Conseil
88106
89107Vous pouvez compiler votre projet en utilisant le commutateur -sr. Cela permettra de conserver les noms de registre imaginaire dans le fichier .s généré.
90108
91109## Hiérarchie des appels pour les méthodes publiques
92110
93111``` pascal
94112Public
95113
96114constructor
97115
98116destructor
99117
100118do_register_allocation - level 1 is ordered by calling
101119 insert_regalloc_info_all
102120 generate_interference_graph
103121 add_edges_used(1, 2) get_alias add_edge
104122 add_edge ibitmap.s
105123 prepare_colouring
106124 make_work_list ri_coalesced
107125 sort_simplify_worklist
108126 colour_registers
109127 simplify ri_coalesced
110128 decrement_degree ri_coalesced
111129 coalesce get_alias simplifyworklist.add(v); ibitmap
112130 conservative ri_coalesced
113131 adjacent_ok ri_coalesced ibitmap
114132 add_worklist simplifyworklist.add(u);
115133 combine ibitmap, add_edge
116134 enable_moves
117135 decrement_degree ri_coalesced simplifyworklist.add(m)
118136 add_edge ibitmap.s
119137 freeze
120138 freeze_moves(, 2) get_alias(3)
121139 select_spill
122140 freeze_moves(, 2) get_alias(3)
123141 assign_colours(, 2) get_alias
124142 epilogue_colouring Destroys the objects used during the coloring - worklist_moves, active_moves, frozen_moves, coalesced_moves, constrained_moves, reginfo.movelist
125143 spill_registers
126144 clear_interferences ibitmap.s
127145 instr_spill_register get_alias
128146 getregisterinline
129147 add_edges_used(2, 2) get_alias
130148 add_edge ibitmap.s
131149 ungetregisterinline
132150 get_spill_subreg
133151 do_spill_replace
134152 do_spill_read
135153 do_spill_written
136154 translate_registers
137155 assign_colours(, 2) get_alias
138156
139157getregister
140158
141159add_move_instruction
142160 add_to_movelist
143161
144162combine ri_coalesced(s)
145163 enable_moves
146164 decrement_degree ri_coalesced
147165
148166-----------------------------------------
149167Properties
150168
151169live_range_direction
152170 set_live_range_direction
153171
154172live_start
155173 get_live_start
156174 set_live_start
157175
158176live_end
159177 get_live_end
160178 set_live_end
161179```
162180
163181## Classe TRgObj
164182
165183### Description
166184
167185Classe principale pour l'allocation de registre dans FPC
168186
169187### Méthodes publiques
170188
171189### Méthodes protégées
172190
173191### Méthodes privées
174192
175193## Registres imaginaires
176194
177195### Stockage des registres imaginaires
178196
179197`TRgObj` stockent les registres imaginaires dans une liste de structure `TRegInfo`.
180198
181199### Vie d'un registre imaginaire
182200
183201La vie d'un registre imaginaire commence quand il est utilisé la première fois pas une instruction assembleur. Lorsqu'il apparaît pour la première fois dans une instruction assembleur dans la liste assembleur pour la routine spécifique. Et finit quand il est utilisé pour la dernière fois dans une instruction assembleur. Dans la structure `TRegInfo` pour le registre imaginaire particulier, il y a deux champs pour la vie du registre.
184202
185203
186204`live_start: TAi;` indique quelle est l'instruction assembleur où le registre est utilisé pour la première fois.
187205
188206`live_end: TAi;` indique quelle est l'instruction assembleur où le registre est utilisé pour la dernière fois.
189207
190208## Interférences
191209
192210Interférences
193211
194212Hiérarchie d'appel:
195213
196-` generate_interference_graph`\
197-`   add_edges_used(1, 2)            get_alias                       add_edge`\
198-`     add_edge                                              ibitmap.s`
214+```text
215+ generate_interference_graph
216+ add_edges_used(1, 2) get_alias add_edge
217+ add_edge ibitmap.s
218+```
199219
200220### generate_interference_graph
201221
202222Cette méthode itère sur la liste assembleur. Recherchant l'instruction `reg_alloc` avec le bon `regType`.
203223
204224Pendant cette itération, nous gardons tout registre en vie dans la liste - `live_registers`. S'il est alloué, elle ajoute le superregistre à `live_registers` S'il est désalloué, elle le retire de `live_registers`.
205225
206226Après l'ajout ou le retrait dans `live_registers`, elle appelle `add_edges_used`.
207227
208228### add_edges_used
209229
210230Itère sur tout `live_registers` et appelle `add_edge` Itère tous les live_register et appelle `add_edge`, elle obtient l'alias du live_register actuel et le super registre qui est à la méthode.
211231
212232### add_edge
213233
214234Quand nous ajoutons un bord, nous gardons l'information sur deux endroits. L'un est dans le `interferenceBitmap`. Et l'autre dans la `adjList` de l'information de registre dans:
215235
216236``` pascal
217237reginfo[u].adjList.add(v);
218238reginfo[v].adjList.add(u);
219239ibitmap[v, u] := true;
220240ibitmap[u, v] := true;
221241```
222242
223243Dans la relation entre ces deux registres, il n'y a pas de dépendance. Il n'y a pas de primaire , ils sont égaux dedans.
224244
225245Dans `reginfo`, vous accédez à l'information par un index et dans la Bitmap vous faites pareil. C'est à peu près la même chose en vitesse.
226246
227247Interférences travaillent avec les super registres.
228248
229249## Arrière-plan
230250
231251### Groupement des instructions
232252
233253Un regard plus abstrait sur les instructions CPU nous montre que nous avons principalement peu de sortes d'instructions.
234254
235255- Le premier groupe contient des instructions qui modifient quelques données.
236256
237257Ces données restent à la même place mais sont modifiées. Vous n'avez pas non plus besoin de données supplémentaires pour effectuer le changement. Quelques instructions de ce groupe sont INC, DEC, CLC, CLD
238258
239259- Le second groupe sont des instructions qui déplacent les données d'un endroit à un autre.
240260
241261Telle que MOV, LAHF, POP, PUSH, LEA
242262
243263- Le troisième groupe d'instructions sont celles qui comparent les données et ne font aucune modification, il suffit de les contrôler.
244264
245265De telles instructions sont TEST, COMPARE
246266
247267- Le quatrième groupe d'instructions sont celles qui changent le flot d'exécution des instructions, telles que JMP, JCC, JCXZ,
248268
249269- Le cinquième groupe sont les instructions qui changent des données basées sur d'autres données comme ADD
250270
251271Un autre regroupement d'instructions pourrait être par la version de la CPU où l'instruction apparaît en premier.
252272
253273Encore un autre groupement pourrait être basé sur les registres qui peuvent être utilisés dans l'instruction.
254274
255275Certaines instructions peuvent utiliser seulement des registres spécifiques pour l'adresse - mov ax, \[bx + si + 1000\]. Vous pouvez choisir seulement entre par exemple ces registres - BP, BX, SI et DI. Certaines instructions peuvent utiliser seulement des registres, d'autres des adresses, d'autres encore les deux.
256276
257277Certains registres ont des sous-registres, d'autres non.
258278
259279### Données dans les instructions
260280
261281Presque toutes les instructions opèrent sur des données. Aussi elles utilisent de la données pour réaliser l'opération. La donnée peut être dans les registres CPU, la mémoire ou dans les drapeaux de la CPU.
262282
263283### Programmation utilisant les instructions
264284
265285Les instructions sont indépendantes l'une de l'autre. Pourtant, quand un programme est créé, il y a des dépendances logiques que le programmeur introduit. Un programmeur veut que des modifiées soient apportées à certaines données. Ce qui peut être réalisé par un peu d'instructions exécutées les unes après les autres. Et à la fin, les données voulues seront produites. Mais la donnée n'est pas exactement un registre CPU ou une adresse mémoire. Les données sont quelque chose dont le programmeur se soucie. Par exemple, nous avons une donnée qui vaut 1000 au départ. Nous pouvons la placée dans un registre CPU que nous voulons pour y ajouter 200. Nous pouvons utilmiser une instruction assembleur ADD mais ensuite nous pouvons déplacer la valeur que nous avons atteinte dans un autre registre. Et alors l'incrémenter de 300 à la fin, nous aurons la valeur recherchée 1500. Mais nous utilisons deux registres pour une seule donnée, ce que nous n'avons pas besoin normalement. Nous pouvons le faire en en utilisant un seul. Parce que nous devons déplacer les données vers le deuxième registre quelque part au milieu. Et cela est une intruction supplémentaire. Cependant, plusieurs données sont traitées en parallèle. Et parfois nous utilisons ce déplacement supplémentaire , seulement pour accélerer les exécutions sur d'autres données. Ceci est normalement nécessaire lorsque des données sont produites pour calculer la troisième. Ce n'est pas très important pour l'allocateur de registre, c'est plus destiné au générateur de code. Le générateur de code pourrait décider où ajouter des instructions supplémentaires, pour accélérer d'autres exécutions. Il semble que l'allocateur de registre ne devrait pas s'en soucier. Mais il est bon d'y penser.
266286
267287### Lignes directrices de la conception objet
268288
269289Dans l'implémentation actuelle, l'obtention des registres et l'attribution sont mélangées. Ils partagent également les mêmes structures, la même classe. Mais les données pour chacun d'entre eux sont utilisées sur des phases complètement différentes. Les mélanger ensemble apporte plus de code en un seul endroit. De plus, il devient difficile de les séparer lorsque vous travaillez dessus. Ils peuvent être facilement divisés sans perte de vitesse. Différents types d'allocations de registre peuvent également être utilisés. L'obtention des registres et le réglage des poids, ainsi que la vie ... peuvent être en un seul endroit. Mais à la fin, différents allocateurs peuvent traiter ces données. Il est difficile de développer davantage l'attribution du registre car de nombreuses phases et la logique ont été réunies en un seul endroit.
270290
271291Dans la méthode
272292
273293``` pascal
274294 TRgObj.colour_registers
275295```
276296
277297, une approche différente peut être effectuée. La saisie de différents états peut être implémentée. Vérifiez plutôt les décomptes de toutes les listes. Ce sera plus rapide. Code clair. Facile à déboguer. Facile à développer davantage.
278298
279299Des améliorations peuvent être apportées aux poids des instructions. Ou de la manière dont l'allocateur décide quel registre déverser et lequel non.
280300
281301---
282302*From the Free Pascal wiki, page "The register allocator/fr", as it stood on 2021-03-20 after a change by E-ric, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history