Kód: Vybrat vše
ICM
Projekt virtuálního stroje, interpret mezikódu. Má jedinou pamět - zásobník,
ten začíná na adrese 0. V tomto základním provedení nemá ani registry, je to
zásobníkový stroj. Všechny proměnné tedy musíme držet na zásobníku.
Každá položka zásobníku je ostře typovaná (pamatuje si svůj typ), obvyklé
základní typy - integer, byte, real, string, pointer, fp (frame pointer,
návratová adresa při skoku do procedury).
SP (Stack pointer) - ukazatel na vrchol zásobníku.
GP - ukazatel na dno zásobníku, pomocí něj se na začátku do zásobníku nasypají
globální proměnné. Pak se přes GP k nim přistupuje (na to budeme potřebovat znát
offsety těchto globálních proměnných).
FP (frame pointer) - ukazatel na hranici mezi paramety volané funkce a začátkem
lokálních proměnných (FP se pohybuje při volání/návratu z procedury).
Instrukce INIT<typ>
Tyto instrukce vyhradí na zásobníku krabičku daného typu, ale bez definované
hodnoty. Takto se vyrábí proměnné.
Ale to nemusíme vůbec dělat, protože o globální a lokální proměnné se samy
postarají tabulky překladače. My si jen budeme muset z tabulek zjistit, kde
jsou proměnné uloženy.
Jediná situace, kdy INIT potřebujeme, je vytvoření krabičky pro návratovou
hodnotu funkce.
Instrukce DTOR<typ>
Destruktor krabičky daného typu (z vrcholu zásobníku). U proměnných opět
automaticky, ale musíme použít u návratové hodnoty funkce.
Volání funkcí
function F(I: int; R: real):string;
A := F(V1,V2);
Postup:
1. zavolat INITS pro vytvoření krabičky pro návratovou hodnotu
2. spočítat V1, výsledná hodnota musí zůstat na zásobníku
3. spočítat V2, výsledná hodnota musí zůstat na zásobníku
4. Vygenerujeme instrukci call, ta si uloží FP
5. ...nyní pracuje funkce
6. zavolá se instrukce RET (nemusíme psát, vygeneruje se sama)
Tato instrukce umaže ze zásobníku lokální proměnné
7. DTORR - smazat 2. parametr ze zásobníku
8. DTORI - smazat 1. parametr ze zásobníku
Na zásobníku nám zůstane návratová hodnota volané funkce.
Rozdíl mezi funkcí a procedurou je ten, že procedure nevolá na začátku
INIT pro místo na návratovou hodnotu.
Získání konstanty:
LDLIT<typ> - parametrem je ukazatel do tabulky kde je daná hodnota
LDLITB - očekává přímo číslo 0 nebo 1
CVRTIR - sežere z vršku zásobníku integer a místo něj vloží real,
tedy konverze int -> real
Obvyklé instrukce pro čísla:
ADDI
ADDR
MULI
ADDS - binární plus na stringy, zřetězení
pLDx - to jsou operace LOAD, něco přečtou a dají na vrchol zásobníku, parametrem
je offset
p = pointer, můžeme si vybrat:
- GLDx (adresuji vůči GP)
- LLDx (vůči FP, čeká se znaménkové číslo)
- SLDx (vůči SP)
x = typ
pSTx - operace STORE, sežere položku ze zásobníku
Jak generovat kód v překladači?
To co je mezi begin a end funkce si musíme někde udržet, tj. ve všech
neterminálech (v jejich atributech), které jsou pod begin a end (neplatí pro
begin end u příkazů, platí jen pro hlavní begin a end programů a funkce). Až
po překladu celé funkci vložíme celý její kód na výstup.
Typ položky pro držení mezikódu: icblock_pointer
symbol_tables::set_main_code
symbol_tables::subprogram
- parametrem těchto funkcí je vygenerovaný mezikód daného hlavního programu
nebo podprogramu.
icblock_create - vyrobí prázdný mezikód (držák na mezikód)
icblock_append_delete - slepí 2 bloky mezikódu do nového a uvolní původní
icblock_pointer má metodu append_instruction() pro přidávání jednotlivých
instrukcí, append_instruction_with_target() pro potřebu skoku. Parametrem
těchto funkcíje třída pro danou instrukci. Každá instrukce, které byly zmíněny,
mají své třídy. Jejich konstruktory očekávají příslušné parametry instrukce
(pokud jsou nějaké potřeba, čassto ne protože se pracuje s argumenty na
zásobníku).
variable_symbol::address() - získávání offsetů (globálních a lokálních)
proměnných z tabulek
Instrukce CALL přijímá parametr, který získáme voláním subprogram_symbol::code()
RECORDY
Typ record_type má metodu find() pro hledání položek záznamu. Pokud daná
položka existuje, vrátí se nám typ field_entry. Z toho opět potřebuju zjistit
typ položky recordu a offset (pozice položky ve struktuře). Offset přičteme
k adrese počátku recordu.
record_type má metody begin() a end(), které vrací iterátory, abychom mohli
projít všechny položky struktury. To potřebujeme při přiřazení struktury. V
takové situaci musíme překopírovat jednu položku po druhé (nejprve load
na vrchol zásobníku, pak store do cílového místa). Položka struktury může
být samozřejm opět struktura.
PŘÍKLAD: VÝRAZ A SČÍTÁNÍ
%type<V> factor, nasobeni, scitani, konstanta
konstanta: UINT { $$.ic = icblock_create();
$$.ic.append_instruction(new LDLITI($1)); }
| STRING { ... }
;
factor: konstanta { $$.ic = $1.ic; }
| promenna { // najit kde lezi
append_instruction(new LLDI) }
| ( vyraz ) { ... }
;
scitani: scitani ADDOPER nasobeni { $$.ic = $1.ic;
icblock_append_delete($ii.ic,$3.ic);
// na zasobniku nyni lezi vysledek scitani
//a nasobeni
$$.ic.append_instruction(new ADDI);
}
| nasobeni