Formato dei file sorgenti¶
EduMIPS64 si propone di seguire le convenzioni usate negli altri simulatori MIPS64 e DLX, in modo tale da non creare confusione riguardante la sintassi per i vecchi utenti.
All’interno di un file sorgente sono presenti due sezioni: quella dedicata ai dati e quella in cui è contenuto il codice, introdotte rispettivamente dalle direttive .data e .code. Nel seguente listato è possibile vedere un semplice programma:
; Questo è un commento
.data
label: .word 15 ; Questo è un commento in linea
.code
daddi r1, r0, 0
syscall 0
Per distinguere le varie parti di ciascuna linea di codice, può essere utilizzata una qualunque combinazione di spazi e tabulazioni, visto che il parser ignora spazi multipli.
I commenti possono essere introdotti utilizzando il carattere «;» qualsiasi cosa venga scritta successivamente ad esso verrà ignorata. Un commento può quindi essere usato «inline» (dopo una direttiva) oppure in una riga a sè stante.
Le etichette possono essere usate nel codice per fare riferimento ad una cella di memoria o ad un’istruzione. Esse sono case insensitive. Per ciascuna linea di codice può essere utilizzata un’unica etichetta. Quest’ultima può essere inserita una o più righe al di sopra dell’effettiva dichiarazione del dato o dell’istruzione, facendo in modo che non ci sia nulla, eccetto commenti e linee vuote, tra l’etichetta stessa e la dichiarazione.
Più etichette possono fare riferimento allo stesso indirizzo. Questo è utile quando due o più etichette sono posizionate su righe consecutive prima di un’istruzione, ad esempio:
.code
daddi r1, r0, 1
loop_end:
loop_begin:
daddi r1, r1, -1
In questo caso, sia loop_end che loop_begin punteranno alla stessa istruzione. Ogni etichetta deve comunque avere un nome univoco; l’utilizzo dello stesso nome di etichetta due volte causerà un errore del parser.
Limiti di memoria¶
EduMIPS64 ha memoria limitata, sia per i dati (sezione .data, limitata a 640 kB – i.e., 80000 valori a 64 bit) che per le istruzioni (sezione .code, limitata a 128 kB – i.e., 32000 istruzioni, ciascuna da 32 bit).
I limiti sono arbitrari e fissati nel codice del simulatore.
La sezione .data¶
La sezione data contiene i comandi che specificano il modo in cui la memoria deve essere riempita prima dell’inizio dell’esecuzione del programma. La forma generale di un comando .data è:
[etichetta:] .tipo-dato valore1 [, valore2 [, ...]]
EduMIPS64 supporta diversi tipi di dato, che sono descritti nella seguente tabella.
Tipo
Direttiva
Bit richiesti
Byte
.byte
8
Half word
.word16
16
Word
.word32
32
Double Word
.word or .word64
64
Dati di tipo doubleword possono essere introdotti sia dalla direttiva .word che dalla direttiva .word64.
I tipi di dato sono interpretati con segno. Questo significa che tutte le costanti intere nella sezione .data devono essere compresi tra -2^(n-1) e 2^(n-1) - 1 (estremi inclusi).
Invece di una costante numerica, il valore di una direttiva intera (.byte, .word16, .word32, .word e .word64) può essere anche una label. In tal caso l’assemblatore memorizza l’indirizzo di memoria a cui la label fa riferimento. La label può riferirsi sia a una posizione definita nella sezione .data sia a un’istruzione nella sezione .code, e può essere definita prima o dopo la direttiva che la utilizza (quindi le tabelle di salto che fanno riferimento a label di codice successive funzionano correttamente). L’indirizzo risolto deve entrare nell’ampiezza della direttiva, altrimenti viene segnalato un errore; una direttiva a 64 bit (.word64) può sempre contenere un indirizzo. Ad esempio:
.data
buffer: .space 8
buf_addr: .word64 buffer ; memorizza l'indirizzo di "buffer"
jump_tbl: .word64 routine ; memorizza l'indirizzo di una label di codice
.code
routine: daddi r1, r0, 42
syscall 0
Esiste una differenza sostanziale tra la dichiarazione di una lista di dati utilizzando un’unica direttiva oppure direttive multiple dello stesso tipo. EduMIPS64 inizia la scrittura a partire dalla successiva double word a 64 bit non appena trova un identificatore del tipo di dato, in tal modo la prima istruzione .byte del seguente listato inserirà i numeri 1, 2, 3 e 4 nello spazio di 4 byte, occupando 32 bit, mentre il codice delle successive quattro righe inserirà ciascun numero in una differente cella di memoria, occupando 32 byte:
.data
.byte 1, 2, 3, 4
.byte 1
.byte 2
.byte 3
.byte 4
Nella seguente tabella, la memoria è rappresentata utilizzando celle di dimensione pari ad 1 byte e ciascuna riga è lunga 64 bit. L’indirizzo posto alla sinistra di ogni riga della tabella è riferito alla cella di memoria più a destra, che possiede l’indirizzo più basso rispetto alle otto celle in ciascuna linea.
0 |
0 |
0 |
0 |
0 |
4 |
3 |
2 |
1 |
8 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
16 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
24 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3 |
32 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
4 |
Ci sono alcune direttive speciali che devono essere discusse: .space, .ascii e .asciiz.
La direttiva .space è usata per lasciare dello spazio vuoto in memoria. Essa accetta un intero come parametro, che indica il numero di byte che devono essere lasciati liberi. Tale direttiva è utile quando è necessario conservare dello spazio in memoria per i risultati dei propri calcoli.
La direttiva .ascii accetta stringhe contenenti un qualunque carattere ASCII, ed alcune «sequenze di escape», simili a quelle presenti nel linguaggio C, che sono descritte nella seguente tabella, ed inserisce tali stringhe in memoria.
Sequenza di escape
Significato
Codice ASCII
\0
Byte nullo
0
\t
Tabulazione orizzontale
9
\n
Carattere di inizio nuova linea
10
\»
Doppi apici
34
\
Backslash
92
La direttiva .asciiz si comporta esattamente come il comando .ascii, con la differenza che essa pone automaticamente alla fine della stringa un byte nullo.
La sezione .code¶
La sezione code contiene le istruzioni che saranno eseguite dal simulatore a run-time. La forma generale di un comando .code è:
[etichetta:] istruzione [param1 [, param2 [, param3]]]
Essa può essere indicata anche con la direttiva .text.
Il numero e il tipo di parametri dipendono dall’istruzione stessa.
Le istruzioni possono accettare tre tipi di parametri:
Registri un parametro di tipo registro è indicato da una «r» maiuscola o minuscola, o da un carattere «$», a fianco del numero di registro (tra 0 e 31). Ad esempio, le scritture «r4», «R4» e «$4» identificano tutt’e tre il quarto registro;
Valori immediati un valore immediato può essere un numero o un’etichetta; il numero può essere specificato in base 10, in base 16 o in base 2. I numeri in base 10 sono inseriti semplicemente scrivendo il numero utilizzando l’usuale notazione decimale; i numeri in base 16 si inseriscono aggiungendo all’inizio del numero il prefisso «0x»; i numeri in base 2 si inseriscono aggiungendo all’inizio del numero il prefisso «0b». I valori immediati possono essere preceduti dal carattere #;
Indirizzi un indirizzo è composto da un valore immediato seguito dal nome di un registro tra parentesi. Il valore del registro sarà usato come base, quello dell’immediato come offset.
La dimensione dei valori immediati è limitata al numero di bit disponibili nella codifica associata all’istruzione.
Nel caso di immediati a 16 bit, come ad esempio i valori immediati delle istruzioni ALU I-Type, è possibile utilizzare come valore immediato un’etichetta di memoria. L’assembler usera come valore immediato l’indirizzo della locazione di memoria a cui l’etichetta punta.
Aritmetica di offset sulle etichette¶
Nella forma «indirizzo» di un parametro (ad esempio l’offset di un’istruzione
di load o store) il valore immediato può essere una semplice espressione
costituita da etichette di memoria e valori numerici combinati con gli
operatori + e -. È ammesso anche un segno + o - iniziale ed è
consentito utilizzare spazi bianchi tra operandi e operatori. Ogni operando è
un valore numerico (in base 10, 16 o 2) oppure un’etichetta di memoria
definita nella sezione .data; le etichette vengono sostituite con il loro
indirizzo prima che l’espressione venga valutata.
Ad esempio, dati data1: .word 42 e data2: .word 43, le seguenti forme
sono tutte accettate:
lw r1, data1+8(r0) ; carica da data1 con offset di 8 byte
lw r1, data1-8(r0) ; carica da data1 con offset di -8 byte
lw r1, 0+data1(r0) ; equivalente a data1(r0)
lw r1, data2-data1(r0) ; differenza tra gli indirizzi di due etichette
lw r1, data1-8+16(r0) ; sono supportate sequenze di + e -
Espressioni con un operando vuoto (ad esempio data1+ oppure
data1++0) sono rifiutate come malformate e un’etichetta non definita
all’interno dell’espressione genera il consueto errore «label not found».
è possibile utilizzare gli alias standard MIPS per i primi 32 registri, mettendo in coda ai prefissi standard per i registri («r», «$», «R») uno degli alias indicati nella seguente tabella.
Registro
Alias
0
zero
1
at
2
v0
3
v1
4
a0
5
a1
6
a2
7
a3
8
t0
9
t1
10
t2
11
t3
12
t4
13
t5
14
t6
15
t7
16
s0
17
s1
18
s2
19
s3
20
s4
21
s5
22
s6
23
s7
24
t8
25
t9
26
k0
27
k1
28
gp
29
sp
30
fp
31
ra
Il comando #include¶
Nei sorgenti può essere utilizzato il comando *#include* nomefile, che ha l’effetto di inserire, al posto della riga contenente questo comando, il contenuto del file nomefile. Questo comando è utile se si vogliono includere delle funzioni esterne, ed è dotato di un algoritmo di rilevamento dei cicli, che impedisce di eseguire inclusioni circolari tipo «#include A.s» nel file B.s e «#include B.s» nel file A.s.