Utilizzare codifiche in formato breve per casi speciali per AL / AX / EAX e altri formati brevi e istruzioni a byte singolo
Gli esempi presuppongono la modalità 32/64 bit, in cui la dimensione dell'operando predefinita è 32 bit. Un prefisso delle dimensioni di un operando modifica le istruzioni in AX anziché EAX (o viceversa in modalità 16 bit).
inc/dec
un registro (diverso da 8 bit): inc eax
/ dec ebp
. (Non x86-64: i 0x4x
byte del codice operativo sono stati riutilizzati come prefissi REX, quindi inc r/m32
è l'unica codifica.)
8-bit inc bl
è 2 byte, utilizzando il inc r/m8
codice operativo + MODR / M operando codifica . Quindi usa inc ebx
per incrementare bl
, se è sicuro. (ad esempio se non è necessario il risultato ZF nei casi in cui i byte superiori potrebbero essere diversi da zero).
scasd
: e/rdi+=4
, richiede che il registro punti alla memoria leggibile. A volte utile anche se non ti interessa il risultato FLAGS (come cmp eax,[rdi]
/ rdi+=4
). E in modalità 64 bit, scasb
può funzionare come 1 byteinc rdi
, se lodsb o stosb non sono utili.
xchg eax, r32
: Questo è dove 0x90 NOP è venuto da: xchg eax,eax
. Esempio: riorganizzare 3 registri con due xchg
istruzioni in un cdq
/ idiv
loop per GCD in 8 byte in cui la maggior parte delle istruzioni sono a byte singolo, incluso un abuso di inc ecx
/ loop
anziché test ecx,ecx
/jnz
cdq
: estendi il segno EAX in EDX: EAX, ovvero copiando il bit alto di EAX su tutti i bit di EDX. Per creare uno zero con noto non negativo o ottenere uno 0 / -1 da aggiungere / sub o maschera con. lezione di storia x86: cltq
vs.movslq
e anche AT&T vs. Intel mnemonics per questo e per i relativi cdqe
.
lodsb / d : mi piace mov eax, [rsi]
/ rsi += 4
senza flag di clobbering. (Supponendo che DF sia chiaro, quali convenzioni di chiamata standard richiedono l'inserimento della funzione.) Anche stosb / d, a volte scas, e più raramente mov / cmps.
push
/ pop reg
. ad es. in modalità 64 bit, push rsp
/ pop rdi
è di 2 byte, ma mov rdi, rsp
necessita di un prefisso REX ed è di 3 byte.
xlatb
esiste, ma è raramente utile. Una grande tabella di ricerca è qualcosa da evitare. Inoltre non ho mai trovato un uso per le istruzioni AAA / DAA o altre istruzioni BCD confezionate o a 2 cifre ASCII.
1 byte lahf
/ sahf
sono raramente utili. Si potrebbe lahf
/ and ah, 1
in alternativa a setc ah
, ma non è in genere utile.
E per CF in particolare, è sbb eax,eax
necessario ottenere uno 0 / -1, o anche non documentato ma universalmente supportato a 1 byte salc
(impostare AL da Carry) che funziona efficacemente sbb al,al
senza influire sui flag. (Rimosso in x86-64). Ho usato SALC in User Appreciation Challenge # 1: Dennis ♦ .
1 byte cmc
/ clc
/ stc
(capovolgi ("complemento"), cancella o imposta CF) sono raramente utili, anche se ho trovato un usocmc
nell'aggiunta di precisione estesa con pezzi di base 10 ^ 9. Per impostare / cancellare incondizionatamente CF, di solito provvedere affinché ciò accada come parte di un'altra istruzione, ad esempio xor eax,eax
cancella CF e EAX. Non ci sono istruzioni equivalenti per altri flag di condizione, solo DF (direzione della stringa) e IF (interruzioni). La bandiera carry è speciale per molte istruzioni; i turni lo impostano, adc al, 0
possono aggiungerlo ad AL in 2 byte, e ho già menzionato il SALC non documentato.
std
/ cld
raramente sembra valerne la pena . Soprattutto nel codice a 32 bit, è meglio utilizzare solo dec
un puntatore e un mov
operando di origine memoria o un'istruzione ALU invece di impostare DF così lodsb
/ stosb
andare verso il basso anziché verso l'alto. Di solito, se hai bisogno del tutto verso il basso, hai ancora un altro puntatore che sale, quindi avresti bisogno di più di uno std
e cld
nell'intera funzione per usare lods
/ stos
per entrambi. Invece, basta usare le istruzioni di stringa per la direzione verso l'alto. (Le convenzioni di chiamata standard garantiscono DF = 0 all'ingresso della funzione, quindi puoi supporre che gratuitamente senza usare cld
.)
8086 storia: perché esistono queste codifiche
In originale 8086, AX era molto speciale: istruzioni piace lodsb
/ stosb
, cbw
, mul
/ div
e altri usano implicitamente. Questo è ancora il caso ovviamente; l'attuale x86 non ha eliminato nessuno dei codici operativi dell'8086 (almeno non uno di quelli ufficialmente documentati). Ma successivamente le CPU hanno aggiunto nuove istruzioni che hanno fornito modi migliori / più efficienti per fare le cose senza prima copiarle o scambiarle su AX. (O a EAX in modalità a 32 bit.)
ad esempio 8086 mancava aggiunte successive come movsx
/ movzx
caricare o spostare + segno-estensione o 2 e 3-operando imul cx, bx, 1234
che non producono un risultato di metà alto e non hanno operandi impliciti.
Inoltre, il principale collo di bottiglia dell'8086 era il recupero delle istruzioni, quindi l'ottimizzazione per la dimensione del codice era importante per le prestazioni di allora . Il designer ISA dell'8086 (Stephen Morse) ha speso molto spazio per la codifica di opcode in casi speciali per AX / AL, inclusi gli speciali codici di destinazione (E) AX / AL per tutte le istruzioni ALU di base immediate-src , solo opcode + immediate senza byte ModR / M. 2 byte add/sub/and/or/xor/cmp/test/... AL,imm8
o AX,imm16
o (in modalità 32 bit) EAX,imm32
.
Ma non c'è un caso speciale per EAX,imm8
, quindi la normale codifica ModR / M di add eax,4
è più breve.
Il presupposto è che se lavorerai su alcuni dati, li vorrai in AX / AL, quindi scambiare un registro con AX era qualcosa che potresti voler fare, forse anche più spesso che copiare un registro in AX con mov
.
Tutto ciò che riguarda la codifica delle istruzioni 8086 supporta questo paradigma, dalle istruzioni come lodsb/w
a tutte le codifiche di casi speciali per gli immediati con EAX al suo uso implicito anche per moltiplicare / dividere.
Non lasciarti trasportare; non è automaticamente una vittoria scambiare tutto con EAX, specialmente se è necessario utilizzare gli immediati con i registri a 32 bit anziché a 8 bit. O se è necessario interlacciare operazioni su più variabili contemporaneamente nei registri. O se stai usando le istruzioni con 2 registri, non immediatamente.
Ma tieni sempre a mente: sto facendo qualcosa che sarebbe più breve in EAX / AL? Posso riorganizzare così ho questo in AL, o sto attualmente sfruttando meglio AL con quello per cui lo sto già usando.
Mescola liberamente le operazioni a 8 e 32 bit per trarne vantaggio ogni volta che è sicuro farlo (non è necessario eseguirlo nel registro completo o altro).
push 200; pop edx
- 3 byte per l'inizializzazione.