Somma di interi
Prendiamo come esempio una semplice operazione e proviamo a capire come deve essere trasformata per poter essere eseguita da un processore.
Ipotizziamo che vogliamo sommare due numeri interi. Come dobbiamo fare? Ricordiamoci che il processore può solo leggere e scrivere sulla memoria, ed effettuare semplici operazioni. Il processore inizia e finisce in uno stato "vuoto", quindi tutto quello che deve fare e tutti i dati che usa devono essere già in memoria prima dell'esecuzione del programma.
Proviamo a scomporre in piccoli passi:
- leggo dalla memoria il primo addendo, e lo copio in un registro interno, ad esempio
regA
- leggo dalla memoria il secondo addendo, e lo copio in un altro registro interno, ad esempio
regB
- leggo dalla memoria l'operazione che devo eseguire, in questo caso somma
- eseguo l'operazione
- scrivo il risultato in un registro, per ottimizzare posso riutilizzare il
regA
(così uso solo due registri in totale) - copio il contenuto di
regA
in memoria.
OK, sembra che ci siamo. Proviamo a vedere se funziona effettivamente così. Per verificare, utilizzeremo il linguaggio C perché è quello che ha meno "passaggi" dal codice sorgente al compilato.
int main() {
int a = 12;
int b = 32;
int c = a + b;
return 0;
}
Per salvare questo file in locale, utilizzate l'editor nano
.
$ nano
Ricopiate il codice sopra, salvatelo con ctrl-o
con il nome main.c
, ed uscite dall'editor con ctrl-x
.
Un alternativa più professionale a
nano
èvim
, lo trovate già installato sulla Raspberry. Un tutorial efficace e divertente per imparare a usare vim è Vim Adventures.
Ora compiliamo il file con gcc
e ci facciamo generare l'assembly con l'opzione -S
:
$ gcc -S main.c
Otteniamo un output simile al seguente:
.arch armv6
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "main.c"
.text
.align 2
.global main
.syntax unified
.arm
.fpu vfp
.type main, %function
main:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
str fp, [sp, #-4]!
add fp, sp, #0
sub sp, sp, #20
mov r3, #12
str r3, [fp, #-8]
mov r3, #32
str r3, [fp, #-12]
ldr r2, [fp, #-8]
ldr r3, [fp, #-12]
add r3, r2, r3
str r3, [fp, #-16]
mov r3, #0
mov r0, r3
add sp, fp, #0
@ sp needed
ldr fp, [sp], #4
bx lr
.size main, .-main
.ident "GCC: (Raspbian 6.3.0-18+rpi1+deb9u1)
Potete eseguire questi passi anche da Android. Dovrete installare prima gli strumenti di sviluppo con il comando
pkg install build-essential
.
La parte che ci interessa è quella all'interno della sezione main:
.
mov r3, #12
str r3, [fp, #-8]
mov r3, #32
str r3, [fp, #-12]
ldr r2, [fp, #-8]
ldr r3, [fp, #-12]
add r3, r2, r3
str r3, [fp, #-16]
Le operazioni che può eseguire il processore sono quelle più a sinistra:
mov
: move, copia un valore costante in un registrostr
: store register, copia il valore dal registro alla memoria ad un certo indirizzoldr
: load register, copia il valore dalla memoria al registro ad un certo indirizzo
Analizziamo il codice assembly nel dettaglio:
mov r3, #12
: assegna il valore12
al registror3
str r3, [fp, #-8]
: copia il valore del registror3
nell'indirizzo di memoria-8
mov r3, #32
: assegna il valore32
al registror3
str r3, [fp, #-8]
: copia il valore del registror3
nell'indirizzo di memoria-12
ldr r2, [fp, #-8]
: copia il dato all'indirizzo di memoria-8
nel registror2
ldr r3, [fp, #-12]
: copia il dato all'indirizzo di memoria-12
nel registror3
add r3, r2, r3
: effettua la somma dei registrir3
er2
ed assegna il risultato ar3
str r3, [fp, #-16]
: copia il valore del registro r3 nell'area di memoria-16
Concettualmente, è così che funziona un processore: anche le operazioni molto complesse vengono suddivise in piccoli parti fino ad arrivare ad un risultato molto simile a quello presentato.