DMI – Corso di laurea magistrale in Informatica
Copyleft
2020 Giuseppe Scollo
di che si tratta:
il microprocessore è il componente programmabile di maggior successo negli ultimi decenni... perché?
Schaumont, Figure 7.1 - Standard design flow of software source code to processor instruction
int gcd(int a[5], int b[5]) {
int i, m, n, max;
max = 0;
for (i=0; i<5; i++) {
m = a[i];
n = b[i];
while (m != n) {
if (m > n) m = m - n;
else n = n - m;
}
if (max < m) max = m;
}
return max;
}
int a[] = {26, 3,33,56,11};
int b[] = {87,12,23,45,17};
int main() {
return gcd(a, b);
}
Schaumont, Listing 7.1 - A C program to find a maximum GCD
Schaumont, Figure 7.2 - Elements of an assembly program produced by gcc
gcd: | ||
str | lr, [sp, #-4]! | |
mov | lr, #0 | |
mov | ip, lr | |
.L13: | ||
ldr | r3, [r0, ip, asl #2] | |
ldr | r2, [r1, ip, asl #2] | |
cmp | r3, r2 | |
beq | .L17 | |
.L11: | ||
cmp | r3, r2 | |
rsbgt | r3, r2, r3 | |
rsble | r2, r3, r2 | |
cmp | r3, r2 | |
bne | .L11 | |
.L17: | ||
add | ip, ip, #1 | |
cmp | lr, r3 | |
movlt | lr, r3 | |
cmp | ip, #4 | |
movgt | r0, lr | |
ldrgt | pc, [sp], #4 | |
b | .L13 | |
a: | ||
.word | 26, 3, 33, 56, 11 | |
b: | ||
.word | 87, 12, 23, 45, 17 | |
main: | ||
str | lr, [sp, #-4]! | |
ldr | r0, .L19 | |
ldr | r1, .L19+4 | |
ldr | lr, [sp], #4 | |
b | gcd | |
.align | 2 | |
.L19: | ||
.word | a | |
.word | b |
Schaumont, Listing 7.2 - ARM assembly dump of Listing 7.1
l'esempio appena visto è sviluppato con il cross-compilatore GNU arm-linux-gcc, che era reperibile come pacchetto Debian nel repository di Gezel, ma questo non è più disponibile
il codice assembly simbolico è ottenuto dal sorgente C mediante il comando:
il comando per generare l'eseguibile ARM ELF è:
è anche possibile ottenere il codice simbolico dall'eseguibile ELF mediante un disassemblatore, in questo esempio con il seguente comando:
l'output del disassemblatore mostra anche il codice binario associato a ogni istruzione simbolica e gli indirizzi associati alle etichette
il codesign hardware/software efficiente richiede una comprensione congiunta di architettura di sistema e del software
la rappresentazione dei tipi di dati è un buon punto di partenza, i compilatori ne conoscono le differenze di:
la tabella 7.1 mostra come C li traduce ai tipi di dati nativi supportati da processori a 32 bit
C data type | |
char | 8-bit |
short | signed 16-bit |
int | signed 32-bit |
long | signed 32-bit |
long long | signed 64-bit |
Schaumont, Table 7.1 - Compiler data types
Schaumont, Figure 7.7 (a) - Alignment of data types
un'organizzazione della memoria basata sulla parola richiede allineamento ai confini di parola, per eseguire un trasferimento di parola con un solo accesso a memoria
Schaumont, Figure 7.7 (b) - Little-endian and Big-endian storage order
l'ordinamento dei byte, in qualche caso persino quello dei bit, è rilevante al codesign hardware/software
un altro aspetto importante della rappresentazione dei dati è il tipo di memoria fisica allocata
Schaumont, Figure 7.8 - Memory hierarchy
la gerarchia di memoria è trasparente a programmi di alto livello, e.g. in C, ma il controllo di basso livello influisce sulle prestazioni; ecco un esempio:
void accumulate(int *c, int a[10]) {
int i;
*c = 0;
for (i=0; i<10; i++) *c += a[i];
}
/usr/local/arm/bin/arm-linux-gcc -O2 -c -S accumulate.c
genera il codice seguente in accumulate.s :
mov | r3, #0 | ||
str | r3, [r0, #0] | ||
mov | ip, r3 | ||
.L6: | |||
ldr | r2, [r1, ip, asl #2] | ; r2 ← a[i] | |
ldr | r3, [r0, #0] | ; r3 ← *c (memory) | |
add | ip, ip, #1 | ; increment loop ctr | |
add | r3, r3, r2 | ||
cmp | ip, #9 | ||
str | r3, [r0, #0] | ; r3 → *c (memory) | |
movgt | pc, lr | ||
b | .L6 |
nell'esempio, il valore della variabile accumulatore viaggia su e giù nella gerarchia di memoria
Storage specifier | Type qualifier |
register | const |
static | volatile |
extern | |
le chiamate di funzioni sono la struttura fondamentale della gerarchia comportamentale dei programmi: ecco un esempio della loro traduzione in linguaggio macchina
int accumulate(int a[10]) {
int i;
int c = 0;
for (i=0; i<10; i++)
c += a[i];
return c;
}
int a[10];
int one = 1;
int main() {
return one + accumulate(a);
}
Schaumont, Listing 7.4 - Sample program
la compilazione del programma senza ottimizzazione mostra la creazione, nella pila, dell'area di attivazione, che viene dinamicamente associata all'esecuzione della funzione per ospitare variabili locali e salvataggio di registri
l'uso del registro FP (frame pointer) permette la nidificazione delle chiamate e la ricorsione
accumulate: | |||
mov | ip, sp | ||
stmfd | sp!, {fp, ip, lr, pc} | ||
sub | fp, ip, #4 | ||
sub | sp, sp, #12 | ||
str | r0, [fp, #-16] | ; base address a | |
mov | r3, #0 | ||
str | r3, [fp, #-24] | ; c | |
mov | r3, #0 | ||
str | r3, [fp, #-20] | ; i | |
.L2: | |||
ldr | r3, [fp, #-20] | ||
cmp | r3, #9 | ; i<10 ? | |
ble | .L5 | ||
b | .L3 | ||
.L5: | |||
ldr | r3, [fp, #-20] | ; i * 4 | |
mov | r2, r3, asl #2 | ||
ldr | r3, [fp, #-16] | ||
add | r3, r2, r3 | ; *a + 4 * i | |
ldr | r2, [fp, #-24] | ||
ldr | r3, [r3, #0] | ||
add | r3, r2, r3 | ; c = c + a[i] | |
str | r3, [fp, #-24] | ; update c | |
ldr | r3, [fp, #-20] | ||
add | r3, r3, #1 | ||
str | r3, [fp, #-20] | ; i = i + 1 | |
b | .L2 | ||
.L3: | |||
ldr | r3, [fp, #-24] | ; return arg | |
mov | r0, r3 | ||
ldmea | fp, {fp, sp, pc} |
Schaumont, Listing 7.6 - Accumulate without compiler optimizations
la figura 7.9 mostra una ipotesi di costruzione dell'area di attivazione nella pila
Schaumont, Figure 7.9 - Stack frame construction
il ripristino dei registri salvati e il rientro si hanno con un'unica istruzione di trasferimento multiplo
l'analisi di questo problema è differita alla prossima esperienza di laboratorio
per la rappresentazione fisica del programma e delle sue strutture dati nella gerarchia di memoria, occorre distinguere fra:
Schaumont, Figure 7.10 - Static and dynamic program layout
letture raccomandate:
per sperimentazione:
per ulteriore consultazione: