Skip to content

Usare LEA su valori che non sono indirizzi/puntatori?

Ciao, abbiamo trovato la risposta alla tua ricerca, scorri e la troverai di seguito.

Soluzione:

lea (si veda la voce del manuale del set di istruzioni di Intel) è un'istruzione shift-and-add che utilizza la sintassi dell'operando di memoria e la codifica della macchina. Questo spiega il nome, ma non è l'unica cosa per cui è utile. Non accede mai effettivamente alla memoria, quindi è come usare & in C.

Si veda ad esempio Come moltiplicare un registro per 37 usando solo 2 istruzioni leali consecutive in x86?

In C, è come uintptr_t foo = &arr[idx]. Si noti il valore & per ottenere il risultato di arr + idx, compreso il ridimensionamento per la dimensione dell'oggetto di arr. In C, questo sarebbe un abuso della sintassi e dei tipi del linguaggio, ma in assembly x86 i puntatori e gli interi sono la stessa cosa. Tutto è solo byte, e sta al programma mettere le istruzioni nel giusto ordine per ottenere risultati utili.


Il progettista/architetto originale del set di istruzioni dell'8086 (Stephen Morse) potrebbe o meno aver avuto in mente la matematica dei puntatori come caso d'uso principale, ma i compilatori moderni la considerano solo un'altra opzione per fare aritmetica su puntatori / interi, ed è così che dovreste considerarla anche voi.

(Si noti che le modalità di indirizzamento a 16 bit non includono gli spostamenti, solo [BP|BX] + [SI|DI] + disp8/disp16, quindi LEA non era ma utile per la matematica senza puntatore prima del 386. Vedere questa risposta per ulteriori informazioni sulle modalità di indirizzamento a 32/64 bit, anche se questa risposta utilizza la sintassi Intel come [rax + rdi*4] invece della sintassi AT&T utilizzata in questa domanda. Il codice macchina x86 è lo stesso indipendentemente dalla sintassi utilizzata per crearlo).

Forse gli architetti dell'8086 volevano semplicemente esporre l'hardware di calcolo degli indirizzi per usi arbitrari perché potevano farlo senza usare molti transistor in più. Il decodificatore deve già essere in grado di decodificare le modalità di indirizzamento e altre parti della CPU devono essere in grado di eseguire i calcoli degli indirizzi. Mettere il risultato in un registro invece di usarlo con un valore di registro di segmento per l'accesso alla memoria non richiede molti transistor in più. Ross Ridge conferma che LEA sull'8086 originale riutilizza l'hardware di decodifica e calcolo dell'indirizzo effettivo della CPU.


Si noti che la maggior parte dei CPU moderne eseguono LEA sulle stesse ALU delle normali istruzioni add e shift.. Hanno AGU (unità di generazione degli indirizzi) dedicate, ma le usano solo per gli operandi di memoria veri e propri. Un'eccezione è rappresentata da Atom in-order; LEA viene eseguito prima nella pipeline rispetto alle ALU: gli ingressi devono essere pronti prima, ma anche le uscite sono pronte prima. Le CPU con esecuzione out-of-order (la stragrande maggioranza dei moderni x86) non vogliono che il LEA interferisca con i carichi/memorizzazioni effettivi, quindi lo eseguono su un'ALU.

lea ha una buona latenza e un buon throughput, ma non quanto il throughput di add o mov r32, imm32 sulla maggior parte delle CPU, quindi utilizzare solo lea quando è possibile salvare un'istruzione con esso invece di add. (Vedere la guida ai microarchi x86 e il manuale di ottimizzazione asm di Agner Fog).


L'implementazione interna è irrilevante, ma è sicuro che la decodifica degli operandi in LEA condivide i transistor con le modalità di indirizzamento della decodifica per qualsiasi altra istruzione. (Quindi c'è un riutilizzo/condivisione dell'hardware anche nelle CPU moderne che non eseguonolea su una AGU). Qualsiasi altro modo di esporre un'istruzione shift-and-add a più ingressi avrebbe richiesto una codifica speciale per gli operandi.

