Virtuaalikoneiden generointi Vmgen-kääntäjällä Risto Saarelma Helsinki 18.4.2005 Ohjelmointikielten kääntäjät -kurssi HELSINGIN YLIOPISTO Tietojenkäsittelytieteen laitos
1 Johdanto 1 Virtuaalikoneet ovat tekniikka, jota käytetään usein tulkattavien ohjelmointikielten tai useammilla laitealustoilla ajettavien käännettyjen ohjelmien toteuttamiseen. Virtuaalikone muuttaa ohjelmakoodia laskentaoperaatioiksi aivan kuten fyysinenkin suoritin, mutta se on toteutettu ohjelmallisesti eikä fyysisellä elektroniikalla. Tämä artikkeli käsittelee virtuaalikoneita ja niiden toteuttamista Vmgen-kääntäjällä. Vmgen:in kuvaus perustuu lähteeseen [EGKP02]. 2 Virtuaalikoneet Virtuaalikone on fyysisessä tietokoneessa ajettava ohjelma, joka suorittaa virtuaalikoneelle käännettyä tavukoodia. Virtuaalikone voi olla emulaattori, joka suorittaa jonkin olemassaolevan fyysisen suorittimen konekieltä mielivaltaisella alustalla, tai se voi perustua suoritusarkkitehtuuriin, jota ei ole toteutettu missään fyysisessä suorittimessa. Tässä artikkelissa keskitytään jälkimmäiseen tyyppiin, eli puhtaasti virtuaalisiin arkkitehtuureihin. Virtuaalikoneen toteutustavassa yksi perustavanlaatuinen valinta on, tehdäänkö sen arkkitehtuurista rekisteripohjainen ja pinopohjainen. Ero koskee sitä, miten kone säilyttää käsittelemiään tietoalkioita. Rekisteripohjaisessa arkkitehtuurissa tietoalkiot säilötään joukkoon nimettyjä muistipaikkoja (eli rekistereitä), kun taas pinopohjaisessa arkkitehtuurissa alkioita säilytetään pinotietorakenteessa. Fyysiset suorittimet ovat pääsääntöisesti rekisteripohjaisia. Vaikka se muistuttaakin vähemmän fyysisiä suorittimia, pinopohjainen arkkitehtuuri on kuitenkin yksinkertaisempi ja sille on helpompi generoida koodia. Virtuaalikonearkkitehtuurit, esimerkiksi Javan JVM, ovatkin usein pinopohjaisia. Myös Vmgenkääntäjä on suunniteltu erityisesti pinopohjaisten virtuaalikoneiden tuottamiseen. 2.1 Virtuaalikoneiden käyttö Virtuaalikone on usein hyvä ratkaisu ohjelmissa, joissa halutaan suorittaa vasta ohjelman ajon aikana tulkattavaa ohjelmakoodia. Toinen käyttötarkoitus on saman käännetyn tavukoodin alustariippumaton suorittaminen. Koska virtuaalikone on toteutettu ohjelmallisesti, se toimii täysin samalla tavalla millä tahansa fyysisellä suoritinarkkitehtuurilla. Ohjelmallinen toteutus tuottaa myös vakiosuuruisen hidastuksen virtuaalikoneella ajettavan ohjelman suoritukseen. Vmgen:in tuottamat virtuaalikoneet suorittavat ohjelmia noin kymmenen kertaa hitaammin kuin fyysinen suoritin.
2 Tulkattavan ohjelmointikielen suoritus on hyvin luonteva sovellus virtuaalikoneelle. Yksinkertaisia komentosarjoja voidaan suorittaa jäsentämällä ne ja käynnistämällä toimintoja suoraan jäsentäjästä. Vuorovaikutteinen komentorivitulkki voi aivan hyvin toimiakin näin. Jos ohjelmointikielessä on kuitenkin silmukkarakenteita, päädytään tilanteeseen jossa samat lauseet on jäsennettävä yhä uudelleen. Käytännössä tämä on hyvin tehotonta. Selväkielinen ohjelma on muunnettava johonkin tehokkaammin suoritettavaan muotoon. Paras suorituskyky olisi saavutettavissa jos tulkattava kieli voitaisiin muuntaa suoritusalustan konekieleksi 1. Tehokkaan konekielen tuottaminen on kuitenkin usein varsin monimutkaista. Tämä ratkaisu olisi myöskin sidoksissa yhteen tiettyyn suoritusalustaan, ja ohjelmien halutaan hyvin usein toimivan useammilla alustoilla. Useimmissa tapauksissa siis tulkkiohjelmaan kannattaa rakentaa virtuaalikone, jonka ymmärtämäksi tavukoodiksi tulkattavan kielen ohjelmat käännetään. Tavukoodin suoritus on suoraviivaista, ja ohjelmakoodin jäsentämiseen verrattuna varsin tehokasta. 3 Vmgen-kääntäjä Virtuaalikoneen ohjelmointi käsin on työlästä. Jokaista virtuaalikonekäskyä kohden on kirjoitettava paljon samanlaisena toistuvaa boilerplate-koodia. Kaikkien operaatioiden on käsiteltävä virtuaalikoneen pinoa, ja niiden olisi myös hyvä tarjota tukea profiloinnille, virtuaalikoodin disassembloinnille ja debuggaukselle. Virtuaalikoneen tehottoman ja virhealttiin käsin C-kielellä ohjelmoinnin sijasta voidaan käyttää Vmgen-kääntäjää, joka tuottaa korkean tason pinokonekuvauksesta C-kielisen lähdekoodin. 3.1 Vmgen:in kuvauskielestä Vmgen-kuvauksessa esitetään käskyn nimi, tätä vastaava pino-operaatio ja käskyyn liittyvä C-koodi. Vähennyslaskuoperaatiota kuvaa seuraava katkelma: sub ( i1 i2 -- i ) i = i1 - i2; 1 Tässä vaiheessa ei oikeastaan enää ole järkevää puhua tulkatusta kielestä. Nykyiaikaiset JITkääntämistekniikat tosin saattavat tehdä tulkatun ja käännetyn ohjelmakoodin eron käytännössäkin jonkin verran sumeaksi.
3 Käsky sub nostaa pinosta kaksi päällimmäistä arvoa nimillä i1 ja i2 ja painaa näiden tilalle arvon i. Tätä vastaava C-kielinen lause taas kertoo, että i:hin sijoitetaan i1:n ja i2:n erotus. Vmgen siis tuottaa virtuaalikoneen takaosan. Käskyt joita koneelle syötetään on tuotettava itse, korkean tason ohjelmakoodia jäsentämällä tai muilla keinoin. Vmgen-ohjelman tulkkiesimerkissä tämä osuus on toteutettu lexillä ja yaccilla rakennetulla yksinkertaisella parserilla. Vmgen tukee useampaa pinoa ja mahdollistaa myös käskyvuon käsittelemisen eräänlaisena pinona. Esimerkiksi käskyvuosta luetun vakioarvon työntäminen pinoon tapahtuu seuraavalla syntaksilla: push ( #i -- i) 3.2 Ylikäskyt Käskyjä jotka toistuvat virtuaalikoneen suorituksessa usein perättäin voidaan yhdistää ylikäskyiksi engl. superinstructions. Tämä vähentää virtuaalikoneen suoritusaskelten määrää ja nopeuttaa siten suoritusta. Vmgen:in profiloijaa käyttämällä voidaan tarkastella mitkä perättäisten käskyjen sarjat toistuvat erityisen usein. Ylikäskyt määritellään yksinkertaisesti luettelemalla ylikäskyä vastaava käskyjono: push_add = push add 3.3 Rekisteripohjaisten virtuaalikoneiden toteuttamisesta Jos halutaan virtuaalikoneen käyttävän pinon sijasta rekistereitä, yksi ratkaisumalli on jättää pino kokonaan käyttämättä ja antaa käskyvuossa rekisteritunnuksia. Esimerkiksi vähennyslaskuoperaatio esitettäisiin tällöin seuraavasti: sub ( #isrc1 #isrc2 #idest -- ) reg[idest] = reg[isrc1] - reg[isrc2]; Toinen mahdollisuus on toteuttaa hybridiratkaisu, jossa käytössä ovat sekä pino että rekisterit, ja rekistereihin on alkeiskäskyjen tasolla pääsy vain rekisteristä pinoon lukevalla load-käskyllä ja rekisteriin pinosta kirjoittavalla store-käskyllä. Muut rekisterejä käyttävät käskyt toteutetaan ylikäskyinä:
4 rsub = load load sub store 4 Vmgen:in toteutuksesta Virtuaalikoneella ei koskaan päästä samaan suorituskykyyn kuin fyysisen suoritinarkkitehtuurin konekielellä, koska virtuaalikoodia suoritettaessa fyysinen suoritin joutuu virtuaalikoodin operaation lisäksi suorittamaan virtuaalikoneen vaatimia operaatioita. Virtuaalikoneen toteutustapa voi kuitenkin vaikuttaa hidastuskertoimeen hyvin merkittävästi. Vmgen:in kehittäjät väittävät että ero naiivin ja tehokkaan toteutuksen välillä voi olla 1000-kertaisen hidastumisen kaventuminen 10-kertaiseksi [EGKP02]. Vmgen:issä onkin pyritty mahdollisimman tehokkaaseen toteutukseen. Vmgen käyttää esimerkiksi seuraavia optimointitekniikoita [EGKP02]: säikeistetty koodi pinon päällimmäisen arvon säilyttäminen rekisterissä virtuaalikäskyjen yhdistäminen ylikäskyiksi muuttumattomien pinoarvojen tallettamisen sivuuttaminen ja seuraavan virtuaalikäskyn aikataulutus haaranennakointitarkkuuden parantaminen. 4.1 Säikeistetty koodi Säikeistetyllä koodilla engl. threaded code ei tässä yhteydessä tarkoiteta rinnakkaisohjelmoinnin säikeistystä, vaan James R. Bellin [Bel73] kehittämää ohjelmointitekniikkaa, jossa virtuaalikäskyjen esitys muistissa on käskyä vastaavan aliohjelman muistiosoite. Tällä tekniikalla vältytään operaatiolta, jossa etsitään käskykoodia vastaava aliohjelma, koska aliohjelman osoite on jo suoraan käskykoodissa. Tekniikkaa ei voida toteuttaa ANSI C:llä, mutta GNU C:n labels-as-values -laajennuksella kylläkin [EGKP02]. 4.2 Pinon päällimmäisen arvon säilyttäminen Vmgen voi haluttaessa säilyttää virtuaalikoneen pinon päällimmäistä arvoa fyysisen suorittimen rekisterissä virtuaalikoodin suorituksen aikana. Tämä nopeuttaa suoritusta, kos-
5 ka pinohakuja tarvitaan vähemmän. Jos vain Vmgen:in generoima koodi käsittelee pinoa, tämä optimointi ei näy virtuaalikonekoodin käyttäjälle. Jos pinoa kuitenkin halutaan käsitellä eksplisiittisesti, esimerkiksi siirtämällä pino-osoitinta, on samalla huolehdittava siitä että säilötty päällimmäinen arvo pysyy oikeana tai vaihtoehtoisesti kytkettävä tämä optimointi pois päältä. 4.3 Muuttumattomien pinoarvojen talletus Jos käskykuvauksen pinomuunnoksessa sekä vanhassa että uudessa pinossa on sama arvo, naiivi toteutus kirjoittaisi uuden pinon arvon itsensä päälle. Esimerkiksi käskyssä dup ( i -- i i ) kirjoitettaisiin i pinoon kahteen kertaan, vaikka vain yksi kirjoitus olisi tarpeen. Vmgen osaa tarkastella pinokuvauksia, ja jos sama symboli esiintyy sekä lähtö- että tulospinossa, se jättää sen kirjoittamatta. Vmgen:in on huomioitava onko pinon päällimmäisen arvon säilyttäminen rekisterissä käytössä. Tässä tapauksessa arvo on talletettava rekisteristä muistiin, eikä tätä optimointia voida käyttää. Muuttumattomien arvojen talletuksen poistaminen ei myöskään toimi ylikäskyjen sisällä. 4.4 Muut parannukset Virtuaalikäskyjen aikataulutus ja haaraennakointi ovat parannuksia, jotka liittyvät C-kääntäjän tapaan optimoida koodia ja konekielikoodin tehokkaaseen suoritukseen. Tarkemmat toimintaperiaatteet eivät täysin minulle auenneet. 5 Lopuksi Julkaisu [EGKP02] käy läpi vaihtoehtoisia virtuaalikonegeneraattoreita, mutta pitää Vmgen:iä näitä ilmaisukykyisempinä. Useat muut virtuaalikoneprojektit on tehty yhden virtuaalikonearkkitehtuurin tarpeisiin, kun taas Vmgen on suunniteltu yleispätevämmäksi ja sille on olemassa toteutukset ainakin Java- ja Forth-virtuaalikoneille. Vmgen:in ratkaisu, jossa virtuaalikäskyjä vastaavat mielivaltaiset C-lauseet, tekee siitä ilmeisesti useita muita virtuaalikonejärjestelmiä ilmaisuvoimaisemman. On olemassa myös erilaisia matalan tason koodingenerointikirjastoja, mutta nämä eivät automatisoi erityisesti virtuaalikoneen tarvitsemia toimintoja samalla tapaa kuin Vmgen.
Lähteet 6 Bel73 Bell, J. R., Threaded code. Communications of the ACM, 16,6(1973), 370 372. EG02 Ertl, M. A. Gregg, D., Building an interpreter with vmgen. Computational Complexity, 2002, 5 8, URL http://citeseer.ist.psu.edu/ertl02building.html. EGKP02 Ertl, M. A., Gregg, D., Krall, A. Paysan, B., Vmgen a generator of efficient virtual machine interpreters. Software Practice and Experience, 32,3(2002), 265 294. URL http://citeseer.ist.psu.edu/470069.html.