Programmazione Assembly. RISC e CISC Esecuzione sequenziale Esecuzione condizionata Cicli Subroutine

Save this PDF as:
 WORD  PNG  TXT  JPG

Dimensione: px
Iniziare la visualizzazioe della pagina:

Download "Programmazione Assembly. RISC e CISC Esecuzione sequenziale Esecuzione condizionata Cicli Subroutine"

Transcript

1 Programmazione Assembly RISC e CISC Esecuzione sequenziale Esecuzione condizionata Cicli Subroutine

2 RISC e CISC Possiamo classificare le architetture di microprocessore in due classi principali RISC CISC RISC: Reduced Instruction Set Computer Architettura semplice Poche istruzioni semplici Molti registri Operazioni aritmetiche e logiche solo sui registri (architettura load/store) CICS: Complex Instruction Set Computer Architettura complessa Molte istruzioni complesse Pochi registri Operazioni aritmetiche e logiche direttamente da memoria

3 Esecuzione sequenziale Consideriamo un primo semplice esempio Calcola la somma di due valori costanti Codice C #define A 10 #define B 20 int C; C = A + B; Codice assembly A EQU 10 B EQU 20 C DATAWORD 0 L0 MOVE, A MOVE, B ADD, STORE C, END L0 A lato è riportato il programma in memoria Viene riservata memoria solo per C C 0 L0 MOVE, A MOVE, B ADD, STORE C, 0x100 0x104 0x108 0x10C 0x110 0x114 0x118 0x11C 0x124 0x128 0x12C 0x130 0x134 0x138 0x13C 0x140 0x144 0x148 0x14C 0x150 0x154 0x158 0x15C 0x160 0x164 0x168 0x16C

4 Esecuzione sequenziale Il programma assembly visto si riferisce ad una architettura RISC I dati vengono caricati nei registri Si esegue l'operazione Il risultato è in un registro Il valore registro viene salvato in memoria Nel caso di una architettura CISC L'operazione di somma avviene direrttamente tra un registro ed una costante A EQU 10 B EQU 20 C DATAWORD 0 L0 MOVE, A ADD, B STORE C, END L0 C 0 L0 MOVE, A ADD, B STORE C, 0x100 0x104 0x108 0x10C 0x110 0x114 0x118 0x11C 0x124 0x128 0x12C 0x130 0x134 0x138 0x13C 0x140 0x144 0x148 0x14C 0x150 0x154 0x158 0x15C 0x160 0x164 0x168 0x16C

5 Esecuzione sequenziale Consideriamo la somma di due locazioni di memoria Assegnamento esplicito degli indirizzi Dei dati Del codice Riserviamo memoria per le variabili mediante le opportune direttive del linguaggio assembly Iniziamo dalla versione RISC Le operazioni avvengono solo sui registri ORIGIN A DATAWORD 111 B DATAWORD 222 C DATAWORD 0 ORIGIN 0x140 L0 LOAD, A LOAD, B ADD, STORE C, END L0 A 111 B 222 C 0 L0 LOAD, A LOAD, B ADD, STORE C, 0x100 0x104 0x108 0x10C 0x110 0x114 0x118 0x11C 0x124 0x128 0x12C 0x130 0x134 0x138 0x13C 0x140 0x144 0x148 0x14C 0x150 0x154 0x158 0x15C 0x160 0x164 0x168 0x16C

6 Esecuzione sequenziale Lo stesso programma per una architettura CISC Un solo operando in memoria ORIGIN A DATAWORD 111 B DATAWORD 222 C DATAWORD 0 ORIGIN 0x140 L0 LOAD, A ADD, B STORE C, END L0 Due operandi in memoria(istruzioni a 3 operandi) ORIGIN A DATAWORD 111 B DATAWORD 222 C DATAWORD 0 ORIGIN 0x140 L0 ADD C, A, B END L0

7 Esecuzione condizionale Consideriamo un semplice costrutto condizionale if( 2 * X > 10 ) Y = 1; else Y = 0; Z = Y + 3; Analizziamone la struttura in termini di percorsi di esecuzione possibili Ne consegue che sono necessarie due etichette if( 2 * X > 10 ) TRUE Y = 1; FALSE Y = 0; L1 Fall-through L2 Z = Y + 3;