Così il 386 ottenne un'istruzione shift-and-add ALU "gratis" quando estese le modalità di indirizzamento per includere lo scaled-index, e la possibilità di usare qualsiasi registro in una modalità di indirizzamento rese LEA molto più facile da usare anche per i non puntatori.

x86-64 ha ottenuto un accesso economico al contatore del programma (invece di dover leggere quello che call spinto) "gratuitamente" tramite LEA perché ha aggiunto la modalità di indirizzamento RIP-relativo, rendendo l'accesso ai dati statici significativamente più economico nel codice x86-64 indipendente dalla posizione rispetto al PIC a 32 bit. (La modalità RIP-relativa richiede un supporto speciale nelle ALU che gestiscono LEA e nelle AGU separate che gestiscono gli indirizzi di caricamento e memorizzazione. Ma non è stata necessaria una nuova istruzione).


È altrettanto valida per l'aritmetica arbitraria che per i puntatori, quindi è un errore pensare che oggi sia destinata ai puntatori.. Non è un "abuso" o un "trucco" usarlo per i non puntatori, perché in linguaggio assembly tutto è un intero. Ha un throughput inferiore rispetto a add, ma è abbastanza economico da poter essere utilizzato quasi sempre quando risparmia anche una sola istruzione. Ma può risparmiare fino a tre istruzioni:

;; Intel syntax.
lea  eax, [rdi + rsi*4 - 8]   ; 3 cycle latency on Intel SnB-family
                              ; 2-component LEA is only 1c latency

 ;;; without LEA:
mov  eax, esi             ; maybe 0 cycle latency, otherwise 1
shl  eax, 2               ; 1 cycle latency
add  eax, edi             ; 1 cycle latency
sub  eax, 8               ; 1 cycle latency

Su alcune CPU AMD, anche un LEA complesso ha una latenza di soli 2 cicli, ma la sequenza di 4 istruzioni avrebbe una latenza di 4 cicli da esi al ciclo finale eax ... pronto. In ogni caso, si risparmiano 3 uops per la decodifica e l'emissione da parte del front-end, che occupano spazio nel buffer di riordino fino al ritiro.

