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.