8 Esecuzione condizionale Il codice assembly che ne risulta è il seguente ORIGIN X DATAWORD 10 Y DATAWORD 0 Z DATAWORD 0 ORIGIN 0x140 L0 LOAD, X! Calculates the test esxpression X*2-10 MUL, #2 SUB, #10! If condition BLE L1! Then-branch STORE Y, #1 B L2! Else-branch L1 STORE Y, #0! After the if-then-else construct L2 LOAD, Y ADD, #3 STORE Z, END L0

9 Cicli Inizializzazione di un array a valori costanti Mediante direttive assembly ORIGIN V0 DATAWORD 0 V1 DATAWORD 1 V63 DATAWORD 63 ORIGIN 0x400 L0 Mediante un ciclo ORIGIN V RESERVE 256 ORIGIN 0x400 L0 LOAD, V LOAD, 0 L1 STORE (), ADD, #4 ADD, #1 CMP, #63 BL L1 0x100 0x104 0x108 0x10C 0x110 0x114 0x118 0x11C V0??? V1??? 0x124 0x128 V63??? 0x21C 0x220 L0 0x400 0x404 0x408 0x40C 0x410 0x414 0x418 0x41C 0x420 0x424 0x428 0x42C

10 Cicli Modalità di indirizzamento con auto incremento Il registro contiene l'indirizzo del dato L0 LOAD, V LOAD, 0 STORE ()+, ADD, #1 CMP, #63 BL L0 Riempimento al rovescio per evitare il confronto esplicito con valore 63 L'istruzione ADD aggiuntiva viene eseguita una sola volta! punta al primo elemento dopo l'array poiché l'autodecremento avviene prima di usare il dato L0 LOAD, V ADD, #256 LOAD, 64 STORE -(), SUB, #1 BGE L0

11 Subroutine L'utilizzo di subroutine comporta alcune difficoltà Come salvare l'indirizzo a cui ritornare dopo la fine della subroutine? Come passare i parametri? Come allocare spazio per le variabili locali della subroutine? Come passare il valore di ritorno? Come lasciare invariato il valore dei registri nel chiamante a cavallo della subroutine? Ci sono molti modi per affrontare questi problemi, che dipendono da Alcuni aspetti del linguaggio sorgente Scope statico o dinamico, nesting, Alcuni aspetti del lingauggio assembly Salvataggio automatico del program counter, salvataggio dei registri, Il tipo di microprocessore Disponibilità di registri speciali, convenzioni di chiamata, Le scelte del programmatore Oppure dello sviluppatore di compilatori

12 Subroutine: Indirizzo di ritorno La funzione chiamata deve ritornare in punti diversi a seconda del chiamante E' necessario salvare l'indirizzo al quale si è verificata la chiamata E' equivalente, ma si preferisce salvare l'indirizzo successivo alla chiamata E' esattamente il punto da cui deve riprendere l'esecuzione 0x100 0x104 STORE X, 0x108 CALL FOO 0x10C MOVE, 0x110 0x114 0x118 ritorno chiamata 0x400 FOO 0x404 0x408 0x40C 0x410 0x414 RET 0x418 0x41C Non è possibile salvare l'indirizzo in un registro Chiamate annidate dovrebbero usare registri diversi Non è possibile poiché il chiamato deve sempre sapere quale registro si sta usando E' necessario ricorrere allo stack Il chiamante pone l'indirizzo di ritorno sullo stack Il chiamato usa tale indirizzo per passare il controllo al punto corretto

13 Subroutine: Indirizzo di ritorno Consideriamo le chiamate annidate e analizziamo lo stack MAIN chiama FOO che chiama BAR MAIN 0x100 0x CALL FOO 0x108 0x10C 0x x10C (in MAIN) 0x114 0x118 0x11C 2 5 BAR 0x x128 RET 0x21C 0x10C (in MAIN) 0x220 0x400 0x404 0x408 FOO 0x40C CALL BAR 0x410 0x414 RET 0x x414 (in FOO) 0x10C (in MAIN) 0x41C 0x420

14 Subroutine: Indirizzo di ritorno Secondo questo schema dunque L'istruzione "CALL addr" equivale alla sequenza: MOVE, PC ADD, #8 B addr Si noti che quando PC viene copiato in, esso punta all'istruzione ADD Quindi PC+4 punta all'istruzione di salto E infine PC+8 punta alla prima istruzione dopo la "CALL" L'istruzione "RET" equaivale alla sequenza POP MOVE PC, Si noti che l'istruzione MOVE PC, è equivalente ad un salto Infatti il suo risultato è quello di modificare il program counter Nella maggior parte dei processori le istruzioni CALL e RET hanno questo effetto La rappresentazione assembly "equivalente" è solo indicativa

