StackVM - veremalapú virtuális gép
(eredetileg 2018 november-decembere környékén készült)
Miért?
Mindig is érdekeltek az alacsony szintű technikai részletek, például egy processzor működése vagy belső felépítése. Innen jött a motiváció hogy tervezzek egy saját virtuális gépet, amely egy saját programozási nyelven programozható. Ezzel kapcsolatos első próbálkozásom ez a projekt.
Mi az a stack (verem)?
A verem egy olyan adatszerkezet, amely egy kupacot modellez. A kupac tetejére tehetünk új elemet, vagy levehetjük a tetején levőt, de a többi elemhez nem nyúlhatunk. Amit legutoljára tettünk a kupacra, azt fogjuk legelőször levenni - azaz ez egy last-if-first-out adatszerkezet (LIFO).
(nem összekeverendő a heap-el, amit kupacnak szoktak fordítani! Az teljesen más dolog, és nem sok köze van ehhez!)
A verem segítségével könnyedén hozhatóak létre lokális névterek, és a verem segítségével akár változók nélkül is írhatunk (egyszerűbb) programokat.
Virtuális gép
Az én virtuális gépem egy végrehajtó egységből és hozzá tartozó memóriából áll. A memória egyetlen megosztott adatblokk, nincs külön címtere az adatoknak, utasításoknak, veremnek. A végrehajtó egység az utasításokat sorban beolvasva hajtja őket végre.
Parancskódok
Mivel az egész gép a veremre épül, mindenképpen kell alapvető veremmanipuláció, mint a push
, drop
vagy dup
. Ha nem csak a legfelső elemet akarom elérni, kellhet még swap
és rot
is.
A matematikai műveletek közül a gépem ismeri az összeadást, kivonást, szorzást, osztást és maradékképzést. Minden számot 16 bites előjeles egészként kezel.
Az összetettebb logikához összehasonlító parancsok is kellenek, mint a kisebb, nagyobb vagy egyenlő.
A feltételes kódblokkok kivitelezéséhez tartalmaz goto
és jz (jump zero)
utasításokat is.
Az újrahasználható függvények írásához a VM tartalmaz egy call stack-et is, és az ehhez tartozó call
és ret
parancsokat.
A külvilággal való kapcsolattartásra az inchar
, innum
, illetve outchar
és outnum
parancsok használhatóak.
A gép leállítható a halt
paranccsal.
Assembler
Írtam egy nagyon egyszerű “assmebler-t” Pythonban, ami az előbbi parancsokat a forráskód segítségével lefordítja bináris formátumra. Mivel minden parancskód 1-1 értéknek felel meg, és a push
kivételével még paramétere sincs egyiknek sem, a fordítás nagyon egyszerű. Az egyetlen bonyolultabb dolog egy címke-rendszer ami megengedi az előrehivatkozást is. A forrásfájlok szintaxisa így a FORTH és az ASM keveréke lesz. A fordító az egész bemenetet keresztülfuttatja a C előfeldolgozón (cpp)
, így használhatóak #define
-ok és feltételes fordítás is.
Hibakezelés
A VM osztály egy boolean flag-ben tárolja hogy a gép fut-e még. Ha valamilyen hiba lép fel, vagy halt
parancsot kap, a gép leáll. A hibáról tárolja a hibakódot, amiből kiderül hogy milyen jellegű hiba lépett fel (nullával osztás, hibás cím, stb. ), a hibát okozó utasítás címét, és egy paramétert, ami hibától függően vesz fel értéket.
Könyvtár
A programot egy könyvtár formájában készítettem el, így könnyen beépíthető más programokba, ha kedvünk tartja. Mindemellett nincs mód egyéb kommunikációra a host és a VM között mint a négy alap parancs, így főleg nem látom sok értelmét beépíteni…
Konklúzió
- a stack kiváló lokális változók tárolására
- az utasításkészlet egyszerű de primitív
- jó lenne stack-relatív indexelés mint címzési mód
- az IO parancsok helyett célszerűbb lenne egy általános
SYSCALL
és/vagyREAD/WRITE
parancs, amit a host implementálhatna tetszése szerint. Esetleg követhetném a unixos “minden file” utat… - jó lenne osztott memória a host rendszerrel
- STDLIB nélkül elég keveset tud a gép
- lehetne írni magasabb nyelvű fordítót
- NEM OOP-képes, ahhoz az adatmodell teljes újratervezése lenne szükséges