Obsah
1. Programovací jazyk Java
2. Běhové prostředí Javy a bytekód Javy
3. Instrukce JVM
4. Mikroprocesor picoJava
5. Mikroprocesor microJava
6. Obsah dalšího pokračování
1. Programovací jazyk Java
Programovací jazyk Java patří do rodiny objektově orientovaných jazyků, je relativně jednoduchý (zejména v porovnání s velmi komplikovaným C++), přenositelný, nezávislý na architektuře a v posledních verzích i velmi výkonný – podle některých studií je dokonce program spouštěný v Javě rychlejší než kód přeložený z C++. Java se na první pohled syntakticky podobá programovacímu jazyku C++, ve skutečnosti však má blíže k takovým vývojovým prostředkům, jakými jsou například Smalltalk a programovací jazyk Self.
Java je také od většiny kompilovaných jazyků odlišná tím, že se překlad programu neprovádí přímo do strojového kódu použitého procesoru, ale do speciálně navrženého zásobníkového kódu nazývaného bytekód. Tento bytekód je možné interpretovat na jakémkoli systému, který obsahuje virtuální procesor jazyka Java, takzvaný JVM. Výhodou tohoto řešení je přenositelnost a paradoxně také možnost provádět další optimalizace nad vygenerovaným bytekódem či nad jeho dynamicky detekovanou částí (hot spot).
V první řadě si však musíme uvědomit, že slovem Java a logem Java není (a podle stále platné licence ani nesmí být) označena určitá implementace jazyka ani určitý programový balík – to evidentně nedochází například RMS, který se snaží mimo jiné všude prosadit řešení bez Javy. V případě Javy se jedná, zjednodušeně řečeno, o tři základní specifikace. Specifikace nazvaná Java Language Specification definuje vlastní programovací jazyk, specifikace nazvaná Java 2 Platform API Specification základní třídy, které jsou programátorovi aplikací i samotným aplikacím dostupné, a konečně specifikace Java Virtual Machine Specification určuje parametry prostředí pro běh aplikací (tj. ve skutečnosti virtuální stroj pro bytekód jazyka Java). Vhodnou kombinací a následnou implementací těchto tří specifikací dostáváme do rukou kompletní (i když neinteraktivní) vývojové prostředí pro Java aplikace, takzvané Java Development Kits (JDKs), které obsahují zejména vlastní překladač (compiler), virtuální stroj jazyka Java a základní třídy (core classes) potřebné pro běh aplikací.
2. Běhové prostředí Javy a bytekód Javy
V tomto článku nás bude nejvíce zajímat běhové prostředí, které je určeno pro provozování aplikací napsaných v programovacím jazyce Java. Jak již víme, jedná se o takzvaný JVM (Java Virtual Machine) – virtuální stroj programovacího jazyka a celé platformy Java, který ve své podstatě implementuje idealizovaný zásobníkový procesor a chráněnou paměť, v níž jsou programy napsané v Javě spouštěny. Parametry virtuálního zásobníkového procesoru jsou velmi precizně popsány ve volně dostupné specifikaci Java Virtual Machine Specification, z níž si v dalších odstavcích uvedeme základní informace. Pro bezchybný běh vytvářené aplikace na různých platformách musí být zaručeno, že se JVM bude chovat ve všech případech podle platné specifikace.
Přeložené třídy jsou uloženy v souborech s koncovkou .class. V těchto souborech jsou uložena jak metadata o třídě, tak i vlastní bytekód. JVM musí soubory .class správně rozkódovat a bytekód buď přeložit do výsledného strojového kódu použitého procesoru, nebo tento bytekód interpretovat. V současné době se překlad a interpretace kombinují, což je z hlediska velkých (a dlouhodobě běžících) aplikací pravděpodobně nejlepší řešení, protože se při překladu vybraných částí kódu (hot spots) mohou využít i velmi sofistikované optimalizace.
Abstraktní zásobníkový procesor předpokládá, že se veškeré výpočty budou provádět na zásobníku operandů. Parametry a lokální proměnné je však možné adresovat přímo, což znamená, že jejich počet je omezen (to však při kvalitním softwarovém návrhu nevadí). Výpočty se provádí vždy nad primitivním datovým typem int, long, float nebo double. Hodnoty ostatních primitivních datových typů musí být před provedením operace na jeden z výše uvedených typů převedeny. Navíc existují specializované instrukce, které na zásobník operandů ukládají vybrané konstanty (null, -1, 0, 1), což zkracuje výsledný bytekód a současně i urychluje jeho interpretaci. Podle specifikace JVM by se měly kontrolovat typy hodnot uložených na zásobníku, čímž se alespoň částečně zamezí spuštění modifikovaného kódu.
3. Instrukce JVM
Všechny instrukce z instrukčního kódu JVM (tj. abstraktního zásobníkového procesoru) mají délku jeden byte a jejich formát je vertikální, na rozdíl od instrukcí zásobníkových procesorů popsaných v předchozích částech tohoto seriálu ([16], [17] a [18]). To znamená, že počet instrukcí je omezen na 256, v současné době je však využito pouze cca 200 instrukčních kódů. Některé instrukce existují i v takzvané rychlé variantě, při které nejsou požadovány některé časově náročnější kontroly. Výhodou takto konstruovaného bytekódu je jeho stručnost, .class soubory (s uloženými informacemi pro ladění) jsou v průměru dokonce menší než původní zdrojový kód. V následujícím přehledu jsou stručně vypsány všechny základní instrukce (bez rychlých variant), které musí každá JVM akceptovat. V prvním sloupci je v desítkové soustavě zapsán jednobytový kód instrukce, ve druhém sloupci její jméno (takto se instrukce zobrazuje v debuggeru) a třetí sloupec obsahuje přepis do pseudoassembleru, případně poznámku.
000 nop nop
001 aconst_null push null
002 iconst_m1 push -1
003 iconst_0 push 0
004 iconst_1 push 1
005 iconst_2 push 2
006 iconst_3 push 3
007 iconst_4 push 4
008 iconst_5 push 5
009 lconst_0 push 0L
010 lconst_1 push 1L
011 fconst_0 push 0f
012 fconst_1 push 1f
013 fconst_2 push 2f
014 dconst_0 push 0d
015 dconst_1 push 1d
016 bipush push const8 (jako 32bit integer, tj. provádí se konverze)
017 sipush push const16 (jako 32bit integer, tj. provádí se konverze)
018 ldc1 push index32[i8]
019 ldc2 push index32[i16]
020 ldc2w push index64[i16]
021 iload push int var[index8]
022 lload push long var[index8]
023 fload push float var[index8]
024 dload push double var[index8]
025 aload push handle var[index8]
026 iload_0 push int var[0]
027 iload_1 push int var[1]
028 iload_2 push int var[2]
029 iload_3 push int var[3]
030 lload_0 push long var[0]
031 lload_1 push long var[1]
032 lload_2 push long var[2]
033 lload_3 push long var[3]
034 fload_0 push float var[0]
035 fload_1 push float var[1]
036 fload_2 push float var[2]
037 fload_3 push float var[3]
038 dload_0 push double var[0]
039 dload_1 push double var[1]
040 dload_2 push double var[2]
041 dload_3 push double var[3]
042 aload_0 push handle var[0]
043 aload_1 push handle var[1]
044 aload_2 push handle var[2]
045 aload_3 push handle var[3]
046 iaload push int array32[index32] (array32{handle} a index32 jsou na zásobníku)
047 laload push long array32[index32]
048 faload push float array32[index32]
049 daload push double array32[index32]
050 aaload push handle array32[index32]
051 baload push byte array32[index32]
052 caload push char array32[index32]
053 saload push signed array32[index32]
054 istore pop int var[index8]
055 lstore pop long var[index8]
056 fstore pop float var[index8]
057 dstore pop double var[index8]
058 astore pop handle var[index8]
059 istore_0 pop int var[0]
060 istore_1 pop int var[1]
061 istore_2 pop int var[2]
062 istore_3 pop int var[3]
063 lstore_0 pop long var[0]
064 lstore_1 pop long var[1]
065 lstore_2 pop long var[2]
066 lstore_3 pop long var[3]
067 fstore_0 pop float var[0]
068 fstore_1 pop float var[1]
069 fstore_2 pop float var[2]
070 fstore_3 pop float var[3]
071 dstore_0 pop double var[0]
072 dstore_1 pop double var[1]
073 dstore_2 pop double var[2]
074 dstore_3 pop double var[3]
075 astore_0 pop handle var[0]
076 astore_1 pop handle var[1]
077 astore_2 pop handle var[2]
078 astore_3 pop handle var[3]
079 iastore pop int array32[index32]
080 lastore pop long array32[index32]
081 fastore pop float array32[index32]
082 dastore pop double array32[index32]
083 aastore pop handle array32[index32]
084 bastore pop byte array32[index32]
085 castore pop char array32[index32]
086 sastore pop short array32[index32]
087 pop pop int (výsledek se ztratí)
088 pop2 pop long (výsledek se ztratí)
089 dup duplikuje int na vrcholu zásobníku
090 dup_x1 duplikuje int a vrací ho od 3. pozice zásobníku
091 dup_x2 duplikuje int a vrací ho od 4. pozice zásobníku
092 dup2 duplikuje long na stacku
093 dup2_x1 duplikuje long a vrací ho od 3. pozice zásobníku
094 dup2_x2 duplikuje long a vrací ho od 4. pozice zásobníku
095 swap swapuje dvě položky typu int
096 iadd add int (operace +)
097 ladd add long (operace +)
098 fadd add float (operace +)
099 dadd add double (operace +)
100 isub sub int (operace -)
101 lsub sub long (operace -)
102 fsub sub float (operace -)
103 dsub sub double (operace -)
104 imul mul int (operace *)
105 lmul mul long (operace *)
106 fmul mul float (operace *)
107 dmul mul double (operace *)
108 idiv div int (operace /)
109 ldiv div long (operace /)
110 fdiv div float (operace /)
111 ddiv div double (operace /)
112 imod mod int (operace %)
113 lmod mod long (operace %)
114 fmod mod float (operace %)
115 dmod mod double (operace %)
116 ineg neg int (aritmetická negace)
117 lneg neg long (aritmetická negace)
118 fneg neg float (aritmetická negace)
119 dneg neg double (aritmetická negace)
120 ishl shl int (aritmetický posun doleva)
121 lshl shl long (aritmetický posun doleva)
122 ishr shr int (aritmeticky posun doprava)
123 lshr shr long (aritmeticky posun doprava)
124 iushr shr int (bitový posun doleva)
125 lushr shr long (bitový posun doleva)
126 iand and int (bitová operace &)
127 land and long (bitová operace &)
128 ior or int (bitová operace |)
129 lor or long (bitová operace |)
130 ixor xor int (bitová operace ^)
131 lxor xor long (bitová operace ^)
132 iinc inkrementuje lokální proměnnou o konstantu
133 i2l převod int na long
134 i2f převod int na float
135 i2d převod int na double
136 l2i převod long na int
137 l2f převod long na float
138 l2d převod long na double
139 f2i převod float na int
140 f2l převod float na long
141 f2d převod float na double
142 d2i převod double na int
143 d2l převod double na long
144 d2f převod double na float
145 int2byte převod int na byte
146 int2char převod int na char
147 int2short převod int na short
148 lcmp pop a; pop b;push sign(a-b)
149 fcmpl -//-, vrací -1 pri NaN
150 fcmpg -//-, vrací +1 pri NaN
151 dcmpl -//-, vrací -1 pri NaN
152 dcmpg -//-, vrací +1 pri NaN
153 ifeq porovnání s nulou a skok při rovnosti
154 ifne porovnání s nulou a skok při nerovnosti
155 iflt porovnání s nulou a skok při relaci <
156 ifge porovnání s nulou a skok při relaci >=
157 ifgt porovnání s nulou a skok při relaci >
158 ifle porovnání s nulou a skok při relaci <=
159 if_icmpeq porovnání dvou operandů a skok při rovnosti
160 if_icmpne porovnání dvou operandů a skok při nerovnosti
161 if_icmplt porovnání dvou operandů a skok při relaci <
162 if_icmpge porovnání dvou operandů a skok při relaci >=
163 if_icmpgt porovnání dvou operandů a skok při relaci >
164 if_icmple porovnání dvou operandů a skok při relaci <=
165 if_acmpeq porovnání dvou polí a skok při rovnosti
166 if_acmpne porovnání dvou polí a skok při nerovnosti
167 goto nepodmíněný skok
168 jsr skok do podprogramu
169 ret návrat z podprogramu
170 tableswitch rozeskok dle tabulky
171 lookupswitch -//- ale existuje default hodnota
172 ireturn vrací int a následně provede ret
173 lreturn vrací long a následně provede ret
174 freturn vrací float a následně provede ret
175 dreturn vrací double a následně provede ret
176 areturn vrací handle a následně provede ret
177 return návrat z podprogramu
178 getstatic získání statické položky třídy
179 putstatic nastavení statické položky třídy
180 getfield získání třídní položky
181 putfield nastavení třídní položky
182 invokevirtual zavolání virtuální metody (přes tabulku)
183 invokenonvirtual zavolání nevirtuální metody (přímo)
184 invokestatic zavolání statické metody
185 invokeinterface zavolání metody specifikované v rozhraní
186 newfromname vytvoření nového objektu
187 new vytvoření nového objektu
188 newarray vytvoření pole o zadané velikosti
189 anewarray př. new p[7] se přeloží jako : bipush 7; anewarray <Class "java.lang.p">
190 arraylength vrací délku pole specifikovaného pomocí handle
191 athrow throw exception - vyvolání výjimky
192 checkcast testování typu objektu
193 instanceof získání instance objektu
194 monitorenter enter monitored code - použito u multithreadingu
195 monitorexit exit monitored code - použito u multithreadingu
196 verifystack dotaz, zda není zásobník prázdný
197 breakpoint zavolání obsluhy breakpointu
198 multianewarray vytvoří multidimenzionální pole
4. Mikroprocesor picoJava
Vývoj mikroprocesoru picoJava začal v roce 1996 ve firmě Sun Microsystems. Tento mikroprocesor byl určen především pro vestavěné systémy, například pro chytré telefony, mikrovlnné trouby, internetové aplikace, TV set boxy a v neposlední řadě i pro takzvaný Network computer, což je ve své podstatě chytrý grafický terminál bez periferních zařízení, který umí lokálně zpracovat bytekód určený pro JVM. Z tohoto důvodu obsahuje picoJava takový instrukční soubor, který do značné míry odpovídá JVM, samozřejmě je však možné použít i aplikace napsané v jiných programovacích jazycích, například v C či C++. Podle dostupných měření je nativně prováděný kód cca 20× rychlejší než jeho interpretovaná varianta běžící na stejném mikroprocesoru.
Návrh tohoto mikroprocesoru (resp. jeho výkonného jádra, které je možné licencovat) byl vytvořen v jazyku Verilog Register Transfer Language (RTL), přičemž celý zdrojový kód měl délku cca 2,4 MB. Interně je na mikroprocesoru vytvořen zásobník, který je sestaven z 64 registrů šířky 32 bitů, které dohromady tvoří kruhový buffer. To znamená, že po uložení 65 hodnot na zásobník se začíná přepisovat první uložená položka – tento princip práce se zásobníkem je sice velmi jednoduchý, na druhou stranu však může způsobit chyby při běhu aplikací.
V jednom taktu je možné provést dvě čtení ze zásobníku a jeden zápis. Vzhledem k tomu, že u firmy Sun přesně nepochopili princip zásobníkových procesorů (například horizontální formát instrukcí), tvoří v reálných aplikacích průměrně celou jednu třetinu instrukcí instrukce pro práci se zásobníkem. Z tohoto důvodu bylo nutné implementovat takzvané skládání instrukcí (instruction folding), které je použito zejména při operacích pro ukládání dat na zásobník – push.
Tento mikroprocesor také částečně podporuje garbage collector. Jádro obsahuje jednotku pro provádění operací v pohyblivé řádové čárce (tu lze pro některé méně náročné aplikace odebrat), dále frontu instrukcí, vyrovnávací paměť pro data i pro instrukce, jednotku pro manipulaci se zásobníkem operandů a částečně i podporu výjimek. Některé složitější instrukce se provádějí pomocí přerušení – v případě, že se narazí na neznámý kód instrukce, provede se přerušení, ve kterém se tento kód odsimuluje.
5. Mikroprocesor microJava
Mikroprocesor microJava byl firmou Sun Microsystems představen 15. 10. 1997. Jednalo se o typ 701 z takzvané série 700. Tento mikroprocesor je taktéž určen především pro provádění javovského bytekódu, lze však provozovat i aplikace napsané pomocí jiného programovacího jazyka. Mikroprocesor je založen na jádru picoJava (viz předchozí kapitolu) a obsahuje ALU pro výpočty nad celými čísly, ALU pro výpočty nad hodnotami uloženými v systému plovoucí řádové tečky, tři čítače a časovače, řadič přerušení, šestnáctipinový universální port GPIO a v neposlední řadě i řadič paměti a sběrnice přímo na čipu – toto řešení podporuje paměti typu EDO DRAM, SDRAM, SRAM, ROM a Flash. Řadič sběrnice odpovídá normě PCI 2.1 a pracuje na nejnižší povolené frekvenci 33 MHz. Kromě toho je pomocí I/O řadiče podporována lokální sběrnice, která může mít bitovou šířku 8, 16 nebo 32 bitů.
Po hardwarové stránce je tento mikroprocesor postavený na 0,25mikronové technologii CMOS. Pracovní frekvence dosahuje u tohoto základního modelu 200 MHz, napájecí napětí jádra je na dnešní dobu poměrně vysoké: 2,5 V, spotřeba je však velmi nízká – pouze 4 watty. Pro napájení lokální sběrnice a PCI sběrnice je vyžadováno ještě přídavné napájecí napětí 3,3 V. Procesor obsahuje pouzdro typu PBGA (Ball Grid Array) s 316 piny. Další modely této série jsou však baleny do odlišných pouzder tak, aby odpovídaly požadavkům aplikací.
6. Obsah dalšího pokračování
V závěrečné části tohoto seriálu si shrneme přednosti a zápory programovacího jazyka Forth, ukážeme si možnosti použití Forthu v současnosti a uvedeme si i odkazy na zajímavé internetovské stránky o tomto tématu.