15 Subroutine: Passaggio dei parametri Il passaggio dei parametri alle subroutine può avvenire in modi diversi Nei registri Sullo stack Passaggio dei parametri nei registri Molto semplice e veloce Dato che alcuni registri sono usati per i parametri, il chiamante deve salvare i registri prima di entrare in una subroutine Il numero di registri è spesso piuttosto limitato Si possono passare per valore solo pochi parametri di piccole dimensioni Per parametri di grandi dimansioni si ricorre al passaggio per indirizzo Passaggio dei parametri sullo stack Più lento, in quanto richiede accesso alla memoria Molto generale Non presenta limitazioni riguardo al numero e dimensione dei parametri

16 Subroutine: Passaggio dei parametri nei registri Passaggio mediante registri Sum = AddInt( 10, 20 ); int AddInt( int a, int b ) { return a + b; } La sequenza di chiamata richiede che Nel chiamante Si salvino i registri prima della chiamata e si recuperi il loro valore al ritorno Si copino 10 e 20 i parametri nei registri ed Nel chiamato Si copi il valore di ritono in STORE T0, STORE T1, MOVE, #10 MOVE, #20 CALL AddInt STORE Sum, LOAD, T0 LOAD, T1 AddInt ADD, RET

17 Subroutine: Passaggio dei parametri sullo stack E' necessario impialre i parametri prima della chiamata Per l'esempio appena visto, lo stack appena dopo la chiamata è: SP: indirizzo di ritorno Quindi i parametri si trovano al di sotto della cima dello stack Il chiamato accede ai parametri mediante indirizzamento con indice e spiazzamento Rispetto allo stack pointer Con spiazzamento positivo (la freccia rossa in figura indica gli indirizzi crescenti) Il chiamato pone quindi il risultato sullo stack Questa soluzione è scorretta poiché sulla cima dello stack non c'è lindirizzo di ritorno SP: valore di ritorno indirizzo di ritorno

18 Subroutine: Passaggio dei parametri sullo stack Il valore di ritono deve essere salvato altrove Nello stack, al posto di uno dei parametri Si accede a tale locazione mediante spiazzamento rispetto allo stack pointer La subroutine conosce la posizione del primo parametro (Stack pointer) + (Dimensione indirizzo di ritorno) + (Dimensione parametri) SP: indirizzo di ritorno valore di ritorno I parametri possono essere sovrascritti La loro "vita" termina alla fine della subroutine Se il valore di ritorno ha dimensioni maggiori dei parametri Questo approccio richiede un'estensione In genere si riserva esplicitamente dello spazio sullo stack per il valore di ritorno In questo contestto tratteremo solo il caso di parametri e valori si ritorno di piccole dimensioni

19 Subroutine: Passaggio dei parametri sullo stack Al ritorno nel chiamante L'istruzione RET del chiamato ha tolto l'indirizzo di ritorno dallo stack che quindi é: SP valore di ritorno Il chiamante Conosce la posizione sullo stack del valore di ritorno sullo (Stack pointer) + (Dimensione dei parametri) Preleva il valore per indirizzamento indiretto rispetto allo stack pointer Riposiziona lo stack pointer alla posizione che aveva prima della chiamata Sullo stack sono state eseguite istruzioni PUSH per impilare i parametri Nessuna corrispondente istruzione di POP è stata invece eseguita Il riposizionamento avviene modificando in modo esplicito lo stack pointer

20 Subroutine: Passaggio dei parametri nei registri Passaggio mediante stack PUSH #10 PUSH #20 CALL AddInt LOAD, 4(SP) STORE Sum, ADD SP, #4 AddInt LOAD, 8(SP) LOAD, 4(SP) ADD, STORE 8(SP), RET Si nota che la subroutine utilizza i registi I valori che tali registri avevano nel chiamante vanno perduti E' necessario salvare i registri esplicitamente Anche in questo caso si utilizza lo stack In genere è il chiamato Ad eseguire il salvataggio appena ne inizia l'esecuzione Ad eseguire il ripristino prima di ritornare al chiamante

21 Subroutine: Passaggio dei parametri sullo stack Supponendo di avere solamente 4 registri Lo stato dello stack appena dopo la chiamata è SP Il chiamato salva i registri Lo stato dello stack prima di iniziare il "corpo" della subroutine è SP

