Documente Academic
Documente Profesional
Documente Cultură
Capitolul 4.Macroinstructiuni
4.1.Scopul macroinstructiunilor.Definire si expandare.
Macroinstructiunile permit programatorului sa defineasca simbolic secvente de program (instructiuni, definitii de
date, directive etc) asociate cu un nume. Folosind numele macroinstructiunii in program se va genera intreaga secventa
de programe. In esenta este vorba de un proces de substitutie (expandarea) in textul programului sursa care se petrece
inainte de ansamblarea programului. Un asamblor care dispune de macroinstructiuni se numeste macroansamblor. Sunt
2 etape de lucru cu macroinstructiuni :
-definirea macroinstructiunilor si utilizare lor. Utilizarea se mai numeste invocare sau chiar apel de macroinstructiune, dar
ultima denumire poate produce confuzie, termenul fiind folosit in cazul procedurilor.
Spre deosebire de proceduri, macroinstructiunile sunt expandate la fiecare utilizare, deci programul nu se micsoreaza.
Avantajul este ca textul sursa scris de programator devine mai clar si mai scurt. Macroinstructiunile pot fi cu parametri sau
fara parametri. Din punct de vedere sintactic, ele sunt asemanatoare cu directivele de tip Define din limbajele de nivel
inalt, dispunand in general de mecanisme mai evoluate decat acestea.
Definitia unei macroinstructiuni fara parametri se face in forma generala : cod 3.80.
-invocarea consta prin scrierea din codul sursa a numelui macroinstructiunii. Macroinstructiunea unit_ds_es este definita
in fisierul io.h prin : cod 3.81.
Macroinstructiunea exit_dos este definita prin cod 3.82.
O pereche de macroinstructiuni care salveaza si refac registrele generale poate fi conceputa astfel : cod 3.83.
Putem utiliza aceste macroinstructiuni la intrarea si la iesirea dintr-o procedura : cod 3.84.
Din punct de vedere simbolic este ca si cum setul de instructiuni al masinii ar fi fost extins cu 2 noi instructiuni : save si
rest.
4.2.Macroinstructiuni cu parametri
Macroinstructiunile importante sunt cele cu parametri. Definitia unei macroinstructiuni cu parametri are forma generala :
cod 3.85, in care p1,p2,..,pn sunt identificatori care specifica parametrii formali apelului (invocarea) unei macroinstructiuni
cu parametri se face prin specificarea numelui, formata de o lista de parametri actuali : cod 3.86.
La expandarea macroinstructiunii, pe langa expandarea propriu zisa se va inlocui fiecare parametru formal cu parametrul
actual respectiv. Sa consideram cateva exemple. Apelurile de functii DOS presupun numarul functiei in registrul AH.
Putem defini deci o macroinstructiune de forma : cod 3.87. si putem invoca macroinstructiunea prin linii de forma cod
3.88.
Similar, pentru deschiderea unui fisier disc pentru citire (operatie realizata prin functia DOS 3DH) putem scrie o
macroinstructiune de forma cod 3.89 in care fname contine numele fisierului si hand este o variabila de tipul word in care
se depune un indicator catre fisierul deschis. Parametru 0C0H codifica modul de acces. Se observa utilizarea
macrointructiunii dosint in definitia lui o_read. Citirea din fisier poate fi codificata intr-o macroinstructiune de forma : cod
3.90 in care hand este indicatorul catre fisierul anterior deschis, nr este numarul de octeti care se citeste, iar buf este
adresa unei zone de memorie in care se vor depunde datele citite.
Inchiderea unui fisier deschis se poate face cu macroinstructiune de forma : cod 3.91.
Macroinstructiunea de mai sus este definita in io.h. Ne propunem acum sa scriem un program executabil care sa afiseze
la consola un fisier text. Beneficiem de macroinstructiunile definite din io.h. cod 3.92.
In zona de date se rezerva spatiu pentru numele fisierului, pentru indicator (handler) si pentru bufer de citire de 1024 de
octeti. Reamintim ca operatiile cu perifericile sunt in esenta transferuri intre periferice si memorie. Programul afiseaza un
mesaj la consola dupa care citeste un nume de fisier. Se face apoi operatia de deschidere a fisierului specificata. Toate
functiile DOS de lucru cu fisiere introc CF=1 in caz de eroare. O eroare tipica la operatia de deschidere pentru citire este
un nume eronat de fisier. Se testeaza deci CF si in caz de eroare se afiseaza un mesaj adecvat si se iese in DOS.
Se trece acum la o bucla de citire-afisare. Functia de citire intoarce in AX numarul de octeti efectivi cititi(care este <= cu
cel cerut). Dorim sa afisam numai ce s-a citit efectiv, asa ca punem terminatorul 0 in buffer dupa ultimul octet efectiv citit.
Este posibil ca la ultima iteratie sa se citeasca 0 octeti. Se afiseaza la consola bufferul respectiv (cu macroinstructiunea
puts).
Daca numarul de octeti efectiv cititi este mai mic strict decat cel cerut (1024) inseamna ca s-a ajuns la sfarsitul fisierului si
bucla se termina. In caz contrar se reia o noua citire din fisier. In final, se inchide fisierul si se iese in DOS. Acest exemplu
ilustreaza foarte bine avantajele macroinstructiunilor. O actiune destul de laborioasa in limbaj de ansamblare (afisare
fisiertext) a putut fi codificata prin cateva linii de program sursa (e drept ca aproape toate sunt invocari de
macroinstructiuni). Daca reusim sa concepem un set de macroinstructiuni adecvat unei probleme (in cazul de fata
interfata cu sistemul DOS) scrierea programelor devine foarte comoda.
In cazul scrierii unui program similar, care sa realizeze copierea unui fisier disc in alt fisier, se pot cuprinde
macroinstructiunile : cod 3.93, care deschid un fisier pentru scriere, respectiv scriu un fisier. Parametrii sunt asemanatori
cu cei din macroinstructiunile o_read si f_read. Bucla de citire din fisier-afisare din exemplul anterior se inlocuieste cu o
bucla de citire din fisier sursa-scriere in fisier destinatie.
In unele situatii substitutia parametrilor formali cu cei actuali poate ridica unele probleme. Sa presupunem ca un
programator care invata limbajul ASM nu a ajuns la instructiunea XCHG, ci are cunostinta doar de instructiunile
PUSH,POP,MOV. El isi propune sa scrie o macroinstructiune care sa interschimbe 2 cantitati de 16 biti : cod 3.94.
Aparent, totul e in ordine. Totusi pot aparea situatii nedorite ca in secventa : cod 3.95. Aceasta invocare de
macroinstructiuni se expandeaza in : cod 3.96 si este evident ca registrul AX nu se modifica. Se poate insa si mai rau ca
in secventa cod 3.97 care se expandeaza in cod 3.98.
Pericolul apare deci in situatiile in care parametrii actuali intra in conflict cu anumite variabile sau registre care sunt
folosite in interiorul macroinstructiunii. Aceste trebuie evident evitate.
Utilizarea foarte mare a macroinstructiunilor devine evidenta la secvente de apel ale procedurilor cu parametri (in acest
caz se vorbeste de macroinstructiune de apel). Apelurile de functii sistem modificate anterior sunt cazuri particulare de
macroinstructiuni de apel.
Procedurile cu parametri utilizeaza diverse tehnici de transmitere a parametrilor. Parametrii trebuie clasati in anumite
registre sau in stiva, intr-o ordine specificata. Aceste detalii de apel sunt greu de tinut mine si nici nu intereseaza pe cel
care apeleaza procedura. Putem insa dezvolta macroinstructiuni care sa ascunda aceste detalii.
Sa consideram o procedura cu numele puts_proc, care afiseaza un sir de caractere (terminat cu 0). Adresa sirului
(offestul in cadrul segmentului curent) adresat prin DS se specifica in registrul SI. O macroinstructiune de apel se poate
scrie in forma : cod 3.99.
Ca parametru actual se poate utiliza orice operand compatibil cu instructiunea LEA, astfel daca exista definitia de date :
cod 3.100 se pot utiliza formele de invocare : cod 3.101.
In al doilea caz nu putem scrie puts BX pentru ca acest apel ar conduce la o expandare de forma lea SI,BX , ceea ce
este o eroare de sintaxa. Forma puts [BX] se expandeaza in LEA SI,[BX] care este corecta.
4.3. Macroinstructiuni repetitive
Aceste macroinstructiuni sunt predefinite, deci nu trebuie definite de utilizator. Scopul lor este de a genera secvente
repetate de program.
- Macroinstructiunea REPT (repeta) :
Forma generala de invocare este : Cod 3.114 in care n este o constanta intreaga. Efectul este repetarea
corpului macroinstructiunii de n ori. Secventa anterioara de generare a 3 mesaje s-ar putea scrie acum - Cod
3.115
Iata o secventa care genereaza un sir de caractere cu litere de la ‘A’ la ‘Z’ – Cod 3.116
- Macroinstructiunea IRP (repeta nedefinit) :
Forma generala de invocare este : Cod 3.117 in care p_formal este un parametru formal , iar lista_par_act este o lista de
parametri actuali separate prin virgule. Efectul este repetarea corpului macroinstructiunii de atatea ori cate elemente
contine lista de parametri actuali.
La fiecare repetare se substituie parametrul formal cu cate un parametru actual. Invocarea ( Cod 3.118 ) se va expanda
in Cod 3.119.
4.4. Operatori specifici
Exista o serie de operatori specifici macroinstructiunilor. Acestia controleaza in principal substitutia parametrilor actuali
fiind utili in special in macroinstructiunile repetitive.
4.4.1. Operatorul de substituire si de concatenare ( & )
Acest operator aplicat unui parametru formal realizeaza substitutia si ( eventual ) concatenarea sa cu un text fix sau un alt
parametru formal. Este necesar in contextul in care un parametru formal ar fi interpretat ca un simbol ( de exemplu, intr-un
sir constant de caractere sau intr-un identificator ) .
Sa presupunem ca dorim sa definim automat liniile de program : Cod 3.120
Vom apela , evident, la macroinstructiunea IRP. O prima incercare ar fi : Cod 3.121. Ceea ce este incorect
deoarece se va produce aceeasi linie de program. Prima aparitie a parametrului x nu poate fi distinsa de simbolul
mesaj_x, iar a doua nu poate fi distinsa de sirul constant text x . Aici intervine operator & , definitia corecta fiind : Cod
3.122 in care primul caz se concateneaza textul fix mesaj & cu parametrul formal x , iar in al doilea se substituie
parametrul formal x chiar daca apare intr-un sir constant de caractere.
Sa consideram un exemplu inrudit , codificat prin macroinstructiunea urmatoare : Cod 3.123
Aici e necesara concatenarea a doi parametri formali ( fix si x ) ; fiecaruia I se aplica operatorul & la stanga sau la dreapta
, dupa locul in care are loc concatenarea. Un apel de forma : Cod 3.124 va produce acelasi text ca macroinstructiunea
IRP de mai sus.
4.4.2. Operatorii de literalizare sir/character (<>,!)
Operatorul de literalizare sir ( <> ) se utilizeaza atunci cand se doreste ca un text in care apar eventualii
separatori ( spatii albe, virgule etc. ) sa fie considerat ca un unic parametru ( sa fie literalizat ) . Operatorul se utilizeaza
practice in definitii cat si in invocarile macroinstructiunii.
De exemplu, in definitia : Cod 3.125 dorim ca parametrul x de la nivelul exterior sa fie transmis ca atare catre
macroinstructiunea IRP. Invocarea se va face in forma : Cod 3.126 ceea ce are ca effect transmiterea listei ‘A’, ‘B’, ‘C’, ‘D’
ca un unic parametru.
Operatorul de literalizare character ( ! ) se aplica unui singur character , efectul fiind de a trata acel character ca
un character obisnuit ( fara a-l interpreta) . Se utilizeaza impreuna cu caractere care au semnificatii speciale in
macroinstructiune.
Sa consideram o macroinstructiune care defineste mesaje de eroare : Cod 3.127
O invocare de forma : Cod 3.128 se va expanda in Cod 3.129
Daca dorim insa sa generam un mesaj de forma ‘par_1 > par_2’ , intram in conflict cu semnificatia speciala a
caracterului > care intra in componenta operatorului de literalizare . Solutia este sa folosim operatorul ! si sa scriem : Cod
3.130 , ceea ce va genera mesajul correct : Cod 3.131
4.4.3. Operatorul de evaluare expresii ( % )
Acest operator se aplica unei expresii oarecare , efectul fiind evaluarea acelei expresii . Dintr-un anumit punct de vedere,
operatorul % este inversul operatorilor de literalizare .
Sa consideram macroinstructiunea : Cod 3.132 care genereaza date.
O invocare de forma : Cod 3.133 va produce liniile de program : Cod 3.134 .
4.5. Invocarea recursiva de macroinstructiuni
O macroinstructiune se poate invoca recursive , adica pe ea insasi. Ca si la procedurile recursive, cel mai important lucru
este oprirea recursivitatii , care se poate face cu directivele de asamblare conditionata IFB sau IFNB .
Sa consideram o macroinstructiune de salvare de registre in stiva . Vrem ca aceasta sa permita un numar variabil de
parametri . Solutia recursive este sa fixam un numar maximal de parametri si sa testam explicit daca primul parametru
este vid : Cod 3.135 .
Daca primul parametru formal nu este vid, se genereaza instructiunea push si apoi se invoca aceeasi macroinstructiune
cu restul de parametri . Aceasta forma poate fi folosita cu un numar oarecare de registre de la 1 la 6 , de exemplu : Cod
3.136 .
O alta varianta este cea repetitiva : Cod 3.137
Invocarea necesita insa operatorul de literalizare : Cod 3.138
4.6. Definirea macroinstructiunilor in macroinstructiuni
Se poate spune ca macroinstructiunile automatizeaza oarecum procesul de definire a datelor si a instructiunilor.
Mai mult decat atat, chiar definirea macroinstructiunilor se poate face prin intermediul altor macroinstructiuni.
Sa consideram un asemenea caz in cazul procesorului x8086 instructiunile de deplasare si de rotatie cu un numar de biti
mai mic sau egal cu 3 se executa mai rapid ca secvente de rotatie de cate un bit in comparatie cu instructiunile care
utilizeaza registrul CL . Dorim sa scriem cate o macroinstructiune pentru cele 8 instructiuni de deplasare si rotatie , fiecare
cu 2 parametri : sursa si numarul de biti , care sa se expandeze in secventa ultima ca timp de executie.
De exemplu, o invocare de forma : Cod 3.139 sa se expandeze in secventa : Cod 3.140 iar o invocare de forma Cod
3.141 ,in secventa Cod 3.142
Dorim ca generarea macroinstructiunilor ( avand numele de forma m_xxx , unde xxx este numele instructiunii
corespunzatoare ) sa fie facuta automat.
Incepem prin a defini o macroinstructiune care primeste ( in parametrul formal operation ) numele instructiunii respective
si genereaza macroinstructiunea corespunzatoare : Cod 3.143 .
La o invocare de forma : Cod 3.144 , se va genera automat macroinstructiunea m_shl , conform definitiei echivalente :
Cod 3.145 ,adica exact ce ne-am propus . Generam acum toate cele 8 macroinstructiuni , observand ca numele
operatiilor ( instructiunilor ) respective se compun din secvente RO , RC , SH, SA , la care se adauga sufixele R sau L .
Exploatam acest fapt prin doua macroinstructiuni repetitive : Cod 3.146 .
Aceasta secventa va defini macroinstructiunile m_ror, m_rol , m_rcr, m_rcl ,m_shr, m_shl , m_sar, m_sal .
[SP-=4* #regs];
[SP+=4* #regs];
Endif:...
Când se compară două valori, este important de ştiu că opusul lui ""mai mic decât" nu este "mai mare decât"; acesta
este "mai mare sau egal decât". Exemplele din figura 5.3 şi 5.4 prezintă acest lucru:
int32_t a,b;
if (a<100) b=1;
figura 6.3:
Incorect Corect
BG L1 ;NUL BGE L1
L1... L1:...
int32_t a,b;
if (a<100) b=1;
figura 6.4
Soluţia 1 Soluţia 2
L1...
Cand se adauga o conditie else trebuie sa adaugam un salt neconditionat in codul de Ansembler.(fig 6.5)
C Ansembler
STR R0,b
STR R0,c
END IF
Acest salt trebuie pus imediat dupa clauza (ramura) then (b=1) pentru a sari peste ramura else (c=2) astfel incat
doar o singura ramura va fi executata.
Y=x; BLT L1
BGT L1
L1:...................
Sa consideram problema putin diferita in sensul ca se face testarea cu x, aflat in afara domeniului :
C Ansembler
STRGT R0,b
STRLE R0,c
Prima instructiune din blocul IT este activa daca conditia din campul operand din instructiunea IT este adevarata.
Restul instructiunilor din bloc sunt controlate de una pana la 3 litere din mnemonica instructiunii IT. Daca litera este
T(then) sunt activate instructiunile corespunzatoare, daca litera este E(Else) sunt activate restul instructiunilor.
6.3.Implementarea buclelor
In general o bucla consta din 4 parti : initializare, testul pentru terminare sau continuare, reactualizarea variabilei
de control si campul public. Buclele se disting prin locul in care este plasat testul de finalizare/continuare: in fata sau la
sfarsitul corpului buclei. Pentru buclele while testul este plasat in fata corpului astfel incat aceasta se repeta de 0 sau mai
multe ori. Pentru buclele do while acest test este plasat la sfarsitul corpului si aceasta se repeta cel putin o data. Exista
bucle cu numar fix de iteratii. Toate buclele for, while sau do while prezinta un salt implicit de la sfarsitul, la inceputul
buclei. Acest salt devine explicit cand este translatat in ansamblare.(fig 6.7 si fig 6.8).
Done: STR
B top
Buclele care se repeta de un numar predefinit de ori sunt atat de utilizate incat setul de instructiuni prezeinta 2
instructiuni (CBZ si CBNZ) care combina o comparatie si un salt intr-o singura instructiune. Spre exemplu instructinunile
BEQ si CMP din 6.8 pot fi inlocuite cu o singura instructiune : CBZ R1,done.
6.4.Implementarea functiilor
Pentru a scrie o functie in limbaj de ansamblare care sa poata fi aplicata si intr-un limbaj de nivel inalt este nevoie
de a sti mai mult decat modul cum se transfera controlul de la program la functia apelanta si inapoi, cum se transfera
parametrii si cum se primeste valoarea returnata . Compilatoarele prezinta reguli despre locul unde se gasesc parametrii
functiilor, valoarea returnata de aceastea, modul de salvarea a registrilor temporari si folosirea acestora.
6.4.1.Apelul si revenirea dintr-o functie
Exista 2 instructiuni pentru apelul si revenirea dintr-o functie prezentat in tabelul 6.3. (slide 3, ppt 7), instructiunea
Branch and link (BL) salveaza adresa de revenire (Adresa instructiunii urmatoare de dupa instructiunea BL) in registrul LR
si executa saltul la adresa de inceput a functiei. Registrul LR este folosit de instructiunea branch indirect BX pentru
revenire prin copierea continutului din LR in programul counter (PC).
Aproape orice instructiune care copie adresa de revenire in PC poate fi folosita (BX LR este echivalenta MOV
PC,LR) dar nu se recomanda varianta a 2 pentru a se mentine compatibilitatea cu setul de instructiuni ARM.
Cand o functie nu prezinta parametri si valoare returnabila, compilatorul genereaza o singura instructiune BL. Un
exemplu simplu de dezactivare a intreruperilor este prezentat in fig 6.9.
DisableInterupts:
Directiva EXPORT spune ansamblorului sa faca cunoscut numele functiei catre link-editor, aceasta putand fi
apelata si din alte fisiere din codul sursa. Registrul LR poate memora o singura adresa de revenire, astfel functiile care
apeleaza alte functii trebuie sa il memoreze la intrarea lor si sa refaca la iesire continutul original al acestuia. Codul din
figura 6.10 (slide 18 ppt 7) face acest lucru utilizand instructiunile PUSH si POP. Ultimele 2 instructiuni (POP {LR} si BX
LR) pot fi inlocuite cu instructiunea POP {PC} .
6.4.2 Folosirea registrelor
Pentru ca o functie scrisa in asamblare sa poata fi apelata si in cod C , aceasta trebuie sa fie compatibila cu
modul in care compilatorul se asteapta sa functioneze. Dincolo de un simplu mecanism de transfer a controlului si
revenire dintr-o subrutina , mai trebuie indeplinite anumite cerinte:
1. Unde gasim parametrii trimisi catre functia noastra?
2. Unde plasma valorile de returnare astfel incat functia apelanta sa le gaseasca?
3. Care registri trebuie salvati si care pot fi modificati?
Din fericire, aceste cerinte sunt specificate de ARM ARCHITECTURE PROCEDURE CALL STANDARD (AAPCS)
prezentate in tabelul 6.4 { slide 15, ppt7 } . Aceasta foloseste registrii r0 si r1 pentru a retine valoarea returnata. R0 e
folosit pentru a returna valori pe 8, 16 , 32 de biti . Valorile pe 64 de biti sunt returnate in r0 si r1 , in r1 memorandu-se
partea superioara. Registrii r0 pana la r3 pot fi modificati in interiorul functiei fara a fi necesara salvarea lor . Majoritatea
celorlalti registri pot fi folositi la fel , dar continutul lor original trebuie memorat inainte de a fi modificat si restaurat inainte
de revenirea din functie .
6.4.3 Transferul parametrilor
Registrii r0 pana la r3 sunt folositi pentru transferul parametrilor ( figura 6.11 ) si sunt de obicei suficienti pentru
rutinele mici, scrise in asamblare .
C ASM
BL display
…..
Figura 6.11
Compilatorul incarca fiecare parametru de 8, 16 sau 32 de biti intr-un singur registru asignat de la stanga la dreapta ;
parametrii double word sunt plasati in registre consecutive ( r0 si r1, r1 si r2 sau r2 si r3 ) cu bitii MSB in al doilea registru
al perechii . Functia apelata poate modifica continutul registrilor r0 pana la r3 fara a fi nevoita sa refaca valoarea originala
a parametrilor continuti in ei .
6.4.4 Valorile de returnare
Se asteapta ca functiile sa returneze orice valoare intre 8, 16 sau 32 de biti in registrul r0 ( 0 filled sau signed
filled) in functie de tipul returnat ( cu sau fara semn) , figura 6.12 { slide 17 , ppt 7}
Functiile care returneaza valori pe 64 de biti trebuie sa faca aceasta folosind registrul r0 ( bitii 0-31) si r1 ( bitii 32-63) ,
figura 6.13 { slide 19 , ppt 7 }.
Daca o functie trebuie sa returneze mai multe rezultate , o practica comuna este aceea ca functia apelanta sa
trimita un pointer la o structura si sa lase functia apelata sa memoreze rezultate in structura
6.4.5 Memorarea registrelor
Functiile care modifica registrii trebuie in general sa-i memoreze si sa-i refaca folosind stiva . Din moment ce stiva
este retinuta in memorie, iar ciclii de memorare afecteaza performanta executiei (bottleneck) este important sa se salveze
cat mai putini registri posibili.
Situatia ideala ( functia f1 , figura 6.14 { slide 23 , ppt 7}) , care nu contine in interior alt apel de functie si care nu modifica
alti registri decat r0 pana la r3 , in acest caz nu este nevoie de memorarea registrelor.
Cand o functie modifica orice registru , altul decat r0 pana la r3, este necesara refacerea continutului original al acestuia.
Cel mai bun mod este de a pune acesti registri pe stiva, imediat dupa intrarea in functie si de a-i extrage de pe stiva
inainte de parasirea acesteia ( functia f2, figura 6.14 )
Cand o functie este apelata in interiorul altei functii, al doilea apel va suprascrie adresa de revenire din LR . De aceea
cand o functie ( functia f3 din figura 6.14 ) contine un apel ( BL ) catre o alta functie ( f4 ) , prima trebuie sa salveze
continutul LR pe stiva la intrare si sa-l refaca inainte de revenire .
Registrii r0 pana la r3 nu pot fi folositi de functiile care apeleaza alte functii pentru a pastra informatii , spre exemplu in
figura 6.14 , apelul functie f4 poate necesita parametri plasati in r0 pana la r3 si / sau pot modifica continutul lor inainte de
returnare. De aceea, o practica uzuala pentru f3 este de a muta parametrii sai in r4 pana la r11 si de a reface continutul
lor original , inainte de revenire .