tutorial · Mega Drive
Assembler e C, perché tutti e due?
Il gioco per Mega Drive è scritto un po' in assembler e un po' in C. Perché due linguaggi per un gioco solo? Perché servono a cose diverse, e usati insieme fanno un lavoro migliore. Vediamo la differenza con qualche esempio di codice: per seguirlo bastano le figure e le note in verde.
La CPU capisce un linguaggio solo
Dentro il Mega Drive c'è una CPU, il Motorola 68000: è il processore che esegue il gioco. Una CPU capisce soltanto il suo linguaggio macchina, fatto di istruzioni elementari: sposta un dato, sommalo, confrontalo, salta a un altro punto. L'assembler è il modo leggibile di scrivere quelle istruzioni, una per una. Per esempio, mettere un colore nella tavolozza (la palette) si fa così.
assembler · linguaggio della CPU
Due istruzioni per un solo colore. Preciso, ma scrivere un gioco intero a questo livello sarebbe lentissimo e facile da sbagliare. Serve uno strato più comodo.
Il C e il compilatore
Ecco lo strato comodo: il C. Il C è un linguaggio più vicino al nostro modo di ragionare, dove le cose si dicono in modo corto e chiaro. La CPU però il C non lo esegue: serve un compilatore, un programma che traduce il C nelle tante istruzioni macchina che la CPU capisce. Il percorso è questo:
A sinistra il C come si scrive, a destra il codice macchina che il compilatore produce.
C · come si scrive
assembler · cosa genera il compilatore
Tre righe di C diventano sei istruzioni macchina, e le scrive il compilatore, senza sbagliare. Per questo la maggior parte del gioco è in C: più corto, più chiaro, più veloce da correggere.
Allora perché non tutto in C?
Se il compilatore è così bravo, perché non scrivere tutto in C ed eliminare l'assembler? Perché ci sono tre punti in cui il compilatore non basta e l'assembler va scritto a mano. Vediamoli.
L'avvio, prima che il C possa girare
All'accensione la CPU parte da uno stato grezzo: la memoria non è preparata e, soprattutto, non esiste ancora lo stack, l'area di memoria che il C usa per le chiamate di funzione e le variabili locali. Senza stack, il C non può girare. Quindi il primissimo codice, quello che imposta lo stack e poi passa il controllo al programma, è per forza in assembler. Per convenzione questo file si chiama crt0.
in cima alla ROM
imposta lo stack, spegne le interruzioni
init e game loop: il gioco parte
crt0.s · assembler
main.c · C
L'assembler prepara il terreno; poi entra il C. Senza quelle poche righe iniziali, il C non potrebbe nemmeno partire.
La tabella dei vettori
Appena accesa, la CPU legge in cima alla ROM una tabella dei vettori: una lista di indirizzi. Il primo dice dov'è lo stack, il secondo da quale indirizzo iniziare, altri dicono quale routine chiamare quando arriva un'interruzione o un errore. Deve stare in una posizione e in un ordine precisi, decisi dall'hardware: si scrive a mano in assembler, perché il compilatore non saprebbe collocarla esattamente lì.
assembler · la tabella dei vettori
dc.l significa «qui va un valore a 32 bit». Ogni riga è una voce della tabella. L'hardware la legge sempre in questo ordine: spostare le voci manda in crash l'avvio.
Quando conta la velocità
Il compilatore genera codice buono, ma in certi punti serve la massima velocità: per esempio copiare molti dati nella VRAM (la memoria video) entro il tempo di un frame, altrimenti si vede uno scatto. In questi punti critici l'assembler scritto a mano, scegliendo le istruzioni migliori, batte il compilatore. Sono poche righe, ma decisive per la fluidità.
assembler · scritto a mano, per la velocità
Qui il controllo dev'essere totale: le istruzioni sono scelte a mano per essere le più rapide. È l'unico caso in cui l'assembler batte il compilatore, sfruttando un accorgimento che il compilatore non applica.
Tutto il resto: la logica, in C
Tolti quei tre punti, tutta la logica di gioco (movimento del personaggio, salti, nemici, collisioni, punteggio) si scrive in C. È la parte che cambia più spesso, e in C è più leggibile e più veloce da modificare.
game.c · il salto del personaggio
Si legge quasi come pseudocodice: se si preme fuoco, salta; la gravità accelera verso il basso; aggiorna la posizione; fermati al pavimento. In assembler puro sarebbe il triplo delle righe e più difficile da modificare.
Due strumenti, un lavoro
assembler · il basso livello
C · la logica di gioco
Assembler e C non sono in competizione: si dividono i compiti. L'assembler gestisce l'avvio e i punti a basso livello; il C esprime la logica in modo chiaro. Insieme sono più efficienti di ciascuno da solo. In più, lo stesso codice C si può compilare anche sul computer di sviluppo: così si collauda prima di metterlo nella ROM.