22 Subroutine: Passaggio dei parametri nei registri La sequenza di chiamata diviene dunque Il chimante non cambia Il chiamato si occupa si gestire lo stack per il salvataggio dei registri Si noti che sono cambiati gli offset dei parametri PUSH #10 PUSH #20 CALL AddInt LOAD, 8(SP) STORE Sum, ADD SP, #4 AddInt PUSH PUSH PUSH PUSH LOAD, 20(SP) LOAD, 16(SP) ADD, STORE 20(SP), POP POP POP POP RET

23 Subroutine: Variabili locali Non è necessario riservare permanentemente la memoria per tutte la variabili locali delle diverse subroutine Tali variabili "vivono" solo per il tempo in cui la funzione è attiva Per rendere le variabili locali Si riserva spazio dinamicamente sullo stack Si tratta di decidere in quale posizione allocare le variabili In altre parole quando spostare lo stack pointer Per convenzione Molto spesso tali variabili vengono allocate tra l'indirizzo di rientro e i registri salvati Tale scelta non è l'unica possibile

24 Subroutine: Variabili locali Consideriamo una versione leggermente diversa della routine di somma int AddInt( int a, int b ) { int c; c = a + b return c; } Il chiamato diviene dunque: AddInt ADD SP, #-4 PUSH PUSH PUSH PUSH LOAD, 24(SP) LOAD, 20(SP) ADD, STORE 24(SP), POP POP POP POP ADD SP, #4 RET

25 Subroutine: Variabili locali Lo stato dello stack durante l'esecuzione del chiamato è dunque il seguente??? SP Spazio riservato per la variabile locale "c" Anche questa modifica alla sequenza di chiamata Modifica gli offset per accedere ai parametri

26 Subroutine: Area di attivazione Come visto, lo stack contiene molti dati di tipo diverso Parametri e valore di ritorno, indirizzo di ritorno, variabili locali, registri salvati Nel caso più generale è necessario disporre di un nuovo registro speciale Il frame pointer L'area dello stack relativa ad una subroutine è detta "area di attivazione" Anche detta "stack frame" o "activation record" La sua struttura generale è la seguente Registri salvati Variabili locali Frame pointer del chiamante Indirizzo di ritorno Parametri e valore di ritorno del chiamante SP

27 Subroutine: Area di attivazione Il frame pointer o FP Punta ad una posizione specifica all'interno dello stack In particolare, punta alla posizione in cui è stato salvato il frame pointer del chiamante In questo modo è possibile ripristinare il suo valore al ritorno da una funzione Questa tecnica implementa lo "static linking" tra gli stack frame Lo stack è dunque Registri salvati Variabili locali Frame pointer del chiamante Indirizzo di ritorno Parametri e valore di ritorno del chiamante SP FP + offset FP FP offset Pertanto si accede mediante FP Alle variabil locali, con un'offset negativo Ai parametri e al valore di ritorno, mediante un'offset positivo

28 Subroutine: Area di attivazione Vediamo ora nel dettaglio come cambia lo stack Funzione chiamante void Dummy() { int y; y = AddInt( 10, 20 ); } Funazione chiamata int AddInt( int a, int b ) { int c; c = a + b return c; }

29 Subroutine: Esempio completo Prima di iniziare la sequenza di chiamata della funzione AddInt() Dummy PUSH #10 PUSH #20 CALL AddInt LOAD, 4(SP)! ADD SP, #8 STORE 4(FP), Si noti che Il frame pointer salvato (0x804) punta alla posizione in cui è stato salvato il frame piointer della funzione chiamante di AddInt() 0x804 y SP FP di Dummy()

30 Subroutine: Esempio completo Immediatamente dopo la chiamata della funzione AddInt() Dummy PUSH #10 PUSH #20 CALL AddInt LOAD, 4(SP)! ADD SP, #8 STORE 4(FP), Si noti che Sono stati impilati i parametri L'istruzione CALL ha impilato l'indirizzo di ritorno in Dummy() x804 y 0x068 SP FP di AddInt() di Dummy()

31 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Si pone sullo stack il frame pointer corrente 0x x804 y 0x068 SP FP di AddInt() di Dummy() Si crea dunque il link statico con lo stack frame del chiamante

32 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Il frame pointer viene fatto puntare al valore corrente dello stack pointer 0x x804 y 0x068 SP FP di AddInt() di Dummy()

