LINGUAGGI
ASSEMBLY Seconda puntata del corso di programmazione sul
linguaggo Assembly MC68000, il linguaggio macchina dell'Amiga
di Paolo Russo Come si programma in Assembly Il primo passo per realizzare un programma è, naturalmente,
sedersi e pensare con calma a cosa si deve fare; è consigliabile
poi scarabocchiare una prima versione del programma su carta. Si può
quindi passare alla fase di editing: con l'ausilio di un qualunque editor,
come il comando ED del CLI, si digita il programma e si salva il testo
(anche un word processor va bene, purché capace di salvare in ASCII).
Se si decide di usare ED bisogna entrare in CLI, richiamare l'editor digitando
ED nomesorgente (dove nomesorgente è il nome che si decide di assegnare
al testo, noto in gergo programmatorio come codice sorgente) e, dopo aver
inserito tutto il testo, salvarlo con ECC X. Bisogna quindi convertire
il testo così ottenuto in un programma in linguaggio macchina; questa
operazione, che nell'ambito dei linguaggi evoluti è svolta da un
interprete o da un compilatore, richiede invece nel nostro caso un assemblatore
(più diffusamente noto come assembler). Ne esistono di molti tipi
e in questa serie di articoli verrà impiegato il macroassembler
standard prodotto dalla Metacomco; gli altri assembler possono differirne
nell'uso delle macro, delle label locali (nn$) e delle direttive di assembly
come CNOP 0,2 che serve ad allineare il programma a indirizzo pari: se
quindi avete un assembler diverso leggete il relativo manuale. Se invece
avete lo stesso assembler potete richiamarlo, sempre da CLI, con ASSEM
nomesorgente -O nomeoggetto, dove quest'ultimo parametro è il nome
con cui desiderate battezzare il programma assemblato, generalmente noto
come codice oggetto. Occorre infine linkare il tutto con il comando ALINK
nomeoggetto TO nomedefinitivo; ALINK si trova, assieme ad ASSEM, nella
directory C del disco dell'assembler e se il CLI ha qualche difficoltà
nel trovarlo dovrete specificare il pathname completo (ASSEM-DEVEL:c/ASSEM
e ASSEM-DEVEL:c/ALINK) quando li attivate. Dopo tutta questa procedura
avrete ottenuto un file chiamato nomedefinitivo che può essere mandato
in esecuzione da CLI semplicemente digitandone il nome. Altri tipi di assembler
possono avere l'editor e il linker incorporati (caratteristica che fece
la fortuna del TurboPascal), allo scopo di semplificare la vita del programmatore.
I possessori del sopracitato assembler della Metacomco sono vivamente consigliati
di usare estesamente il RAM Disk e le sequenze di comandi in base alla
propria esperienza individuale, per non estenuare in misura eccessiva il
drive e la propria pazienza.
Struttura di una linea L'Assembly non prevede l'uso del multistatement, cioè
della possibilità di inserire più comandi in una stessa riga.
Ogni linea di programma possiede una struttura ben precisa e può
essere pensata come suddivisa in campi, la lunghezza di ognuno dei
quali può però variare da linea a linea. I campi sono quattro:
label, istruzione, operandi e commento. Una label (etichetta) è
una corta stringa alfanumerica usata per marcare un punto del programma,
come riferimento per i salti e per la manipolazione delle variabili; e
importante notare che le label esistono solo per l'assemblatore, non per
il microprocessore. Se a esempio si contrassegna un punto del programma
con una label e poi in un altro punto si inserisce un'istruzione di salto
a quella etichetta l'assembler sostituirà alla label, nell'istruzione
di salto, un numero che rappresenta l'indirizzo di memoria a cui saltare.
Il microprocessore ragiona sempre in termini di indirizzi numerici: le
label sono comode per gli esseri umani e per questo motivo l'assembler
le adotta, convertendole poi in numeri e indirizzi al momento di generare
il codice oggetto. Il campo della label può essere vuoto.
Byte, word e long word I registri del 68000, come illustrato nella scorsa puntata,
sono a 32 bit, ossia sono delle long word. Nonostante ciò, non sempre
il processore esegue calcoli con 32 bit: per motivi di ordine pratico ci
si limita spesso a 16 oppure 8 bit. La lunghezza dei dati su cui si opera
non è intrinseca al dato stesso ma deve essere specificata in ogni
istruzione. Per esempio esistono tre forme dell'istruzione ADD: ADD.B,
ADD.W e ADD.L che sommano rispettivamente numeri a 8, 16 e 32 bit. Naturalmente
B, W ed L significano rispettivamente byte, word e long word. L'istruzione
ADD.B D1 ,D2 somma gli otto bit meno significativi di D1 e D2, ponendo
il risultato negli otto bit meno significativi di D2. I rimanenti 24 bit
di D2 non vengono alterati; eventuali riporti da e verso il nono bit vengono
troncati senza pietà.
I modi di indirizzamento Abbiamo visto in precedenza come l'istruzione ADD D1, D2
sommasse il contenuto di D1 al registro D2; in questo caso l'istruzione
fondamentale è ADD mentre D1 e D2 sono i suoi operandi. Che cosa
avremmo potuto utilizzare in luogo di D1 e D2? In altri termini, quali
sono i possibili operandi su cui può agire l'istruzione ADD?
#n: l'operando è il numero n. Se l'istruzione è veloce (quick), ossia se il suo nome termina per Q (MOVEQ, ADDQ, SUBQ) l'uso di # n come primo operando non richiede byte aggiuntivi. Dn: l'operando è il registro dati Dn, o meglio lo è il dato contenuto in Dn. An: come sopra, con la differenza che il registro coinvolto è un registro indirizzi. La distinzione è sensata in quanto molte istruzioni operano con una sola delle due classi di registri. (An): questo modo di indirizzamento e i seguenti sono indiretti. L'operando è la locazione di memoria puntata da An, ossia il cui in.dirizzo è contenuto in An. (An)+ : come sopra, ma con l'aggiunta del postincremento: dopo che il dato puntato da An e stato utilizzato, An viene incrementato di un numero pari alla lunghezza del dato in questione. -(An): come sopra, ma con il predecremento: prima che il dato venga utilizzato, An viene decrementato della lunghezza del dato. d16(An): l'operando è la locazione di memoria il cui indirizzo si ottiene sommando il numero di 6 ad An. La costante d16, detta displacement o spiazzamento, è a 16 bit. Sarebbe forse stato piij logico indicare questo modo come (An + d16), ma questo formato non è standard e non viene riconosciuto dagli assem blatori. d8(An,i): come sopra, ma per ottenere l'indirizzo occorre sommare anche i , detto registro indice, che può essere un qualunque registro, sia dati che indirizzi, Lo spiazzamento ammesso è a soli otto bit, mentre l'indice i può essere inteso sia come word che come tong word a seconda del suffisso che gli viene posposto. d16(PC): l'operando è la locazione di memoria il cui indirizzo si ottiene sommando d16 al program counter. Questo modo di indirizzamento è spesso impiegato per manipolare dati e variabili incorporati nel programma stesso e il cui indirizzo è quindi vincolato alla posizione che il programma occupa nella RAM. In tal modo il programma che si ottiene è position independent, cioè indipendente dalla posizione; ciò significa che il programma è in grado di trovare i suoi dati in qualunque zona di memoria venga caricato. Questo argomento sarà ripreso in futuro. Purtroppo i modi di indirizzamento relativi al program counter funzionano solo per leggere dati dalla memoria e non per scriverveli, a causa di una assai discutibile scelta dei progettisti. d8(PC,i): come sopra, con l'aggiunta di un registro indice. Non molto usato a causa della limitazione imposta sul displacement, serve a manipolare tabelle di dati incluse nel programma. a16: l'operando è la locazione il cui indirizzo è il numero a16, che risulta quindi essere un indirizzo a 16 bit. Questo modo di indirizzamento può essere usato solo nei 32K inferiori di RAM. a32: come sopra, ma l'indirizzo è a 32 bit e consente di raggiungere qualunque locazione di memoria. Quando si specifica un indirizzo è l'assembler a decidere se considerarlo un a16 o un a32. range: utilizzato unicamente dall'istruzione MOVEM, è un insieme di registri. Per esempio, D0-D3/D5/A1 /A3-A5 è un range che,comprende i registri D0, D1, D2, D3, label: utilizzata nei salti, per specificare il punto a cui si salta. Tramite le label si può anche manipolare un dato incorporato nel programma: in questo caso l'assembler convertirà automaticamente la label in un d8(PC,i), a seconda dei casi, come si vedrà meglio in seguito. Tutti gli indirizzi, gli indici e gli spiazzamenti sono intesi come numeri dotati di segno. Alcune istruzioni L'istruzione più usata in ogni programma è
MOVE, che, come si può arguire dal nome stesso, sposta dati da un
punto a un altro e può essere considerata un'istruzione di assegnazione,
simile all'arcaico LET di alcuni dialetti BASIC. Il comando MOVE richiede
due operandi che devono essere separati, com'è d'uso in Assembly,
da una virgola; il primo di essi, detto sorgente, fornisce il valore da
assegnare al secondo, detto destinazione. Per esempio MOVE.W D1,D2 copia
la word meno significativa di D1 in quella di D2, senza alterarne la metà
superiore; MOVE.L D0,A3 assegna il contenuto di D0 ad A3; MOVE.B # 10,D6
assegna il valore dieci al byte inferiore di D6 mentre M0VE.B 10,D6 legge
il byte di memoria all'indirizzo dieci e lo pone in D6 (attenti al ' #'
! MOVE è l'istruzione che consente la massima libertà nella
scelta dei modi di indirizzamento per gli operandi; quasi ogni combinazione
di modi è lecita. La maggior parte delle altre istruzioni consente
tale libertà per uno solo dei due operandi, quello sorgente o quello
destinazione, a scelta del programmatore, mentre l'altro parametro può
variare in un ambito molto limitato (solitamente #n e Dn, ma non sempre).
A esempio ADD.W D1,D1, ADD.W 16(A3),D1 e ADD.W D1,16(A3) sono lecite mentre
ADD.W 16(A3),16(A3) non lo è; MOVE.W 16(A3),16(A3) è invece
perfettamente lecita, per quanto improduttiva in questo caso particolare
data la coincidenza dei due operandi. Tutto questo può sembrare
un po' complicato ed eccessivamente mnemonico, ma l'esperienza insegna
che il programmatore medio apprende in breve tempo con l'esercizio le combinazioni
istruzione-operandi proibite e le rare volte che commette un errore l'assembler
stesso lo segnala prontamente.
Un piccolo esempio Fino a questo momento non sono ancora state presentate
sufficienti nozioni da consentire la realizzazione di un vero e proprio
programma. Si può però prendere in considerazione qualche
microscopico esempio. Il problema è il seguente: esistono tre interi
a 16 bit in memoria agli indirizzi consecutivi 100000, 100002 e 100004,
che per brevità indicheremo come A, B e C; bisogna porre nel terzo
la somma dei primi due. Esistono svariati modi per farlo e ne prenderemo
in considerazione alcuni.
Il primo metodo è il meno efficiente (forse il compilatore
di un linguaggio evoluto lo sceglierebbe), il secondo è il più
logico e intuitivo mentre il terzo, sfruttando il fatto che i dati in memoria
sono consecutivi, è il più compatto e veloce. Non assemblate
realmente questi programmi, se lo faceste otterreste una Guru Meditation
dovuta all'assenza di un'istruzione di ritorno e alla arbitraria e distruttiva
alterazione della word 100004. Se anche per assurdo tutto funzionasse non
vedreste comunque nulla sullo schermo. Prossimamente, quando sarà
stato illustrato l'uso dei flag e delle istruzioni di controllo, sarà
possibile realizzare qualche programma vero, anche se breve.
|