lea ha diversi vantaggi importanti soprattutto nel codice a 32/64 bit, dove le modalità di indirizzamento possono utilizzare qualsiasi registro e possono essere spostate:

  • non distruttivo: uscita in un registro che non è uno degli ingressi. A volte è utile come semplice copia e aggiungi come lea 1(%rdi), %eax o lea (%rdx, %rbp), %ecx.
  • può fare 3 o 4 operazioni con una sola istruzione (vedi sopra).
  • Matematica senza modificare gli EFLAGS può essere utile dopo un test prima di un cmovcc. O forse in un ciclo add-with-carry su CPU con stalli di flag parziali.
  • x86-64: il codice indipendente dalla posizione può usare un LEA RIP-relativo per ottenere un puntatore a dati statici.

    7 byte lea foo(%rip), %rdi è leggermente più grande e più lento di mov $foo, %edi (5 byte), quindi preferite mov r32, imm32 nel codice dipendente dalla posizione su sistemi operativi in cui i simboli si trovano nei 32 bit inferiori dello spazio degli indirizzi virtuali, come Linux. Potrebbe essere necessario disabilitare l'impostazione predefinita di PIE in gcc per utilizzarlo.

    Nel codice a 32 bit, mov edi, OFFSET symbol è analogamente più breve e più veloce di lea edi, [symbol]. (Tralasciare l'opzione OFFSET nella sintassi NASM). RIP-relativo non è disponibile e gli indirizzi si adattano a un immediato a 32 bit, quindi non c'è motivo di considerare lea invece di mov r32, imm32 se si ha bisogno di inserire indirizzi di simboli statici nei registri.

A parte il LEA RIP-relativo in modalità x86-64, tutti questi si applicano ugualmente al calcolo dei puntatori rispetto al calcolo di aggiunte/spostamenti di interi non puntatori.

Si veda anche il wiki dei tag x86 per guide e manuali sull'assemblaggio e informazioni sulle prestazioni.


Dimensione dell'operazione vs dimensione dell'indirizzo per x86-64 lea

Vedere anche Quali operazioni intere a complemento a 2 possono essere utilizzate senza azzerare i bit alti degli ingressi, se si desidera solo la parte bassa del risultato? La dimensione dell'indirizzo a 64 bit e la dimensione dell'operando a 32 bit è la codifica più compatta (senza prefissi aggiuntivi), quindi è da preferire lea (%rdx, %rbp), %ecx quando possibile, invece di 64 bit lea (%rdx, %rbp), %rcx o 32 bit lea (%edx, %ebp), %ecx.

x86-64 lea (%edx, %ebp), %ecx è sempre uno spreco di prefisso di dimensione dell'indirizzo rispetto a lea (%rdx, %rbp), %ecxma la dimensione dell'indirizzo/operando a 64 bit è ovviamente necessaria per fare matematica a 64 bit. (Il disassemblatore objconv di Agner Fog mette in guardia da inutili prefissi di dimensione dell'indirizzo su LEA con dimensione dell'operando di 32 bit).

Tranne forse su Ryzen, dove Agner Fog riporta che la dimensione dell'operando a 32-bit lea in modalità a 64 bit ha un ciclo di latenza in più. Non so se sovrascrivere l'address-size a 32 bit possa velocizzare LEA in modalità a 64 bit se si ha bisogno di troncare a 32 bit.


Questa domanda è una quasi duplicazione di quella molto votata Qual è lo scopo dell'istruzione LEA?, ma la maggior parte delle risposte la spiega in termini di calcolo dell'indirizzo sui dati effettivi del puntatore. Questo è solo un uso.

leaq non hanno di operare sugli indirizzi di memoria e calcola un indirizzo, ma in realtà non legge dal risultato, quindi fino a quando un mov o simili, è solo un modo esoterico per aggiungere un numero, più 1, 2, 4 o 8 volte un altro numero (o lo stesso numero in questo caso). È spesso "abusato" per scopi matematici, come si vede. 2*%rdi+%rdi è solo 3 * %rdiquindi sta calcolando x * 3 senza coinvolgere l'unità moltiplicatrice della CPU.

Allo stesso modo, lo spostamento a sinistra, per i numeri interi, raddoppia il valore per ogni bit spostato (ogni zero aggiunto a destra), grazie al modo in cui funzionano i numeri binari (allo stesso modo in cui nei numeri decimali, l'aggiunta di zeri a destra moltiplica per 10).

Si tratta quindi di un abuso del metodo leaq per ottenere la moltiplicazione per 3, poi sposta il risultato per ottenere un'ulteriore moltiplicazione per 4, per un risultato finale di moltiplicazione per 12 senza mai usare effettivamente un'istruzione di moltiplicazione (che presumibilmente crede che funzionerebbe più lentamente, e per quanto ne so potrebbe avere ragione; giudicare il compilatore è di solito una partita persa).

: Per essere chiari, non si tratta di un abuso nel senso di abuso ma solo di usarlo in un modo che non è chiaramente in linea con lo scopo implicito che ci si aspetterebbe dal suo nome. Va bene al 100% usarlo in questo modo.

LEA serve a calcolare l'indirizzo. Non dereferenzia l'indirizzo della memoria.

Dovrebbe essere molto più leggibile nella sintassi Intel.

m12(long):
  lea rax, [rdi+rdi*2]
  sal rax, 2
  ret

Quindi la prima riga è equivalente a rax = rdi*3
Quindi lo spostamento a sinistra consiste nel moltiplicare rax per 4, il che risulta in rdi*3*4 = rdi*12

Sezione recensioni e valutazioni



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.