33 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Viene spostato lo stack pointer per Tenere conto del frame pointer salvato Riservare lo spazio per la variabile locale "c"??? (c) 0x x804 y 0x068 SP FP di AddInt() di Dummy()

34 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Vengono salvari i registri??? (c) 0x x804 y 0x068 SP FP di AddInt() di Dummy() Il valore dei registri è quello che essi hanno nella funzione chiamante Dummy()

35 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Carica in il primo parametro??? (c) 0x x804 y 0x068 SP FP di AddInt() di Dummy()

36 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Somma ad il secondo parametro??? (c) 0x x804 y 0x068 SP FP di AddInt() di Dummy() Il risultato è in

37 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Salva il risultato della somma nella variabile locale "c" di AddInt() 30 0x x804 y 0x068 SP FP di AddInt() di Dummy()

38 Subroutine: Esempio completo Nella funzione AddInt() AddInt PUSH FP MOVE FP, SP ADD SP, #-4 PUSH PUSH PUSH PUSH MOVE, 12(FP) ADD, 8(FP) STORE -4(FP), LOAD, -4(FP) STORE 12(FP), Si noti che Sposta il risultato dalla variabile locale "c" nella posizione adatta al valore di ritorno 30 0x x804 y 0x068 SP FP di AddInt() di Dummy()

39 Subroutine: Esempio completo Nella funzione AddInt() AddInt LOAD, -4(FP) STORE 12(FP), POP POP POP POP ADD SP, #4 POP FP RET Si noti che Recupera il valore dei registri salvati 30 0x x804 y 0x068 SP FP di AddInt() di Dummy()

40 Subroutine: Esempio completo Nella funzione AddInt() AddInt LOAD, -4(FP) STORE 12(FP), POP POP POP POP ADD SP, #4 POP FP RET Si noti che Rilascia lo spazio allocato per le variabili locali spostando esplicitamente lo stack pointer 0x x804 y 0x068 FP SP di AddInt() di Dummy()

41 Subroutine: Esempio completo Nella funzione AddInt() AddInt LOAD, -4(FP) STORE 12(FP), POP POP POP POP ADD SP, #4 POP FP RET Si noti che Recupera il frame pointer del chiamante x804 y 0x068 SP FP di AddInt() di Dummy() In questo modo il frame attivo è quello del chiamante

42 Subroutine: Esempio completo Nella funzione AddInt() AddInt LOAD, -4(FP) STORE 12(FP), POP POP POP POP ADD SP, #4 POP FP RET Si noti che Ritorna al chiamante rimuovendo l'indirizzo di ritorno dallo stack x804 y 0x068 SP FP di AddInt() di Dummy()

43 Subroutine: Esempio completo Immediatamente dopo la chiamata della funzione AddInt() Dummy PUSH #10 PUSH #20 CALL AddInt LOAD, 4(SP) ADD SP, #8 STORE 4(FP), Si noti che Recupera il valore di ritono e lo salva in x804 y 0x068 SP FP di AddInt() di Dummy()

44 Subroutine: Esempio completo Immediatamente dopo la chiamata della funzione AddInt() Dummy PUSH #10 PUSH #20 CALL AddInt LOAD, 4(SP) ADD SP, #8 STORE 4(FP), Si noti che Rilascia lo spazio allocato per il passaggio dei parametri spostando esplicitamente lo stack pointer 0x804 y 0x068 SP FP di Dummy()

45 Subroutine: Esempio completo Immediatamente dopo la chiamata della funzione AddInt() Dummy PUSH #10 PUSH #20 CALL AddInt LOAD, 4(SP) ADD SP, #8 STORE 4(FP), Si noti che Salva il valore di ritorno copiandolo da alla posizione della variabile locale "y" L'accesso avviene mediante il frame pointer dello stack frame correntemente attivo 0x x068 SP FP di Dummy()

46 Subroutine: Schema chiamante/chiamato In generale dunque Chiamante e chiamato si dividono il compito di costruire e distruggere lo stack frame La figura mostra i compiti svolti da ognuno di essi Chiamante Chiamato Prologue Prologue Salva FP corrente Aggiorna FP Alloca variabili locali Push registri Push parametri Pre-call Push indirizzo di ritorno CALL Recupera valore di ritorno Rimuove i parametri Post-call Epilogue Pop registri Dealloca variabili locali Recupera FP salvato RET Pop indirizzo di ritorno Epilogue