Javan tavukoodi Ari Silvennoinen Timo Suomela Helsinki 1. huhtikuuta 2005 HELSINGIN YLIOPISTO Tietojenkäsittelytieteen laitos Ohjelmointikielten kääntäjät kevät 2005 i
Sisältö 1 Javan virtuaalikone 1 1.1 Virtuaalikoneen rakenne........................ 1 1.2 Tietotyypit............................... 1 1.3 Suorituspino ja kehykset........................ 2 2 Luokkatiedoston rakenne 4 3 Tavukoodi 5 3.1 Käskyt.................................. 5 3.1.1 Lataus- ja tallennuskäskyt................... 5 3.1.2 Aritmeettiset käskyt...................... 6 3.1.3 Tyyppikonversiot........................ 6 3.1.4 Olioiden luonti ja manipulointi................ 7 3.1.5 Metodien kutsuminen...................... 7 3.1.6 Operandipinon suora käsittely................. 8 3.1.7 Kontrollivuon hallintaa..................... 8 3.1.8 Poikkeuksien heitto ja käsittely................ 9 3.1.9 Synkronointi........................... 9 3.1.10 Erikoiskäskyt.......................... 9 ii
1 Javan virtuaalikone Javan virtuaalikone (JVM, engl. Java Virtual Machine) tarjoaa javan tavukoodille järjestelmästä riippumattoman suoritusympäristön [1]. Virtuaalikone käsittelee luokkatiedostoja, jotka ovat binäärimuotoisia esityksia java-kielen määrittelemistä luokista tai rajapinnoista. Luokkatiedosto sisältää myös luokan määrittelemien metodien toteutukset tavukoodimuodossa. Virtuaalikoneen käynnistyttyä se pyytää luokkalataajaa etsimään ja lataamaan halutut luokkatiedostot. Lataamisen yhteydessä virtuaalikone alustaa luokan tavukoodin suorituksessa tarvittavat tietorakenteet, muuttaa symboliset viittaukset varsinaisiksi vittauksiksi sekä asettaa muuttujille alkuarvot. Lataamisen jälkeen luokan tavukoodi on valmis suorittamista varten. 1.1 Virtuaalikoneen rakenne Virtuaalikoneen keskeisimmät tehtävät tavukoodin suorituksen kannalta ovat ohjelmalaskurin (engl. program counter) päivittäminen, muistinhallinta sekä suorituspinojen hallinta. Virtuaalikone käyttää suorituspinoa parametrien välitykseen sekä metodikutsujen ja poikkeusten toteuttamiseen. Virtuaalikoneessa kaikille säikeille yhteisiä muistialueita ovat metodialue ja keko (kuva 1). Metodialueella pidetään tietoa mm. ladattujen luokkien metodeista, kentistä sekä vakioista. Keko on tarkoitettu ajonaikaiselle muistinvaraamiselle ja mahdollistaa säikeiden välisen kommunikaation. Muistin vapauttaminen on virtuaalikoneen vastuulla. 1.2 Tietotyypit Virtuaalikone tukee sekä viite-, että alkeistyyppejä. Alkeistyyppejä ovat numeeriset tyypit, totuusarvot sekä paluuosoitetyyppi. Viitteet ovat osoittimia luokkatiedostojen perusteellä luotuihin olioihin. Olioita käsitellään aina viitetyypin avulla. Numeerisia tyyppejä on kokonais-, ja reaalilukuja varten. Kokonaislukutyyppejä ovat byte 8-bittinen etumerkillinen kokonaisluku ( 2 7, 2 7 1) short 16-bittinen etumerkillinen kokokonaisluku ( 2 15, 2 15 1) int 32-bittinen etumerkillinen kokonaisluku ( 2 31, 2 31 1) long 64-bittinen etumerkillinen kokonaisluku ( 2 63, 2 63 ) char 16-bittinen etumerkitön kokonaisluku Unicode-kirjasimia varten. Reaalilukutyyppejä ovat 1
Kuva 1: Virtuaalikoneen rakenne. float 32-bittinen IEEE-standardin mukainen liukulukuarvo double 64-bittinen IEEE-standardin mukainen liukulukuarvo. Totuusarvotyyppi boolean voi saada arvon true tai false. Paluuosoitetyypin arvot ovat osoittimia tavukoodin käskyihin eikä niitä voi muokata ajonaikana. Virtuaalikone ei suorita ajonaikaisia tyyppitarkistuksia. Tyyppitieto ilmaistaan eksplisiittisesti tavukoodin käskyissä. 1.3 Suorituspino ja kehykset Virtuaalikoneen jokainen syntyvä säie ylläpitää omaa suorituspinoa ja ohjelmalaskuria. Suorituspino säilyttää tietoa kunkin säikeen metodikutsuista ja ohjelmalaskuri osoittaa kulloinkin suoritettavaan tavukoodikäskyyn. Suorituspinoon talletetaan kehyksiä. Metodikutsun kehys (engl. frame) sisältää tiedot paikallisten muuttujien arvoista, parametreista sekä tavukoodikäskyjen välituloksista (kuva 2). Kehys luodaan aina metodia kutsuttaessa, jolloin se myös talletetaan suorituspinoon. Kun metodin suoritus päättyy, metodin luoma kehys poistetaan suorituspinosta. Virtuaalikone käyttää kehyksen paikallisia muuttujia parametrien välitykseen. Paikallisia muuttujia voidaan käsitellä metodiin liittyvän tavukoodin avulla. Jokaisella kehyksellä on nk. operandipino. Operandipino toimii laskennan välitulosten säilytyspaikkana ja sitä käsitellään tavukoodin käskyillä. Operandipinoon 2
Kuva 2: Kehyksen rakenne. voidaan tallettaa kaikkia virtuaalikoneen tukemia tietotyyppejä mutta pinon jäseniä täytyy käsitellä jäsenten tyyppiä vastaavilla tavukoodikäskyillä. Virtuaalikone olettaa, että tyyppeihiin liittyvät tarkistukset on suoritettu jo luokan lataamisen yhteydessä. 3
2 Luokkatiedoston rakenne Javan luokkatiedosto koostuu virrasta kahdeksanbittisiä tavuja. Jokainen 16, 32 tai 64 bittiä pitkä suure koostuu vastaavasti kahdesta, neljästä tai kahdeksasta peräkkäisestä tavusta. Merkitsevä tavu esiintyy virrassa ensimmäisenä. Alla on esitelty luokkatiedoston rakenne C-kielestä tutun tietuerakenteen mukaisessa muodossa. ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } Luokkatiedosto alkaa neljä tavua pitkällä tunnisteella magic, jonka arvo hexadesimaalilukuna on 0xCAFEBABE. Seuraavat neljä tavua muodostavat luokkatiedostoformaatin versionumeron. Luokan määrittelemät kentät on kuvattu taulukossa field info ja metodit taulukossa method info. Luokan määrittelemät vakiot sekä koodi sijaitsevat constant pool-nimiseessä taulukkossa. 4
3 Tavukoodi Tavukoodi muodostaa käskykannan jonka avulla virtuaalikonetta ohjataan. Jokainen tavukoodin käsky koostuu yhdestä tavun mittaisesta käskykoodista sekä käskyyn liittyvistä operandeista. Suurin osa käskyistä määrittelee eksplisiittisesti tiedon operandien tyypistä. Samasta käskystä on siten olemassa useita versioita erityyppisille operandeille. Käskykoodin muistikas (engl. mnemonic) sisältää tyyppitiedon etuliitteenä olevan kirjaimen muodossa. Etuliite on tyypin mukaan joko i (int), l (long), s (short), b (byte), c (char), f (float) tai d (double). Viitetyyppejä käsittelevien käskyjen muistikkaat alkavat kirjaimella a. Jotkut käskyt ovat yksiselitteisiä operandiensa tyypin suhteen, kuten esim. arraylength, joka palauttaa taulukko-tyyppisen operandinsa pituuden. Kaikki käskyt eivät käsittele tyypitettyä tietoa, kuten esim. ehdoton hyppykäsky goto. Koska jokainen käskykoodi on tavun pituinen, tavukoodin käskykannan koko on rajoitettu 256 käskyyn. Tiukasta rajoitteesta johtuen jokaisesta käskystä ei ole olemassa versiota jokaista operandityyppiä varten. Kiertotienä tavukoodi tarjoaa omat käskyt tyyppimuunnoksia varten. Esim. byte-tyyppisen arvon lataamiseen ei ole suoraa käskyä. Arvo ladataan int-tyyppisenä ja muunnetaan byte-tyyppiseksi. Suurimmalle osalle käskyjä ei ole olemassa erityismuotoa byte-, char- tai short-tyyppisille operandeille. Tämäntyyppisiä arvoja käsitellään käskyjä suoritettaessa int-tyyppisinä. Sekä byte- että short-tyyppiset operandit kasvatetaan int tyypin kokoiseksi säilyttäen niiden etumerkillinen arvo (sign extension). Etumerkittömät char-tyyppiset arvot täytetään nollilla int-tyypin mukaiseen kokoon (zero padding). 3.1 Käskyt Tavukoodi tarjoaa käskyt operandien lataukseen ja tallentamiseen, aritmeettisille operaatioille, tyyppikonversiolle, olioiden ja luokkien käsittelyyn, kontrollivuon hallintaan sekä pinon välittömään manipulointiin. 3.1.1 Lataus- ja tallennuskäskyt Jokaista tyyppiä varten, lukuunottamatta byte, short sekä char, on olemassa oma käsky, joka lukee arvon paikallisesta muuttujasta ja laittaa sen operandipinoon: iload, lload, fload ja dload. Käskyt saavat operandina indeksin paikallismuuttujataulukkoon. Vastaavasti istore, lstore, fstore sekä dstore tallentavat operandipinossa olevan arvon paikallismuuttujataulukkooon. Paikallismuuttujien arvojen lisäksi pinoon voidaan kirjoittaa myös vakioita. Esim. käsky ldc 18 lukee vakion aktiivisen luokan vakiovarannon (engl. constant pool) indeksistä 18 ja laittaa arvon pinoon. 5
Lataus- ja tallennuskäskyistä on olemassa myös Tload n/tstore n muotoiset versiot, missä n on arvo väliltä [0..3]. Kyseiset käskyt käsittelevät paikallista muuttujaa, jonka indeksi on annettu n, esim istore 1 ottaa int-tyyppisen arvon pinosta ja tallentaa sen paikallismuuttujataulukkon kohtaan 1. 3.1.2 Aritmeettiset käskyt Tavukoodi määrittelee seuraavat aritmeettiset käskyt int-, long-, float- ja doubletyyppisille operandeille: Yhteenlasku (Tadd, missä T on korvattu tyyppiä vastaavalla etuliitteellä), vähennyslasku (Tsub), kertolasku (Tmul), jakolasku (Tdiv), jakojäännös (Trem) sekä negaatio (Tneg). Viimeistä käskyä lukuunottamatta, kaikki edellä mainitut operaatiot käyttävät pinon kahta päällimmäistä arvoa operandeina ja asettavat tuloksen takaisin pinoon. Negaatio käyttää vain päällimäistä operandia. Kokonaislukutyyppejä (int ja long) varten on olemassa seuraavat aritmeettiset käskyt: oikealle (Tshr ja Tushr) ja vasemmalle (Tshl ja Tushl) siirto, bittioperaattorit AND (Tand), OR (Tor) sekä poissulkeva OR (Txor). Käskyllä iinc voidaan kasvattaa int-tyyppisen paikallismuuttujan arvoa yhdellä. int a = 1 + 2; Kahden int-tyyppisen vakion yhteenlasku ja sijoitus int-tyyppiseen muuttujaan näyttää yllä olevasta javakoodista tavukoodiksi käännettynä seuraavalta: 0 iload_1 // Laittaa vakion 1 pinoon 1 iload_2 // Laittaa vakion 2 pinoon 2 iadd // Ottaa pinosta kaksi arvoa, laskee ne yhteen // ja laittaa tuloksen takaisin pinoon 3 istore 1 // Ottaa arvon pinosta ja tallentaa sen paikalliseen // muuttujaan indeksissä 1 3.1.3 Tyyppikonversiot Tavukoodissa tyyppimuunnokset ovat joko implisiittisiä tai eksplisiittisiä. Virtuaalikone suorittaa implisiittisen muunnoksen aina, jos laskutoimituksen operandina on joko byte-, short- tai char-tyypinen arvo. Kuten aikaisemmin jo mainittiin, nämä muunnetaan int-tyyppisiksi ennen käskyn suoritusta. Eksplisiittinen muunnos suoritetaan käskyillä, joiden muoto on S2T, missä S on lähtötyyppiä vastaava kirjain ja T on kohdetyyppiä vastaava kirjain. Esim. d2i muuntaa double-tyyppisen arvon int-tyyppiseksi ja vastaavasti i2d muuntaa inttyyppisen arvon double-tyyppiseksi. 6
3.1.4 Olioiden luonti ja manipulointi Olio luodaan käskyllä new. Operandina käskylle annetaan kaksitavuinen indeksi luokan vakiovaranteeseen, jonka pitää osoittaa luotavan olion tyyppiin. Taulukko voidaan luoda käskyllä newarray. Operandina käskylle annetaan taulukon alkioiden tyyppiä vastaava koodi. Pinon huipulla pitää olla alkioden lukumäärää kuvaava arvo. Luokan kenttiä voidaan lukea käskyllä getstatic ja kirjoittaa putstatic. Olion kenttiä voidaan lukea käskyllä getfield ja kirjoittaa käskyllä putfield. Taulukon alkioita voidaan lukea operandipinon huipulle Taload käskyperheellä. Käskystä on olemassa oma muoto jokaiselle tyypille. Vastaavasti voidaan siirtää pinon huipulla oleva arvo taulukon alkioksi käskyperheellä Tastore. Taulukon pituus voidaan tarkistaa käskyllä arraylength. Taulukkojen ja olioiden ominaisuuksia voidaan tarkistaa käskyillä instanceof sekä checkcast. 3.1.5 Metodien kutsuminen Luokan metodeja kutsutaan käskyllä invokestatic. Olion metodeja kutsutaan käskyllä invokevirtual. Myöhäissidonnan avulla määriteltyä rajapinnan metodia kutsutaan käskyllä invokeinterface. Muut metodit, kuten konstruktorit, kutsutaan käskyllä invokespecial. Jokainen edellä mainituista käskyistä ottaa pinosta kutsuttavan metodin parametrit käänteisessä järjestyksessä. Muut kuin invokestatic ottavat lisäksi viitteen olioon pinosta, jonka metodia ollaan kutsumassa. Käskyjen operandiksi annetaan kaksitavuinen indeksi luokan vakiovaranteeseen joka osoittaa kutsuttavan metodin nimeen. Metodin paluuarvo määritellään Treturn käskyn avulla. Mahdollisia tyyppejä ovat int, long, float, double sekä olioviite. Jos paluuarvo on byte-, short- tai char-tyyppinen niin se palautetaan int-tyyppisenä. int add12and13() { return addtwo(12, 13); } Yllä javakoodina esitelty metodi add12and13(), joka kutsuu metodia addtwo(int, int) kääntyy seuravanmuotoiseksi tavukoodiksi: Method int add12and13() 0 aload_0 // Laittaa viitteen paikallisesta muuttujasta // indeksissä 0 (this) pinoon 1 bipush 12 // Laittaa vakion 12 pinoon 3 bipush 13 // Laittaa vakion 13 pinoon 7
5 invokevirtual #4 // Kutsuu metodi, joka on kuvattu vakiovaranteen // indeksissä 4. 8 ireturn // Palauttaa kutsutun metodin paluuarvon pinon // huipulta. 3.1.6 Operandipinon suora käsittely Tavukoodin käskyjen avulla voidaan käsitellä operandipinoa myös suoraan. Pinon päällimmäinen sana voidaan ottaa pinosta (pop), kahdentaa (dup) ja vaihtaa toiseksi päällimmäisen sanan kanssa (swap). 3.1.7 Kontrollivuon hallintaa Tavukoodi tarjoaa useita käskyjä sekä ehdolliseen, että ehdottomaan haarautumiseen. Ehdollista haarautumista varten on käytettävissä tyyppikohtaiset käskyt kahden int-, long-, float- ja double-tyyppisen operandin vertailuun. int-tyyppisiä operandeja varten on vielä monia muita vertaus- ja haarautumiskäskyä. Lisäksi yhdistetty ehdollinen haarautuminen on mahdollista tableswitch sekä lookupswitch käskyjen avulla. Ehdoton haarautuminen on mahdollista goto, jsr sekä ret käskyjen avulla. void whileint() { int i = 0; } while (i < 100) { i++; } Yllä esitelty metodi whileint() kasvattaa paikallisen muuttujan arvo whilesilmukassa nollasta sataan. Tavukoodiksi käännettynä metodi näyttää seuraavalta: Method void whileint() 0 iconst_0 \\ Laittaa vakion 0 pinoon 1 istore_1 \\ Ottaa arvon pinosta ja tallentaa sen \\ paikalliseen muuttujaan 2 goto 8 \\ Hyppää osoitteeseen 8 5 iinc 1 1 \\ Kasvattaa paikallista muuttujaa yhdellä 8 iload_1 \\ Laittaa paikallisen muuttujan arvon pinoon. 9 bipush 100 \\ Laittaa vakion 100 pinoon 11 if_icmplt 5 \\ Hyppää osoitteeseen 5, jos pinon kaksi \\ päällimmäistä arvoa ovat samat. 14 return \\ Palaa kutsujalle 8
3.1.8 Poikkeuksien heitto ja käsittely Poikkeus heitetään käskyllä athrow. Pinon huipulla pitää olla viite olioon, jota voidaan käsitellä Throwable-tyyppisenä. Poikkeuksen käsittelylohkon alku on merkitty omaan hyppytaulukkoon. 3.1.9 Synkronointi Koodilohkoja voidaan suojata monitorilla java-kielen synchronized-avainsanan tapaan. Lohko aloitetaan käskyllä monitorenter ja lopetetaan käskyllä monitorexit. Kummatkin käskyt edellyttävät että pinon huippu on viite olioon, jonka monitori halutaan ottaa haltuun tai vastaavasti viite olioon jonka monitorista halutaan luopua. 3.1.10 Erikoiskäskyt Kolme käskykoodia on varattu virtuaalikoneen sisäiseen käyttöön. Kaksi näistä, impdep1 (koodi 245) sekä impdep2 (koodi 255), ovat vapaita virtuaalikoneen toteuttajan käytettäväksi. Kolmas käsky on breakpoint (koodi 202), jonka on tarkoitettu käytettäväksi debug-ohjelmistojen kanssa. Näitä käskyjä ei saa esiintyä class-tiedostossa. Viitteet [1] Lindholm, T, Yellin, F, The Java Virtual Machine Spesification, 2nd edition, Sun Microsystems Inc. 9