Esercizio 33array in assembly ARM, contare e invertire
In questa pagina 4
Testo (tipo di esercizio previsto dalla modalità di verifica del corso per la programmazione in assembly ARM: "sviluppare, analizzare e comprendere semplici programmi in linguaggio assembly ARM"; nessun tema pubblico di questo tipo è stato trovato, quindi il testo è originale). Si usa ARM a 32 bit con la convenzione di chiamata AAPCS (Stack e chiamata di funzioni in ARMChiamata con BL e ritorno con BX LR; convenzione di chiamata AAPCS (argomenti in r0-r3, risultato in r0, registri da preservare r4-r11); stack discendente pieno con PUSH e POP; record di attivazione; funzioni foglia e non foglia; esempio ricorsivo del fattoriale con evoluzione dello stack.Stack e chiamata di funzioni in ARM →): argomenti in r0–r3, risultato in r0, ritorno con BX lr; le funzioni qui sotto sono foglia e usano solo r0–r3 e r12, quindi non devono salvare niente.
Tradurre in assembly ARM le seguenti funzioni C, spiegando la scelta delle istruzioni:
int conta(const int *v, int n, int soglia) { // quanti elementi sono > soglia (con segno)
int c = 0;
for (int i = 0; i < n; i++)
if (v[i] > soglia) c++;
return c;
}
void inverti(int *v, int n) { // inverte l'array in loco
int *p = v, *q = v + n - 1;
while (p < q) {
int t = *p; *p = *q; *q = t;
p++; q--;
}
}Teoria: Array e strutture dati in assembly ARMArray in memoria e calcolo dell'indirizzo di un elemento; scorrimento con indice o con puntatore; esempi svolti (somma, massimo, copia); stringhe terminate da zero e calcolo della lunghezza; matrici per righe; struct con offset dei campi e allineamento.Array e strutture dati in assembly ARM →, Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →, Istruzioni ARM di accesso alla memoriaLDR e STR per parole, byte e mezze parole con e senza segno; modalità di indirizzamento con offset immediato o registro scalato, pre-indicizzata con write-back e post-indicizzata, con esempi di indirizzo effettivo; caricamento di indirizzi e costanti; LDM e STM.Istruzioni ARM di accesso alla memoria →.
1. conta
Registri. All'ingresso: r0 = v (indirizzo del primo elemento), r1 = n, r2 = soglia. Si usano r3 (elemento letto) e r12 (contatore c). Si scorre l'array con il puntatore r0 e si consuma n contando alla rovescia: il test n > 0 sostituisce i < n.
Scelte.
LDR r3, [r0], #4: post-indicizzato: legge*r0e poi incrementar0di 4 (unintè di 4 byte): fa in un'istruzioner3 = *p; p++.CMP r3, r2eADDGT: esecuzione condizionata: se soglia (confronto con segno, quindiGTe nonHI) si incrementa il contatore, senza saltare. Un salto su un corpo di una sola istruzione costerebbe di più (vedi Branch predictionTecniche per gli hazard sul controllo: stallo, flussi multipli, prelievo anticipato del bersaglio, loop buffer, salto ritardato; predizione statica e dinamica con bit di storia, predittore a 1 e a 2 bit con esempio su un ciclo, tabella dei bersagli (BTB); costo di una predizione errata.Branch prediction →).CMP r1, #0/BLE fine: il ciclo termina quando (con segno: anche negativo dà zero iterazioni, come in C).
conta: MOV r12, #0 @ c = 0
ciclo: CMP r1, #0
BLE fine @ n <= 0: finito
LDR r3, [r0], #4 @ r3 = *p; p++
CMP r3, r2
ADDGT r12, r12, #1 @ se *p > soglia: c++
SUB r1, r1, #1 @ n--
B ciclo
fine: MOV r0, r12 @ risultato in r0
BX lrTraccia con v = {5, -2, 9, 0, 7}, soglia = 3 (stato a ogni inizio di ciclo, prima del CMP r1, #0):
| passo | r0 (offset da v) |
r1 ( rimasto) |
r12 () |
elemento letto | ? |
|---|---|---|---|---|---|
| inizio | 0 | 5 | 0 | ||
dopo 5 |
4 | 4 | 1 | 5 | sì |
dopo -2 |
8 | 3 | 1 | -2 | no |
dopo 9 |
12 | 2 | 2 | 9 | sì |
dopo 0 |
16 | 1 | 2 | 0 | no |
dopo 7 |
20 | 0 | 3 | 7 | sì |
Con si esce subito e r12 = 0 è il risultato; la funzione restituisce 3.
Con indice invece del puntatore (più vicino al C): LDR r4, [r0, r3, LSL #2] con r3 come indice i e CMP r3, r1 / BGE fine per il test; ma serve un registro in più per l'elemento, e se non basta r12 ci si deve appoggiare a un registro callee-saved (r4–r11) da salvare con PUSH e ripristinare con POP: per una funzione foglia la versione col puntatore è più economica.
2. inverti
Registri. r0 = p (parte da v), r1 prima è n e poi diventa q, r2, r3 temporanei.
Scelte.
ADD r1, r0, r1, LSL #2:r1 = v + 4n, cioè l'indirizzo subito dopo l'ultimo elemento (offset scalato conLSL #2);SUB r1, r1, #4lo riporta a&v[n-1]. Per risulta e il ciclo non gira.- Il test
p < qè un confronto tra indirizzi: si usa il confronto senza segno,BHS(higher or same): si esce se . ConBGE(con segno) un indirizzo alto potrebbe apparire negativo. - Lo scambio sfrutta il post-indicizzato:
STR r3, [r0], #4scrive*q(già letto inr3) in*pe fa avanzarep;STR r2, [r1], #-4scrive il vecchio*p(inr2) in*qe fa arretrareq. Le due LDR vengono prima delle due STR, perciò non si perde nessuno dei due valori.
inverti: ADD r1, r0, r1, LSL #2 @ r1 = v + 4n
SUB r1, r1, #4 @ r1 = &v[n-1] = q
ciclo: CMP r0, r1
BHS fine @ p >= q (senza segno): finito
LDR r2, [r0] @ r2 = *p
LDR r3, [r1] @ r3 = *q
STR r3, [r0], #4 @ *p = vecchio *q; p++
STR r2, [r1], #-4 @ *q = vecchio *p; q--
B ciclo
fine: BX lrTraccia con (): e puntano a 1 e 5: scambio → 5 2 3 4 1; poi 2 e 4: 5 4 3 2 1; poi (entrambi su 3): BHS esce. Con , : 4 2 3 1, poi 4 3 2 1; poi : esce. Il numero di scambi è : tempo, spazio.
Verifica
Le due funzioni sono state assemblate ed eseguite in un emulatore ARM (stesso codice dei riquadri) su 500 casi casuali ciascuna, con da 0 a 12 e valori negativi, confrontando il risultato con quello di una funzione Python equivalente; in più si è controllato che i registri callee-saved r4–r6 restino invariati dopo la chiamata. Esiti sul caso della traccia: conta → 3; inverti su {1,2,3,4,5} → {5,4,3,2,1}; su {1,2,3,4} → {4,3,2,1}; su un array vuoto non cambia niente.
Errori comuni
- Passo sbagliato: avanzare di 1 invece di 4 nell'array di
int(LDR r3, [r0], #4). - Condizione con segno al posto di senza segno (e viceversa):
GT/LTper i valori,HI/HS/LO/LSper gli indirizzi e per i valori senza segno (vedi Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →). - Usare
r4–r11in una funzione foglia senza salvarli: li deve preservare il chiamante. - Scrivere con
STRprima di aver letto conLDRentrambi gli elementi nello scambio. - Dimenticare di sottrarre 4 dopo
ADD r1, r0, r1, LSL #2(si punterebbe oltre l'ultimo elemento). - Dimenticare
BX lr: l'esecuzione prosegue nelle istruzioni successive.
Versione ripasso
Tipo di esercizio previsto dalla modalità di verifica (programmazione in assembly ARM, testo originale). ARM 32 bit, AAPCS: argomenti r0–r3, risultato r0, ritorno BX lr; funzioni foglia con solo r0–r3, r12. Teoria: Array e strutture dati in assembly ARMArray in memoria e calcolo dell'indirizzo di un elemento; scorrimento con indice o con puntatore; esempi svolti (somma, massimo, copia); stringhe terminate da zero e calcolo della lunghezza; matrici per righe; struct con offset dei campi e allineamento.Array e strutture dati in assembly ARM →, Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →.
conta(v, n, soglia) (elementi soglia, con segno): scansione con puntatore e alla rovescia.
conta: MOV r12, #0
ciclo: CMP r1, #0
BLE fine
LDR r3, [r0], #4 @ r3 = *p; p++
CMP r3, r2
ADDGT r12, r12, #1 @ esecuzione condizionata, confronto con segno
SUB r1, r1, #1
B ciclo
fine: MOV r0, r12
BX lrCon {5, -2, 9, 0, 7} e soglia 3 → 3.
inverti(v, n): r1 = v + 4n - 4 ( = ultimo elemento), p = r0; test su indirizzi senza segno (BHS); LDR di entrambi gli elementi, poi STR r3, [r0], #4 e STR r2, [r1], #-4; scambi.
inverti: ADD r1, r0, r1, LSL #2
SUB r1, r1, #4
ciclo: CMP r0, r1
BHS fine
LDR r2, [r0]
LDR r3, [r1]
STR r3, [r0], #4
STR r2, [r1], #-4
B ciclo
fine: BX lrErrori comuni: passo 1 invece di 4; GT/LT sugli indirizzi; r4–r11 non salvati; STR prima delle due LDR; -4 dimenticato; BX lr omesso.