mov
-immédiat est cher pour les constantes
Cela peut être évident, mais je le mettrai toujours ici. En général, il vaut la peine de penser à la représentation au niveau du bit d'un nombre lorsque vous devez initialiser une valeur.
Initialisation eax
avec 0
:
b8 00 00 00 00 mov $0x0,%eax
devrait être raccourci ( pour les performances ainsi que la taille du code ) à
31 c0 xor %eax,%eax
Initialisation eax
avec -1
:
b8 ff ff ff ff mov $-1,%eax
peut être raccourci en
31 c0 xor %eax,%eax
48 dec %eax
ou
83 c8 ff or $-1,%eax
Ou plus généralement, toute valeur étendue de signe 8 bits peut être créée en 3 octets avec push -12
(2 octets) / pop %eax
(1 octet). Cela fonctionne même pour les registres 64 bits sans préfixe REX supplémentaire; push
/ pop
taille d'opérande par défaut = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Ou étant donné une constante connue dans un registre, vous pouvez créer une autre constante proche en utilisant lea 123(%eax), %ecx
(3 octets). C'est pratique si vous avez besoin d'un registre mis à zéro et d' une constante; xor-zero (2 octets) + lea-disp8
(3 octets).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Voir aussi Définir efficacement tous les bits du registre CPU sur 1
push 200; pop edx
- 3 octets pour l'initialisation.dec
, par exemplexor eax, eax; dec eax
push imm8
/pop reg
est de 3 octets, et est fantastique pour les constantes 64 bits sur x86-64, oùdec
/inc
est de 2 octets. Etpush r64
/pop 64
(2 octets) peut même remplacer un 3 octetsmov r64, r64
(3 octets avec REX). Voir aussi Réglez tous les bits du registre CPU à 1 efficacement pour des choses commelea eax, [rcx-1]
une valeur connueeax
(par exemple, si vous avez besoin d'un registre mis à zéro et d' une autre constante, utilisez simplement LEA au lieu de push / popDans de nombreux cas, les instructions basées sur des accumulateurs (c'est-à-dire celles qui prennent
(R|E)AX
comme opérande de destination) sont 1 octet plus courtes que les instructions générales; voir cette question sur StackOverflow.la source
al, imm8
cas spéciaux, commeor al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ja .non_alphabetic
étant de 2 octets chacun, au lieu de 3. L'utilisational
pour les données de caractères permet égalementlodsb
et / oustosb
. Ou utilisezal
pour tester quelque chose sur l'octet de poids faible d'EAX, commelodsd
/test al, 1
/setnz cl
fait cl = 1 ou 0 pour impair / pair. Mais dans les rares cas où vous avez besoin d'un immédiat 32 bits, alors bien sûrop eax, imm32
, comme dans ma réponse chroma-keyChoisissez votre convention d'appel pour placer les arguments où vous le souhaitez.
Le langage de votre réponse est asm (en fait du code machine), alors traitez-le comme faisant partie d'un programme écrit en asm, pas en C-compiled-for-x86. Votre fonction ne doit pas être facilement appelable depuis C avec n'importe quelle convention d'appel standard. C'est un bon bonus si cela ne vous coûte pas d'octets supplémentaires.
Dans un programme asm pur, il est normal que certaines fonctions d'assistance utilisent une convention d'appel qui soit pratique pour elles et pour leur appelant. Ces fonctions documentent leur convention d'appel (entrées / sorties / clobbers) avec des commentaires.
Dans la vraie vie, même les programmes asm ont (je pense) tendance à utiliser des conventions d'appel cohérentes pour la plupart des fonctions (en particulier sur différents fichiers source), mais toute fonction importante donnée peut faire quelque chose de spécial. Dans le code-golf, vous optimisez la merde d'une seule fonction, donc c'est évidemment important / spécial.
Pour tester votre fonction à partir d'un programme C, vous pouvez écrire un wrapper qui place les arguments aux bons endroits, enregistre / restaure tous les registres supplémentaires que vous tapotez et place la valeur de retour dans le
e/rax
cas contraire.Les limites de ce qui est raisonnable: tout ce qui n'impose pas un fardeau déraisonnable à l'appelant:
DF (indicateur de direction de chaîne pour
lods
stos
est normal d'exiger que / / etc.) soit effacé (vers le haut) lors d'un appel / retrait. Le laisser indéfini lors d'un appel / retrait serait correct. Exiger qu'il soit effacé ou réglé à l'entrée, mais le laisser modifié à votre retour serait bizarre.Le retour des valeurs FP en x87
st0
est raisonnable, mais le retour enst3
avec des ordures dans un autre registre x87 ne l'est pas. L'appelant devrait nettoyer la pile x87. Même revenirst0
avec des registres de pile supérieurs non vides serait également discutable (à moins que vous ne retourniez plusieurs valeurs).call
, tout[rsp]
comme votre adresse de retour. Vous pouvez évitercall
/ret
sur x86 en utilisant un registre de liens commelea rbx, [ret_addr]
/jmp function
et revenir avecjmp rbx
, mais ce n'est pas "raisonnable". Ce n'est pas aussi efficace que call / ret, donc ce n'est pas quelque chose que vous trouveriez vraisemblablement dans du vrai code.Cas limites: écrire une fonction qui produit une séquence dans un tableau, étant donné les 2 premiers éléments comme arguments de fonction . J'ai choisi que l'appelant stocke le début de la séquence dans le tableau et passe simplement un pointeur sur le tableau. C'est définitivement plier les exigences de la question. J'ai envisagé de prendre les arguments
xmm0
pourmovlps [rdi], xmm0
, qui serait également une convention d'appel bizarre.Retourne un booléen en DRAPEAUX (codes de condition)
Les appels système OS X font cela (
CF=0
signifie aucune erreur): est-il considéré comme une mauvaise pratique d'utiliser le registre des indicateurs comme valeur de retour booléenne? .Toute condition qui peut être vérifiée avec un JCC est parfaitement raisonnable, surtout si vous pouvez en choisir une qui a une pertinence sémantique par rapport au problème. (par exemple, une fonction de comparaison peut définir des indicateurs afin
jne
sera donc prise si elles ne sont pas égales).Exiger des arguments étroits (comme un
char
) pour être signe ou zéro étendu à 32 ou 64 bits.Ce n'est pas déraisonnable; utiliser
movzx
oumovsx
pour éviter les ralentissements de registres partiels est normal dans un asm x86 moderne. En fait, clang / LLVM fait déjà du code qui dépend d'une extension non documentée de la convention d'appel System V x86-64: les arguments plus étroits que 32 bits sont signe ou zéro étendu à 32 bits par l'appelant .Vous pouvez documenter / décrire l'extension à 64 bits en écrivant
uint64_t
ouint64_t
dans votre prototype si vous le souhaitez. Par exemple, vous pouvez utiliser uneloop
instruction, qui utilise l'ensemble des 64 bits de RCX, sauf si vous utilisez un préfixe de taille d'adresse pour remplacer la taille jusqu'à 32 bits ECX (oui vraiment, la taille de l'adresse n'est pas la taille de l'opérande).Notez qu'il
long
s'agit uniquement d'un type 32 bits dans l'ABI Windows 64 bits et l'ABI Linux x32 ;uint64_t
est sans ambiguïté et plus court à taper queunsigned long long
.Conventions d'appel existantes:
Windows 32 bits
__fastcall
, déjà suggéré par une autre réponse : arguments entiers dansecx
etedx
.x86-64 System V : transmet de nombreux arguments dans les registres et contient de nombreux registres clobés que vous pouvez utiliser sans préfixe REX. Plus important encore, il a été choisi pour permettre aux compilateurs de s'aligner
memcpy
ou derep movsb
facilement: les 6 premiers arguments entiers / pointeurs sont passés en RDI, RSI, RDX, RCX, R8, R9.Si votre fonction utilise
lodsd
/ à l'stosd
intérieur d'une boucle qui s'exécutercx
fois (avec l'loop
instruction), vous pouvez dire "appelable depuis C commeint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
avec la convention d'appel x86-64 System V". exemple: chromakey .GCC 32 bits
regparm
: arguments entiers dans EAX , ECX, EDX, retour dans EAX (ou EDX: EAX). Le fait d'avoir le premier argument dans le même registre que la valeur de retour permet certaines optimisations, comme dans ce cas avec un exemple d'appelant et un prototype avec un attribut de fonction . Et bien sûr, AL / EAX est spécial pour certaines instructions.L'ABI Linux x32 utilise des pointeurs 32 bits en mode long, vous pouvez donc enregistrer un préfixe REX lors de la modification d'un pointeur ( exemple d'utilisation ). Vous pouvez toujours utiliser la taille d'adresse 64 bits, sauf si vous avez un entier négatif 32 bits étendu à zéro dans un registre (ce serait donc une grande valeur non signée si vous le faisiez).
[rdi + rdx]
).Notez que
push rsp
/pop rax
est de 2 octets et équivaut àmov rax,rsp
, de sorte que vous pouvez toujours copier des registres 64 bits complets sur 2 octets.la source
ret 16
; ils ne sautent pas l'adresse de retour, poussent un tableau, puispush rcx
/ret
. L'appelant devrait connaître la taille du tableau ou avoir enregistré RSP quelque part en dehors de la pile pour se retrouver.Utiliser des codages abrégés dans des cas spéciaux pour AL / AX / EAX et d'autres formes abrégées et instructions à un octet
Les exemples supposent un mode 32/64 bits, où la taille d'opérande par défaut est 32 bits. Un préfixe de taille opérande modifie l'instruction en AX au lieu de EAX (ou l'inverse en mode 16 bits).
inc/dec
un registre (autre que 8 bits):inc eax
/dec ebp
. (Pas x86-64: les0x4x
octets d'opcode ont été réutilisés en tant que préfixes REX, doncinc r/m32
c'est le seul encodage.)8-bit
inc bl
est de 2 octets, en utilisant leinc r/m8
code d' opération + ModR / M opérande codant . Alors utilisezinc ebx
pour incrémenterbl
, si c'est sûr. (par exemple, si vous n'avez pas besoin du résultat ZF dans les cas où les octets supérieurs peuvent être différents de zéro).scasd
:e/rdi+=4
, nécessite que le registre pointe vers une mémoire lisible. Parfois utile même si vous ne vous souciez pas du résultat FLAGS (commecmp eax,[rdi]
/rdi+=4
). Et en mode 64 bits,scasb
peut fonctionner comme un octetinc rdi
, si lodsb ou stosb ne sont pas utiles.xchg eax, r32
: C'est là 0x90 NOP est provenuxchg eax,eax
. Exemple: réorganiser 3 registres avec deuxxchg
instructions dans une bouclecdq
/ pour GCD en 8 octets où la plupart des instructions sont à un octet, y compris un abus de / au lieu de /idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
: signe-étendre EAX dans EDX: EAX, c'est-à-dire copier le bit élevé d'EAX sur tous les bits d'EDX. Pour créer un zéro avec non négatif connu, ou pour obtenir un 0 / -1 à ajouter / sous ou masque avec. Leçon d'histoire x86:cltq
vs.movslq
, et aussi les mnémoniques AT&T vs Intel pour cela et les éléments connexescdqe
.lodsb / d : comme
mov eax, [rsi]
/rsi += 4
sans drapeaux clobbering. (En supposant que DF est clair, quelles conventions d'appel standard exigent lors de l'entrée de fonction.) Aussi stosb / d, parfois scas, et plus rarement movs / cmps.push
/pop reg
. par exemple en mode 64 bits,push rsp
/pop rdi
est de 2 octets, mais amov rdi, rsp
besoin d'un préfixe REX et est de 3 octets.xlatb
existe, mais est rarement utile. Une grande table de recherche est à éviter. Je n'ai également jamais trouvé d'utilisation pour les instructions AAA / DAA ou d'autres instructions BCD ou à 2 chiffres ASCII.1 octet
lahf
/sahf
sont rarement utiles. Tu pourraislahf
/and ah, 1
comme alternative àsetc ah
, mais ce n'est généralement pas utile.Et pour CF en particulier, il
sbb eax,eax
doit y avoir un octetsalc
0 / -1, ou même non documenté mais universellement pris en charge (définir AL à partir de Carry), ce qui fait effectivementsbb al,al
sans affecter les indicateurs. (Supprimé dans x86-64). J'ai utilisé SALC dans le défi d'appréciation des utilisateurs n ° 1: Dennis ♦ .1 octet
cmc
/clc
/stc
(flip ("complément"), clear ou set CF) sont rarement utiles, bien que j'aie trouvé une utilisation pour l'cmc
addition en précision étendue avec des blocs de base 10 ^ 9. Pour régler / effacer inconditionnellement les FC, faites généralement en sorte que cela se fasse dans le cadre d'une autre instruction, par exemplexor eax,eax
efface CF ainsi que EAX. Il n'y a pas d'instructions équivalentes pour les autres drapeaux de condition, juste DF (direction de chaîne) et IF (interruptions). Le drapeau de transport est spécial pour de nombreuses instructions; les décalages le définissent,adc al, 0
peuvent l'ajouter à AL en 2 octets, et j'ai mentionné plus tôt le SALC non documenté.std
/cld
semblent rarement en valoir la peine . Surtout dans le code 32 bits, il est préférable de simplement utiliserdec
sur un pointeur et unmov
opérande source de mémoire sur une instruction ALU au lieu de définir DF solodsb
/stosb
go downward au lieu de up. Habituellement, si vous avez besoin de descendre, vous avez toujours un autre pointeur qui monte, vous en aurez donc besoin de plus d'unstd
etcld
dans toute la fonction pour utiliserlods
/stos
pour les deux. À la place, utilisez simplement les instructions de chaîne pour la direction ascendante. (Les conventions d'appel standard garantissent DF = 0 à l'entrée de la fonction, vous pouvez donc supposer cela gratuitement sans utilisercld
.)Historique 8086: pourquoi ces encodages existent
En 8086 d' origine, AX était très spécial: instructions aiment
lodsb
/stosb
,cbw
,mul
/div
et d' autres utilisent implicitement. C'est toujours le cas bien sûr; le x86 actuel n'a abandonné aucun des opcodes de 8086 (du moins aucun des officiellement documentés). Mais les CPU ultérieurs ont ajouté de nouvelles instructions qui ont donné des moyens meilleurs / plus efficaces de faire les choses sans les copier ou les échanger d'abord vers AX. (Ou vers EAX en mode 32 bits.)Par exemple, 8086 manquait d'ajouts ultérieurs comme
movsx
/movzx
pour charger ou déplacer + extension de signe, ou 2 et 3 opérandesimul cx, bx, 1234
qui ne produisent pas un résultat élevé et n'ont pas d'opérandes implicites.En outre, le principal goulot d'étranglement du 8086 était la récupération d'instructions, il était donc important d'optimiser la taille du code pour les performances à l'époque . Le concepteur ISA de 8086 (Stephen Morse) a dépensé beaucoup d'espace de codage d'opcode sur des cas spéciaux pour AX / AL, y compris des opcodes de destination spéciaux (E) AX / AL pour toutes les instructions de base ALU immédiates-src , juste opcode + immediate sans octet ModR / M. 2 octets
add/sub/and/or/xor/cmp/test/... AL,imm8
ouAX,imm16
ou (en mode 32 bits)EAX,imm32
.Mais il n'y a pas de cas particulier pour
EAX,imm8
, donc l'encodage ModR / M normal deadd eax,4
est plus court.L'hypothèse est que si vous allez travailler sur certaines données, vous en aurez besoin dans AX / AL, donc échanger un registre avec AX est quelque chose que vous voudrez peut-être faire, peut-être même plus souvent que de copier un registre vers AX avec
mov
.Tout ce qui concerne le codage d'instructions 8086 prend en charge ce paradigme, des instructions comme
lodsb/w
à tous les codages de cas spéciaux pour les intermédiaires avec EAX à son utilisation implicite même pour la multiplication / division.Ne vous laissez pas emporter; ce n'est pas automatiquement une victoire de tout échanger vers EAX, surtout si vous devez utiliser des intermédiaires avec des registres 32 bits au lieu de 8 bits. Ou si vous devez entrelacer des opérations sur plusieurs variables dans des registres à la fois. Ou si vous utilisez des instructions avec 2 registres, pas du tout immédiats.
Mais gardez toujours à l'esprit: est-ce que je fais quelque chose qui serait plus court dans EAX / AL? Puis-je réorganiser ce que j'ai en AL, ou suis-je actuellement en train de mieux tirer parti de AL avec ce que je l'utilise déjà.
Mélangez librement les opérations 8 bits et 32 bits pour en profiter chaque fois que cela est sûr (vous n'avez pas besoin d'effectuer dans le registre complet ou autre).
la source
cdq
est utile pourdiv
lequel les besoins sont misedx
à zéro dans de nombreux cas.cdq
avant de ne pas signerdiv
si vous savez que votre dividende est inférieur à 2 ^ 31 (c'est-à-dire non négatif lorsqu'il est traité comme signé), ou si vous l'utilisez avant de définireax
une valeur potentiellement importante. Normalement (en dehors du code-golf), vous utiliseriezcdq
comme configuration pouridiv
etxor edx,edx
avantdiv
Utiliser les
fastcall
conventionsLa plate-forme x86 possède de nombreuses conventions d'appel . Vous devez utiliser ceux qui transmettent les paramètres dans les registres. Sur x86_64, les premiers paramètres sont de toute façon passés dans les registres, donc pas de problème. Sur les plates-formes 32 bits, la convention d'appel par défaut (
cdecl
) transmet les paramètres dans la pile, ce qui n'est pas bon pour le golf - l'accès aux paramètres sur la pile nécessite de longues instructions.Lors de l'utilisation
fastcall
sur des plates-formes 32 bits, 2 premiers paramètres sont généralement transmis dansecx
etedx
. Si votre fonction a 3 paramètres, vous pourriez envisager de l'implémenter sur une plate-forme 64 bits.Prototypes de fonctions C pour la
fastcall
convention (tirés de cet exemple de réponse ):la source
Soustrayez -128 au lieu d'ajouter 128
Samely, ajoutez -128 au lieu de soustraire 128
la source
< 128
en<= 127
pour réduire l'amplitude d'un opérande immédiat pourcmp
, ou gcc préfère toujours le réarrangement. compare pour réduire l'amplitude même si ce n'est pas -129 contre -128.Créez 3 zéros avec
mul
(puisinc
/dec
pour obtenir +1 / -1 ainsi que zéro)Vous pouvez mettre à zéro eax et edx en multipliant par zéro dans un troisième registre.
aura pour résultat EAX, EDX et EBX étant tous à zéro en seulement quatre octets. Vous pouvez mettre à zéro EAX et EDX sur trois octets:
Mais à partir de ce point de départ, vous ne pouvez pas obtenir un troisième registre mis à zéro dans un octet de plus, ou un registre +1 ou -1 dans 2 autres octets. Utilisez plutôt la technique mul.
Exemple d'utilisation: concaténation des nombres de Fibonacci en binaire .
Notez qu'une fois la
LOOP
boucle terminée, ECX sera nul et peut être utilisé pour mettre à zéro EDX et EAX; vous n'avez pas toujours à créer le premier zéro avecxor
.la source
Les registres et drapeaux du processeur sont dans des états de démarrage connus
Nous pouvons supposer que le CPU est dans un état par défaut connu et documenté basé sur la plate-forme et le système d'exploitation.
Par exemple:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
la source
_start
. Alors oui, il est juste d'en profiter si vous écrivez un programme au lieu d'une fonction. Je l'ai fait dans Extreme Fibonacci . (Dans un exécutable lié dynamiquement, ld.so runs avant de sauter à votre_start
, et fait ordures congé dans les registres, mais statique est juste votre code.)Pour ajouter ou soustraire 1, utilisez le
inc
ou lesdec
instructions d' un octet qui sont plus petites que les instructions d'ajout et de sous-octets multi-octets.la source
inc/dec r32
avec le numéro de registre encodé dans l'opcode.inc ebx
Est donc 1 octet, maisinc bl
est 2. Encore plus petit queadd bl, 1
bien sûr, pour les registres autres queal
. Notez également queinc
/dec
laissez CF non modifié, mais mettez à jour les autres indicateurs.lea
pour les mathématiquesC'est probablement l'une des premières choses que l'on apprend sur x86, mais je le laisse ici pour rappel.
lea
peut être utilisé pour effectuer une multiplication par 2, 3, 4, 5, 8 ou 9 et ajouter un décalage.Par exemple, pour calculer
ebx = 9*eax + 3
en une seule instruction (en mode 32 bits):Ici, c'est sans décalage:
Hou la la! Bien sûr,
lea
peut également être utilisé pour faire des calculs commeebx = edx + 8*eax + 3
pour calculer l'indexation de tableaux.la source
lea eax, [rcx + 13]
s'agit de la version sans préfixe supplémentaire pour le mode 64 bits. Taille d'opérande 32 bits (pour le résultat) et taille d'adresse 64 bits (pour les entrées).Les instructions de boucle et de chaîne sont plus petites que les séquences d'instructions alternatives. Le plus utile est celui
loop <label>
qui est plus petit que la séquence de deux instructionsdec ECX
etjnz <label>
, etlodsb
est plus petit quemov al,[esi]
etinc si
.la source
mov
petits intermédiaires dans les registres inférieurs, le cas échéantSi vous savez déjà que les bits supérieurs d'un registre sont à 0, vous pouvez utiliser une instruction plus courte pour déplacer un immédiat dans les registres inférieurs.
contre
Utilisation
push
/pop
pour imm8 pour remettre à zéro les bits supérieursNous remercions Peter Cordes.
xor
/mov
est de 4 octets, maispush
/pop
n'est que de 3!la source
mov al, 0xa
est bon si vous n'en avez pas besoin à zéro étendu jusqu'au reg complet. Mais si vous le faites, xor / mov est de 4 octets contre 3 pour push imm8 / pop oulea
d'une autre constante connue. Cela peut être utile en combinaison avecmul
zéro à 3 registres sur 4 octets , oucdq
si vous avez besoin de beaucoup de constantes.[0x80..0xFF]
, qui ne sont pas représentables comme un imm8 étendu par signe. Ou si vous connaissez déjà les octets supérieurs, par exemplemov cl, 0x10
après uneloop
instruction, car la seule façonloop
de ne pas sauter est quand elle a faitrcx=0
. (Je suppose que vous avez dit cela, mais votre exemple utilise unxor
). Vous pouvez même utiliser l'octet de poids faible d'un registre pour autre chose, tant que quelque chose d'autre le remet à zéro (ou autre) lorsque vous avez terminé. par exemple mon programme Fibonacci reste-1024
en ebx et utilise bl.xchg eax, r32
), par exemplemov bl, 10
/dec bl
/jnz
afin que votre code ne se soucie pas des octets élevés de RBX.Les DRAPEAUX sont fixés après de nombreuses instructions
Après de nombreuses instructions arithmétiques, le drapeau de transport (non signé) et le drapeau de débordement (signé) sont définis automatiquement ( plus d'informations ). Le drapeau de signe et le drapeau zéro sont définis après de nombreuses opérations arithmétiques et logiques. Cela peut être utilisé pour la ramification conditionnelle.
Exemple:
ZF est défini par cette instruction, nous pouvons donc l'utiliser pour la ramification conditionnelle.
la source
test al,1
; vous ne recevez généralement pas cela gratuitement. (Ouand al,1
pour créer un entier 0/1 selon impair / pair.)test
/cmp
", alors ce serait un débutant x86 assez basique, mais cela vaut quand même un vote positif.Utilisez des boucles do-while au lieu de boucles while
Ce n'est pas spécifique à x86 mais c'est une astuce d'assemblage pour débutants largement applicable. Si vous savez qu'une boucle while s'exécutera au moins une fois, la réécriture en boucle do-while, avec vérification de l'état de la boucle à la fin, enregistre souvent une instruction de saut de 2 octets. Dans un cas particulier, vous pourriez même être en mesure d'utiliser
loop
.la source
do{}while()
l'idiome de bouclage naturel est dans l'assemblage (en particulier pour l'efficacité). Notez également que 2 octetsjecxz
/jrcxz
avant une boucle fonctionne très bien avecloop
pour gérer le cas "doit s'exécuter zéro fois" de manière "efficace" (sur les CPU rares où illoop
n'est pas lent).jecxz
est également utilisable à l' intérieur de la boucle pour implémenter unwhile(ecx){}
, avecjmp
en bas.Utilisez les conventions d'appel qui vous conviennent
System V x86 utilise la pile et le système V x86-64 utilisations
rdi
,rsi
,rdx
,rcx
, etc. pour les paramètres d'entrée, etrax
que la valeur de retour, mais il est tout à fait raisonnable d'utiliser votre propre convention d'appel. __fastcall utiliseecx
etedx
comme paramètres d'entrée, et d' autres compilateurs / OS utilisent leurs propres conventions . Utilisez la pile et tous les registres comme entrée / sortie lorsque cela vous convient.Exemple: le compteur d'octets répétitifs , utilisant une convention d'appel intelligente pour une solution à 1 octet.
Méta: écriture d'entrée dans les registres , écriture de sortie dans les registres
Autres ressources: notes d'Agner Fog sur les conventions d'appel
la source
int 0x80
qui nécessite un tas de configuration.int 0x80
en code 32 bits, ousyscall
en code 64 bits, pour invoquersys_write
, est le seul bon moyen. C'est ce que j'ai utilisé pour Extreme Fibonacci . En code 64 bits__NR_write = 1 = STDOUT_FILENO
, vous pouvez doncmov eax, edi
. Ou si les octets supérieurs de EAX sont nuls,mov al, 4
en code 32 bits. Vous pourriez aussicall printf
ouputs
, je suppose, et écrire une réponse "x86 asm pour Linux + glibc". Je pense qu'il est raisonnable de ne pas compter l'espace d'entrée PLT ou GOT, ou le code de bibliothèque lui-même.char*buf
et de produire la chaîne en cela, avec un formatage manuel. par exemple, comme ceci (maladroitement optimisé pour la vitesse) asm FizzBuzz , où j'ai obtenu des données de chaîne dans le registre, puis les ai stockées avecmov
, parce que les chaînes étaient courtes et de longueur fixe.Utiliser des mouvements
CMOVcc
et des ensembles conditionnelsSETcc
C'est plus un rappel pour moi, mais des instructions de jeu conditionnel existent et des instructions de déplacement conditionnel existent sur les processeurs P6 (Pentium Pro) ou plus récents. Il existe de nombreuses instructions basées sur un ou plusieurs des indicateurs définis dans EFLAGS.
la source
cmov
a un opcode (2 octets0F 4x +ModR/M
), c'est donc 3 octets minimum. Mais la source est r / m32, vous pouvez donc charger conditionnellement en 3 octets. Autre que ramification,setcc
est utile dans plus de cas quecmovcc
. Considérez tout de même l'ensemble des instructions, pas seulement les instructions de base 386. (Bien que les instructions SSE2 et BMI / BMI2 soient si grandes qu'elles soient rarement utiles.rorx eax, ecx, 32
Est de 6 octets, plus long que mov + ror. Agréable pour les performances, pas pour le golf, sauf si POPCNT ou PDEP enregistre de nombreux isns)setcc
.Économiser sur
jmp
octets en organisant dans if / then plutôt que if / then / elseC'est certainement très basique, je pensais simplement que je publierais cela comme quelque chose à penser lors du golf. Par exemple, considérez le code simple suivant pour décoder un caractère de chiffre hexadécimal:
Cela peut être raccourci de deux octets en laissant un cas "alors" tomber dans un cas "sinon":
la source
sub
latence supplémentaire sur le chemin critique pour un cas ne fait pas partie d'une chaîne de dépendances en boucle (comme ici où chaque chiffre d'entrée est indépendant jusqu'à la fusion de blocs 4 bits ). Mais je suppose que +1 de toute façon. BTW, votre exemple a une optimisation manquée distincte: si vous avez besoin d'unmovzx
fin à la fin, alors n'utilisezsub $imm, %al
pas EAX pour profiter de l'encodage sans modrm à 2 octets deop $imm, %al
.cmp
en faisantsub $'A'-10, %al
;jae .was_alpha
;add $('A'-10)-'0'
. (Je pense que j'ai bien compris la logique). Notez qu'il'A'-10 > '9'
n'y a donc aucune ambiguïté. La soustraction de la correction d'une lettre encapsulera un chiffre décimal. Donc, c'est sûr si nous supposons que notre entrée est un hex valide, tout comme le vôtre.Vous pouvez extraire des objets séquentiels de la pile en définissant esi sur esp et en exécutant une séquence de lodsd / xchg reg, eax.
la source
pop eax
/pop edx
/ ...? Si vous devez les laisser sur la pile, vous pouvezpush
tous les récupérer après pour restaurer ESP, toujours 2 octets par objet sans avoir besoin de le fairemov esi,esp
. Ou vouliez-vous dire pour les objets de 4 octets en code 64 bits oùpop
obtiendrait 8 octets? BTW, vous pouvez même utiliserpop
pour boucler sur un tampon avec de meilleures performances quelodsd
, par exemple, pour un ajout de précision étendue dans Extreme FibonacciPour codegolf et ASM: utilisez les instructions, utilisez uniquement des registres, appuyez sur pop, minimisez la mémoire de registre
la source
Pour copier un registre 64 bits, utilisez
push rcx
;pop rdx
au lieu d'un octetmov
.La taille d'opérande par défaut de push / pop est 64 bits sans avoir besoin d'un préfixe REX.
(Un préfixe de taille d'opérande peut remplacer la taille push / pop par 16 bits, mais la taille d'opérande push / pop 32 bits n'est pas encodable en mode 64 bits même avec REX.W = 0.)
Si l'un ou les deux registres sont
r8
..r15
, utilisez-lesmov
car push et / ou pop auront besoin d'un préfixe REX. Dans le pire des cas, cela perd en fait si les deux ont besoin de préfixes REX. Évidemment, vous devriez généralement éviter r8..r15 de toute façon dans le golf de code.Vous pouvez garder votre source plus lisible tout en développant avec cette macro NASM . N'oubliez pas qu'il marche sur les 8 octets en dessous de RSP. (Dans la zone rouge dans x86-64 System V). Mais dans des conditions normales, c'est un remplacement direct pour 64 bits
mov r64,r64
oumov r64, -128..127
Exemples:
La
xchg
partie de l'exemple est parce que parfois vous devez obtenir une valeur dans EAX ou RAX et ne vous souciez pas de conserver l'ancienne copie. push / pop ne vous aide pas vraiment à échanger.la source