LFA - Constructia compilatorului - masina virtuala - Cap 6 [pg 56-60]
Continuam explicatiile incepute luni privind implementarea masinii virtuale.
Vedeti postarea anterioara LFA.
Astazi este ora de laborator, din saptamana a 11-a, deschideti manualul de Constr Comp. cu Flex si bison la pagina unde se afla "Modulul "Masina Virtuala cu Stiva", SM.h". aprox pg 56.
Are you ready ? Sunteti gata ?
Din acest capitol invatam sa implementam efectiv o mica masina virtuala, in fond un emulator de procesor.
Aveti nevoie de dosarul Lab4, cu toate update-urile pe care il copiati in dosarul Lab6.
Acum va rog sa deschideti cu editorul favorit un fisier nou pe care il veti numi SM.h
Ce scriem in el ? Va explic:
Intai o lista a denumirilor instructiunilor. Pentru a defini o serie de constante intregi numite HALT, STORE, JUMP ... nu folosim:
#define HALT 0
#define JUMP 1
samd ca ar fi prea lung.
Folosim: enum
enum code_ops { HALT, STORE, JMP_FALSE, GOTO,
DATA, LD_INT, LD_VAR,
READ_INT, WRITE_INT,
LT, EQ, GT, ADD, SUB, MULT, DIV, PWR };
Acest enum da pe rand valori succesive constantelor de pe lista.
Pentru a lista codul generat am insa nevoie de denumirile tiparibile ale instructiunilor, altfel daca as tipari cu printf sau altceva ar iesi la vedere numerele nu denumirile instructiunilor.
Din acest motiv punem si un vector de stringuri care contine toate numele instructiunilor.
char *op_name[] = {"halt", "store", "jmp_false", "goto",
"data", "ld_int", "ld_var",
"in_int", "out_int",
"lt", "eq", "gt", "add", "sub", "mult", "div", "pwr" };
O instructiune, in acest C in care e facut compilatorul se scrie ca un struct, (o structura, un record, e ca o clasa fara metode).
struct instruction
{
enum code_ops op;
int arg;
};
Memoria este simulata nu ca un vector ci ca doi vectori. De ce ? Fiindca instructiunile sunt compuse si datele din memorie sunt simple.
struct instruction code[999];
int stack[999];
Acum definim registrii procesorului:
int pc = 0 ;
struct instruction ir ;
int ar = 0 ;
int top = 0 ;
char ch ;
Cititi paragraful de la pg 58 de deasupra scrierii lor. Incepe cu Urmeaza...[pg 58].
Ciclul fetch-execute, ciclul adu si executa este poartea cea mai importanta a masinii virtuale, el executa dce fapt instructiunile pe masura ce le aduce din memorie:
Sa explicam un pic din ce este format:
- este o bucla do { ... } while ( conditie ), conditia fiiind [pg 60] ca in registrul IR sa nu fie o instructiune cu codul HALT, adica ir.op != HALT .
Ati gasit conditia ?
Acum sa vedem exact descrierile instructiunilor: [pg 59]
Ce e important sa stiti aici este ca sunt, majoritatea lor, instructiuni care lucreaza cu expresiile de pe stiva, in ideea ca avem de calculat valorile unor expresii scrise in forma poloneza infixata, cu ajutorul unei stive. Se va adauga o instructiune STORE pt stocat rezultatele in memorie si doua pentru citit valorile care intra in expresie, constante sau variabile, LD_INT si LD_VAR.
Calculele nu se fac in acumulator, cum v-ati fi asteptat, ci direct pe stiva, la aceasta masina virtuala.
Instructiunile: LD_INT, ADD, STORE
De exemplu adunarea: x = 3+4 va da un cod (dupa compilare)
LD_INT 3
LD_INT 4
ADD
STORE @X <- adica "STORE la adresa lui x" , o va sti compilatorul pe adresa
Iar in stiva vom avea
| 3 |
------
apoi vine si 4 deasupra
| 4 | <- Top indica aici
------
| 3 | <- Top -1 indica aici
------
apoi vine adunarea, vedeti la case ADD:
case ADD: stack[top-1] = stack[top-1] + stack[top];
top--;
break;
Si dupa cum aceea vine ADD si aduna stack[top-1] + stack[top] pune rezultatul in stack[top-1]
| 4 | <- Top indica aici
------
| 7 | <- Top -1 indica aici
------
... si indicatorul il scade cu o unitate, ceea ce inseamna ca practic se face un POP si se scoate o valoare din stiva;
------
| 7 | <- Top indica aici.
------
Apoi vine STORE @X , adica STORE urmat de adresa lui X. Si scoate acel 7 din stiva si il pune la adresa lui X. Adresa lui X ii vine ca argument al instructiunii STORE, a avut grija compilatorul sa o puna la locul potrivit in instructiune, ca argument al acesteia: ir.arg.
case STORE : stack[ir.arg] = stack[top--]; break;
Si in final stiva ramane astfel si fara 7 si Top indica sub locul unde a fost 7-le.
------
| | 7 fusese aici.
------
<- Top indica aici.
Alte instructiuni, folosesc tot stiva ca sursa de parametri:
Ia uitati-va la:
Instructiunea WRITE_INT - ia ultima valoare din stiva si o tipareste.
Daca am fi avut de compilat:
WRITE (3-4)
Codul generat ar fi fost:
LD_INT 3
LD_INT 4
SUB
WRITE_INT
Iar in stiva vom avea
| 3 |
------
apoi vine si 4 deasupra
| 4 | <- Top indica aici
------
| 3 | <- Top -1 indica aici
------
apoi vine scaderea, vedeti la case SUB:
case SUB: stack[top-1] = stack[top-1] - stack[top];
top--;
break;
Si dupa cum aceea vine SUB si scade stack[top-1] - stack[top] pune rezultatul in stack[top-1]
| 4 | <- Top indica aici
------
|-1 | <- Top -1 indica aici
------
... si indicatorul il scade cu o unitate, ceea ce inseamna ca practic se face un POP si se scoate o valoare din stiva;
------
|-1 | <- Top indica aici.
------
Apoi vine WRITE_INT , care iata ce face:
case WRITE_INT : printf( "Output: %d\n", stack[top--] );
Scoate din stiva rezultatul calcului si il afiseaza folosind, in cazul nostru, "apelul de sistem" printf. In realitate, la alte compilatoare se face un CALL la o anumita adresa speciala de sistem, sau se pun parametrii in niste registri si se porneste o anumita intrerupere. Cei care is mai amintesc acea carte de apeluri de sistem DOS stiu la ce ma refer.
READ_INT este inversul lui write, citeste de la tastatura si pune in stiva.
De obicei apare in situatia compilarii unui:
read (x);
care va genera un cod:
READ_INT @X
adica il citesc pe X de la tastatura, il pun in stiva in locatia destinata lui X.
Sa zicem ca executam acest READ_INT si se tasteaza un 7.
case READ_INT : printf( "Input: " );
scanf( "%ld", &stack[ar+ir.arg] );
break;
Apare promptul/prompterul Input:
... ... acum tastam pe 7. Si READ_INT il puned in stiva (ar=0) la locatia data la adresa argument a instructiunii.
Nota: registrul ar s-ar folosi la instructiuni cu variabile locale ale functiilor, ceea ce limbajul Simple nu are.
Instructiunea JUMP_FALSE adresa
Va servi la compilarea if-urilor si while-urilor, atunci cand se sare la o anumita adresa, aceasta va fi pusa de compilator in codul generat, ca argument al instructiunii JUMP_FALSE.
Remarcati cum se executa JUMP_FALSE:
case JMP_FALSE : if ( stack[top--] == 0 )
pc = ir.arg;
break;
E bine de stiut: Mai inainte de ea este o bucata de cod care evalueaza conditia, si bineinteles, o pune in stiva (credeti-ma pe cuvant!).
stack[top--]
indica valoarea stack[top] si o scadere pe urma a lui top cu o unitate !
Deci de fapt scot valoarea conditiei din stiva !
Daca este 0, 0 = conditie falsa, atunci fac saltul , incarcand in PC - program counter, adresa-argument a lui JUMP_FALSE, el ingloband intotdeauna adresa destinatie.
Instructiunea LD_VAR adresa de variabila:
Ati gasit-o pe pagina [59] ?
Sa citim codul !
case LD_VAR : stack[++top] = stack[ar+ir.arg]; break;
La noi ar=0, deci conteza doar adresa variabilei, ir.arg, furnizata ca argument al instructiunii. De acolo, de pe stiva (variabilele stau la baza stivei) se ia valoarea variabilei, si se pune copia acelei valori taman in varful stivei pentru a participa la calcule.
Instructiunea GOTO.
Pentru salturi, neconditionate, ca la if, cand termini de executat ramura then si sari peste ramura else, sau e nevoie de ea la alt fel de bucle. Incarca adresa destinatie in reg PC ca executia sa continue de acolo.
case GOTO : pc = ir.arg; break;
Instructiunea DATA.
Este pentru compilarea declaratiilor. Daca am 3 variabile, compilatorul va genera :
DATA 2 (2 = 3-1)
Si la executia:
case DATA : top = top + ir.arg; break;
Din stiva care initial nu are nimic.
------
| 0 | <- Top indica aici.
------
Obtinem:
------
| 0 | <- Top indica aici.
-----
| 0 |
------
| 0 |
------
facand astfel loc in baza stivei pentru cele 3 variabile.
Restul instructiunilor functioneaza similar cu ADD, comparatiile fiind un fel de adunari care dau 0 sau 1.
Continuati cu paginile 59-60.
Refaceti compilatorul conform indicatiilor din paginile 59-60 si modului de lucru din anexa 2.
Compilati programele din Lab1 cu noul compilator, dar nu va mirati ca nu face mare lucru, fiindca el nu genereaza inca cod.
Astept imagini cu compilatorul ruland !
Atentie: Aveti in vedere ca pot cere compilatorul si coduri compilate cu el la final de semestru si la examen.
Sfarsit.
Niciun comentariu:
Trimiteți un comentariu