Le chapitre précédent a montré que 20 mnémoniques représentent environ 93 % des instructions des programmes ordinaires. Ce chapitre passe ces instructions en revue, regroupées selon ce qu’elles font. Chaque groupe a une démo : avancez pas à pas et regardez les registres changer.
Déplacer des données
| Instruction | Effet |
|---|---|
mov dst, src | copier |
movzx dst, src | copier une valeur plus petite en remplissant le haut de zéros (non signé) |
movsx dst, src / movsxd | copier une valeur plus petite en remplissant le haut avec son bit de signe (signé) |
lea dst, [adresse] | calculer une adresse, sans lire la mémoire |
xchg a, b | échanger deux valeurs |
push src / pop dst | déposer une valeur sur la pile / en retirer une (rsp bouge de 8) |
- mov ebx, 0xff80
- movzx eax, bl ; bl = 0x80 → 128
- movsx ecx, bl ; bl = 0x80 → -128
- lea edx, [rbx+rbx*4] ; 5 × rbx, sans accès mémoire
- xchg eax, ecx
- push rax
- pop rdi
Le même octet, 0x80, devient 128 avec movzx et -128 (0xffffff80) avec movsx : tout dépend de si la source est vue comme non signée ou signée. En C, c’est la différence entre unsigned char et signed char. lea calcule rbx + rbx*4 = 5 × 0xff80 = 0x4fd80 sans toucher à la mémoire ni aux drapeaux, d’où son usage par les compilateurs pour les petites multiplications. push puis pop fait passer rax dans rdi par la pile, et rsp descend de 8 puis remonte.
Calculer
| Instruction | Effet |
|---|---|
add / sub | additionner / soustraire, en positionnant les drapeaux |
inc / dec | ajouter / retirer 1 (sans toucher à CF) |
neg | changer le signe : 0 − x |
imul dst, src (et une forme à 3 opérandes) | multiplication signée, en gardant la moitié basse |
mul src / imul src | multiplication complète : rdx:rax = rax × src |
div src / idiv src | diviser rdx:rax par src : quotient dans rax, reste dans rdx |
cdq / cqo | étendre le signe de eax dans edx (rax dans rdx) avant une division signée |
- mov eax, 100
- mov ecx, 7
- add eax, ecx ; 107
- sub eax, 9 ; 98
- imul eax, ecx ; 686
- add eax, 3 ; 689
- cdq ; edx = signe de eax (0)
- idiv ecx ; 689 / 7 → eax = 98, edx = 3
- neg eax ; -98
La division est à part. Elle ne prend pas d’opérande de destination : elle divise toujours la valeur de 64 bits edx:eax (ou celle de 128 bits rdx:rax), et elle écrit deux résultats, le quotient dans eax et le reste dans edx. C’est pourquoi une division signée est presque toujours précédée de cdq ou cqo dans le code compilé. Diviser par zéro ne produit pas de valeur : cela déclenche une exception du processeur, que l’OS transforme en plantage (SIGFPE sous Linux).
La division est aussi lente : souvent 10 à 40 cycles selon le CPU et la taille des opérandes, contre 1 pour une addition. Les compilateurs l’évitent quand ils le peuvent : diviser par une constante se compile en général en une multiplication par un « nombre magique » suivie d’un décalage.
Bits et décalages
| Instruction | Effet |
|---|---|
and / or / xor / not | ET, OU, OU exclusif, NON bit à bit |
shl | décalage à gauche : × 2ⁿ |
shr | décalage à droite en remplissant de zéros : ÷ 2ⁿ non signé |
sar | décalage à droite en recopiant le bit de signe : ÷ 2ⁿ signé, arrondi vers le bas |
rol / ror | rotation : les bits qui sortent d’un côté rentrent de l’autre |
bt, popcnt, bsf… | tester un bit, compter les 1, trouver le premier 1 |
- mov eax, 0xb6 ; 1011 0110
- and eax, 0x0f ; garder les 4 bits bas : 0110 = 6
- or eax, 0x80 ; mettre le bit 7 à 1 : 0x86
- xor eax, 0xff ; inverser les 8 bits bas : 0x79
- shl eax, 4 ; × 16 : 0x790
- shr eax, 8 ; ÷ 256 : 7
- mov ecx, -16
- sar ecx, 2 ; -16 / 4 = -4 (signe conservé)
- mov edx, -16
- shr edx, 2 ; 0x3ffffffc (signe perdu)
andavec un masque garde certains bits,orles met à 1 etxorles inverse.xor eax, eaxest la façon habituelle de mettre un registre à 0, ettest eax, eaxcelle de vérifier s’il vaut 0.saretshrne diffèrent que sur les nombres négatifs.sargarde -16 négatif (-4) ;shrle traite comme un énorme nombre non signé.sararrondit aussi vers moins l’infini :-17 sar 2vaut -5, alors que-17 / 4vaut -4 en C. C’est pourquoi une division signée par une puissance de deux se compile avec quelques instructions de plus autour dusar.
Comparer et choisir
Ces instructions comparent, puis agissent sur le résultat à travers les drapeaux. Le chapitre sur les drapeaux détaille chaque condition.
| Instruction | Effet |
|---|---|
cmp a, b | calculer a − b, positionner les drapeaux, jeter le résultat |
test a, b | calculer a ET b, positionner les drapeaux, jeter le résultat |
jcc étiquette | sauter si la condition cc est vraie : je, jne, jl, jg, jb, ja… |
setcc r8 | mettre un octet à 1 ou 0 selon la condition |
cmovcc dst, src | copier seulement si la condition est vraie, sans saut |
- mov eax, 5
- cmp eax, 3 ; 5 − 3 : plus grand
- setg bl ; bl = 1
- mov ecx, 100
- cmovg eax, ecx ; plus grand → eax = 100
- test eax, eax ; eax vaut-il zéro ?
- jz done ; non : on continue
- mov edx, 1
- done:
- nop
cmp suivi d’un saut conditionnel, c’est ainsi que se compilent tous les if, les boucles et les switch. setcc et cmovcc prennent le même genre de décision sans sauter, ce qui évite les mauvaises prédictions de branchement.
Appels et sauts
| Instruction | Effet |
|---|---|
jmp cible | sauter sans condition ; la cible peut aussi être dans un registre (jmp rax) |
call cible | empiler l’adresse de retour, puis sauter |
ret | dépiler l’adresse de retour dans rip |
leave | défaire un cadre de pile : mov rsp, rbp + pop rbp |
- _start:
- mov edi, 6
- call triple ; empile l’adresse de retour
- jmp end
- mov eax, 0 ; jamais exécuté
- triple:
- lea eax, [rdi+rdi*2]
- ret ; retour au jmp
- end:
- nop
call empile l’adresse du jmp avant de sauter à triple. ret la dépile, et l’exécution reprend juste après l’appel, avec eax = 18. La façon dont circulent les arguments et la valeur de retour est le sujet du chapitre sur les conventions d’appel.
Le reste du jeu d’instructions
Quelques familles apparaissent plus rarement, mais vous les croiserez :
- Les instructions de chaîne —
movsb,stosb,cmpsb,scasbavec un préfixerep— copient, remplissent, comparent ou parcourent des blocs entiers de mémoire en une seule instruction.rep stosbsert àmemset. Le chapitre sur le microcode montre comment une telle instruction devient une boucle. - Les instructions système :
syscalldemande un service au système d’exploitation,int3est le point d’arrêt d’un octet qu’insèrent les débogueurs (0xcc),hltarrête le CPU jusqu’à la prochaine interruption, etnopne fait rien — utile comme remplissage. - Virgule flottante et SIMD : les instructions SSE et AVX travaillent sur les registres
xmm,ymmetzmm. Elles traitent lesfloatetdouble(addsd,mulss…) et opèrent sur plusieurs valeurs à la fois (paddd,vaddps…). L’ancienne pile flottante x87 survit surtout dans le code 32 bits.
À retenir
- Déplacer des données :
mov,movzx/movsx(élargissement non signé ou signé),lea(calcul d’adresse sans mémoire),push/pop. - Calculer :
add,sub,inc,dec,neg,imul. La division utilise implicitementrdx:raxet rend un quotient et un reste. - Bits :
and/or/xoravec des masques.shl/shr/sarmultiplient et divisent par des puissances de deux ;sargarde le signe. - Décider :
cmp/testpositionnent les drapeaux, puisjcc,setccoucmovccagissent en conséquence. - Contrôle :
jmp,call,ret— plus les instructions de chaîne, système et SIMD pour les tâches spéciales.