Choisir quatre registres d'arguments sur x64 - commun à UN*X / Win64
L'une des choses à garder à l'esprit à propos du x86 est que l'encodage du nom de registre en "numéro de registre" n'est pas évident ; en termes d'encodage d'instruction (l'instruction MOD R/M octet, voir http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm ), les numéros de registre 0...7 sont - dans cet ordre - ?AX
, ?CX
, ?DX
, ?BX
, ?SP
, ?BP
, ?SI
, ?DI
.
D'où le choix de A/C/D (regs 0..2) pour la valeur de retour et les deux premiers arguments (qui sont les 32 bits "classiques"). __fastcall
convention) est un choix logique. En ce qui concerne le passage à 64 bits, les regs "supérieurs" sont commandés, et tant Microsoft que UN*X/Linux ont opté pour R8
/ R9
que les premiers.
En gardant cela à l'esprit, le choix de Microsoft de RAX
(valeur de retour) et RCX
, RDX
, R8
, R9
(arg[0..3]) sont une sélection compréhensible si vous choisissez quatre registres pour les arguments.
Je ne sais pas pourquoi l'ABI de l'AMD64 UN*X a choisi RDX
avant RCX
.
Choisir six registres d'arguments sur x64 - spécifique à UN*X
UN*X, sur les architectures RISC, a traditionnellement effectué le passage d'arguments dans les registres - spécifiquement, pour le premier six (c'est le cas sur PPC, SPARC, MIPS au moins). C'est peut-être l'une des principales raisons pour lesquelles les concepteurs de l'ABI de l'AMD64 (UN*X) ont choisi d'utiliser six registres sur cette architecture également.
Donc si vous voulez six pour passer des arguments, et il est logique de choisir les registres suivants RCX
, RDX
, R8
y R9
pour quatre d'entre eux, quels sont les deux autres à choisir ?
Les registres "supérieurs" nécessitent un octet de préfixe d'instruction supplémentaire pour les sélectionner et ont donc une empreinte de taille d'instruction plus importante, vous ne voudrez donc pas choisir l'un d'entre eux si vous avez le choix. Parmi les registres classiques, en raison de la implicite signification de RBP
y RSP
ceux-ci ne sont pas disponibles, et RBX
a traditionnellement une utilisation spéciale sur UN*X (global offset table) avec laquelle les concepteurs de l'ABI AMD64 ne voulaient apparemment pas devenir inutilement incompatible.
Ergo, le seul choix étaient RSI
/ RDI
.
Donc si vous devez prendre RSI
/ RDI
comme registres d'arguments, de quels arguments s'agit-il ?
Les rendre arg[0]
y arg[1]
présente certains avantages. Voir le commentaire de cHao.
?SI
y ?DI
sont des opérandes source/destination d'instructions de chaîne de caractères, et comme cHao l'a mentionné, leur utilisation en tant que registres d'arguments signifie qu'avec les conventions d'appel UN*X de l'AMD64, la plus simple des conventions d'appel est la suivante strcpy()
par exemple, se compose uniquement des deux instructions du CPU repz movsb; ret
car les adresses source/cible ont été placées dans les registres corrects par l'appelant. Il existe, en particulier dans le code de bas niveau et le code "glue" généré par le compilateur (pensez, par exemple, à certains allocateurs de tas C++ qui remplissent à zéro les objets lors de la construction, ou au noyau qui remplit à zéro les pages de tas lors de la construction), un code de bas niveau. sbrk()
Il sera donc utile pour le code si fréquemment utilisé d'économiser les deux ou trois instructions du CPU qui, autrement, chargeraient de tels arguments d'adresse source/cible dans les registres "corrects".
Ainsi, d'une certaine manière, UN*X et Win64 ne sont différents que par le fait que UN*X "ajoute" deux arguments supplémentaires, dans des formats choisis à dessein. RSI
/ RDI
aux registres, au choix naturel de quatre arguments en RCX
, RDX
, R8
y R9
.
Au-delà de ça...
Les différences entre les ABI UN*X et Windows x64 ne se limitent pas à l'affectation des arguments à des registres spécifiques. Pour une vue d'ensemble sur Win64, consultez :
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64 et AMD64 UN*X diffèrent également de manière frappante dans la façon dont l'espace de pile est utilisé ; sur Win64, par exemple, l'appelant doit allouer de l'espace de stockage pour les arguments de la fonction même si les args 0...3 sont passés dans des registres. D'autre part, sur UN*X, une fonction feuille (c'est-à-dire une fonction qui n'appelle pas d'autres fonctions) n'est même pas obligée d'allouer de l'espace de pile si elle n'en a pas besoin de plus de 128 octets (oui, vous possédez et pouvez utiliser une certaine quantité de pile sans l'allouer... enfin, à moins que vous ne soyez du code noyau, source de bogues astucieux). Il s'agit là de choix d'optimisation particuliers, dont la plupart des raisons sont expliquées dans les références ABI complètes auxquelles renvoie la référence wikipedia du posteur original.
4 votes
Eh bien, juste pour le premier registre : rcx : ecx était le paramètre "this" pour la convention msvc __thiscall x86. Donc probablement juste pour faciliter le portage de leur compilateur vers x64, ils ont commencé avec rcx comme premier. Le fait que tout le reste soit différent n'était qu'une conséquence de cette décision initiale.
1 votes
@Chris : J'ai ajouté une référence au document complémentaire de l'ABI AMD64 (et quelques explications sur ce qu'il est réellement) ci-dessous.
1 votes
Je n'ai pas trouvé de justification de la part de MS mais j'ai trouvé quelques discussions aquí