Obsah
1. Pohled pod kapotu JVM – porovnání bajtkódu virtuálního stroje Javy s Lua a Pythonem
2. Historie vývoje bajtkódu a jeho využití
3. Zásobníkové vs. registrové virtuální stroje
4. Praktická ukázka rozdílu mezi bajtkódem určeným pro zásobníkový a registrový VM
5. Bajtkód virtuálního stroje jazyka Java (JVM)
6. Příklady metod přeložených do bajtkódu JVM
7. Bajtkód využívaný jazykem Lua
8. Příklady funkcí přeložených do bajtkódu jazyka Lua
9. Bajtkód využívaný jazykem Python
10. Příklady funkcí přeložených do bajtkódu Pythonu
11. Repositář se zdrojovými kódy všech tří dnešních demonstračních příkladů
1. Pohled pod kapotu JVM – porovnání bajtkódu virtuálního stroje Javy s Lua a Pythonem
V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se, prozatím ovšem bez větších podrobností, pokusíme o porovnání základních vlastností bajtkódu JVM s dalšími dnes často používanými bajtkódy, zejména s bajtkódy využívanými virtuálními stroji jazyků Lua a Python. Toto porovnání sice může vypadat poněkud odtažitě od dennodenní programátorské praxe, ve skutečnosti se ovšem ukazuje, že zvolený bajtkód (formát instrukcí, podporované datové typy atd.) daného virtuálního stroje do značné míry určuje a někdy taktéž omezuje vlastnosti programovacích jazyků, jejichž překladače jsou nad daným bajtkódem postaveny. To platí ve velké míře taktéž pro programovací jazyk Java – týká se to jak komplikací při implementaci nových sémantických prvků do Javy (lambda výrazy a obecně funkce jako plnohodnotný datový typ), tak i takových zdánlivých „maličkostí“, jako je neuzavřenost aritmetických operací nad celočíselnými datovými typy byte a short.
Nejprve se však ve stručnosti seznámíme s historií používání bajtkódu, která je poměrně dlouhá a přitom i z programátorského hlediska zajímavá, protože úspěch mnoha programovacích jazyků vycházel mj. i z toho, že se díky použití bajtkódů (a s nimi souvisejících virtuálních strojů) mohly jejich překladače snadno a relativně rychle rozšiřovat na různé platformy a architektury počítačů (a to se v žádném případě netýká pouze Javy, ale dříve například i Pascalu či BPCL, což je pradávný předchůdce rodiny jazyků C a C++).
Bajtkód je využíván především dvěma nástroji. Na jedné straně se jedná o překladač (například v případě Javy to může být překladač javac, API tohoto překladače dostupné v moderních JDK či interní překladač využívaný nějakým integrovaným vývojovým prostředím), který překládá zdrojový kód napsaný programátorem (či poloautomaticky vygenerovaný v IDE :-) do bajtkódu; což mj. znamená, že je překlad zcela nezávislý na použité platformě. Na straně druhé již jednou přeložený bajtkód využívá interpret či dnes již v mnoha případech spíše JIT (just in time) překladač. Ve skutečnosti však s bajtkódem může pracovat i mnoho dalších nástrojů. Příkladem ve světě Javy a bajtkódu JVM mohou být například nástroje typu Cobertura a EMMA sloužící pro zjištění, které části zdrojového kódu aplikací jsou pokryty (jednotkovými) testy. Tyto nástroje musí umět dobře kooperovat s virtuálním strojem Javy, proto nejprve modifikují bajtkódy testovaných tříd, aby bylo možné při běhu testů dynamicky zjistit, které řádky kódu jsou skutečně z testů volány.
Podobným způsobem je bajtkód modifikován nástroji podporujícími aspektově orientované programování (aspect oriented programming), které taktéž mohou zasahovat do bajtkódu vygenerovaného překladačem. Teoreticky je sice možné při použití aspektově orientovaného programování transformovat přímo zdrojové kódy programů (což je použito především v jiných programovacích jazycích), ale transformace bajtkódu je v případě Javy mnohem snazší. Ze stejného důvodu (analýza bajtkódu je jednodušší než analýza zdrojového textu) je bajtkód použit pro statickou analýzu a hledání potenciálních chyb nástrojem FindBugs.
2. Historie vývoje bajtkódu a jeho využití
Myšlenka na využití bajtkódu pro překlad a následnou interpretaci programů je v informatice úspěšně využívána poměrně dlouho. Jedním z prvních známých a rozšířených bajtkódů byl OPCODE, který byl vyvinut pro potřeby programovacího jazyka BCPL. Jazyk BPCL (Basic Combined Programming Language), jenž byl vytvořen Martinem Richardsem již v roce 1966, je z hlediska historie IT důležitý především proto, že z něho Ken Thompson odvodil nový programovací jazyk nazvaný jednoduše B a z jazyka B se postupně vyvinulo klasické Céčko (nejdříve K&R C a posléze dodnes používané ANSI/ISO C). Bajtkód nazvaný OPCODE byl založen na instrukcích pracujících se zásobníkem operandů a v práci s daty dostupnými přes indexové registry. Například výraz x / y + z se pro proměnné x, y a z uložené na pozicích 1, 2 a 10 přeložil do bajtkódu OPCODE následujícím způsobem, který se nápadně podobá bajtkódu JVM (což ostatně není náhoda, jak si řekneme dále):
LP 1 LP 2 DIV LP 10 PLUS
Popularita používání bajtkódu byla úzce spojena i s úspěšností takzvaného p-code, což byl (a ostatně stále ještě je) bajtkód využívaný některými překladači programovacího jazyka Pascal (to se ovšem netýká Turbo Pascalu). Díky využití p-code bylo snadnější portovat jak překladač Pascalu na různé platformy, tak i interpret vlastního p-kódu. Tento bajtkód je v současnosti ještě využíván na některých školách při výuce IT; mj. i z tohoto důvodu se jedná o stále živý projekt, o čemž ostatně mohou svědčit i stránky projektu UCSD p-system Virtual Machine dostupné na adrese http://ucsd-psystem-vm.sourceforge.net/. Dále je vhodné zmínit bajtkód využívaný Smalltalkem. Jedná se o poměrně vysokoúrovňový bajtkód, který se v některých ohledech přibližuje bajtkódu používaném Pythonem (posílání zpráv je v tomto případě velmi podobné volání virtuálních metod apod.). Mimochodem – velká část bajtkódu Smalltalku je reflexivně dostupná i z vlastního jazyka, viz například http://marianopeck.files.wordpress.com/2011/05/screen-shot-2011–05–21-at-6–51–24-pm.png.
3. Zásobníkové vs. registrové virtuální stroje
Využívání bajtkódů má v současnosti za sebou zhruba čtyřicet let postupného vývoje, takže není divu, že za tuto poměrně dlouhou dobu bylo navrženo a implementováno mnoho různých řešení virtuálního stroje+bajtkódu, která se od sebe v mnoha ohledech odlišovala, a to jak úrovní abstrakce bajtkódu (nízkoúrovňové instrukce dobře transformovatelné na strojový kód vs. vysokoúrovňové instrukce podporující například polymorfismus využívaný například virtuálním strojem Pythonu), tak i způsobem, jakým jednotlivé instrukce pracovaly s argumenty. Naprostou většinu existujících a v současnosti používaných bajtkódů lze rozdělit do dvou skupin. V první skupině se nachází bajtkódy zpracovávané virtuálními stroji založenými na zásobníku operandů (operand stack) a v druhé skupině se nachází bajtkódy využívající sadu registrů (register set) nabízených virtuálním strojem. Oba přístupy mají své přednosti i zápory a taktéž skalní zastánce i odpůrce, jak je tomu ostatně v IT dobrým zvykem :-)
Bajtkódy a virtuální stroje využívající zásobník operandů a instrukce pro práci s argumenty uloženými na tomto zásobníku většinou obsahují mnoho bezparametrických instrukcí, jejichž operační kódy tak mohou být velmi krátké a typicky bývají uloženy v jednom bajtu (z toho také ostatně označení „bajtkód“ vychází). Interpretace takového bajtkódu bývá velmi jednoduchá a lze ji efektivně provádět i na těch mikroprocesorech, které obsahují velmi malé množství pracovních registrů, z čehož ostatně vyplývá i oblíbenost takto navržených bajtkódů v době osmibitových mikroprocesorů a mikrořadičů (poněkud speciálním případem je jazyk Forth). Před přibližně deseti lety, kdy se ve větší míře začaly rozšiřovat JIT překladače, se předpokládalo, že nové JIT překladače budou mít problémy s překladem instrukcí založených na použití zásobníku operandů do strojového kódu moderních mikroprocesorů (ty mají většinou velkou sadu pracovních registrů), ovšem ukázalo se, že JIT dokáží bez větších problémů pracovat jak se zásobníkovými instrukcemi, tak i s instrukcemi využívajícími sadu pracovních registrů VM.
Tím se pomalu dostáváme ke druhému rozšířenému typu bajtkódů. Jedná se o bajtkódy, jejichž instrukce dokážou pracovat s obsahem množiny pracovních registrů zvoleného virtuálního stroje. Délka instrukčního slova i možnosti takto navržených bajtkódů závisí především na počtu těchto pracovních registrů; v moderních VM se setkáme minimálně s použitím šestnácti či 32 registry, což znamená, že mnoho instrukcí má délku minimálně dva bajty, mnohdy i tři či čtyři bajty. Liší se taktéž počet operandů instrukcí – některé bajtkódy využívají takzvaný dvouadresový kód (používají dva registry – jeden registr zdrojový a druhý registr současně zdrojový i cílový), jiné se zaměřují na tříadresový kód (dva zdrojové registry a jeden registr cílový). Způsob interpretace takto navržených bajtkódů může být problematičtější v případě, že mikroprocesor, na němž interpret běží, obsahuje menší množství fyzických pracovních registrů, ovšem (jak již bylo řečeno v předchozím odstavci), při použití JIT se rozdíly mezi oběma způsoby práce s operandy do značné míry rozostřují.
4. Praktická ukázka rozdílu mezi bajtkódem určeným pro zásobníkový a registrový VM
Podívejme se nyní na dvě ukázky, jak se může lišit bajtkód založený na zásobníkovém virtuálním stroji od bajtkódu, který je určen pro registrový virtuální stroj. V obou příkladech se má vyhodnotit jednoduchý výraz result = a+b*c-d; pro jednoduchost předpokládejme, že všech pět proměnných je lokálních a současně mají typ celé číslo (integer). Způsob překladu do bajtkódu využívajícího zásobník operandů (konkrétně je použit bajtkód JVM) může vypadat následovně:
; 0 je index proměnné a ; 1 je index proměnné b ; 2 je index proměnné c ; 3 je index proměnné d ; 4 je index proměnné result 0: iload 0 ; uložení obsahu proměnné a na zásobník 1: iload 1 ; uložení obsahu proměnné b na zásobník 2: iload 2 ; uložení obsahu proměnné c na zásobník 3: imul ; provedení operace b*c, výsledek je ponechán na zásobníku 4: iadd ; provedení operace a+(b*c) 5: iload 3 ; uložení obsahu proměnné d na zásobník 6: isub ; dokončit příkaz a+(b*c)+d 7: istore 4 ; uložení výsledku z TOS (obsah zásobníku operandů) do proměnné result
Příklad kompilace téhož příkazu result = a+b*c-d do bajtkódu využívajícího pracovní registry, konkrétně do bajtkódu využívaného programovacím jazykem Lua:
; 0 je index proměnné a ; 1 je index proměnné b ; 2 je index proměnné c ; 3 je index proměnné d ; 4 je index proměnné result 1 [101] MUL 4 1 2 ; přímé vynásobení obsahu proměnných b a c 2 [101] ADD 4 0 4 ; přičíst k obsahu proměnné a mezivýsledek, výsledek uložit do proměnné result 3 [101] SUB 4 4 3 ; odečíst od mezivýsledku obsah proměnné b, výsledek uložit do proměnné result
Ve druhém případě se nemusely vůbec použít instrukce pro uložení proměnných na zásobník ani pro načtení hodnoty zpět ze zásobníku operandů do proměnné, ovšem na druhou stranu musely mít všechny aritmetické instrukce v instrukčním slovu uloženy i indexy operandů.
5. Bajtkód virtuálního stroje jazyka Java (JVM)
První typ v současnosti používaného bajtkódu, s nímž se ve stručnosti seznámíme, je bajtkód využívaný JVM. Virtuální stroj jazyka Java samozřejmě využívá jiný soubor „strojových“ instrukcí, než fyzický mikroprocesor, na němž jsou Javovské programy spouštěny. Dokonce ani mikroprocesory určené pro přímé spouštění bajtkódu – dnes již zpola zapomenuté projekty MicroJava a PicoJava, dokonce i ARM procesory s technologií Jazelle – nedokázaly nativně vykonávat všechny instrukce bajtkódu. V prvních několika letech existence Javy byly instrukce bajtkódu (tvořící těla jednotlivých metod) v naprosté většině případů pouze interpretovány, a to mnohdy velmi jednoduchým způsobem: v programové smyčce se postupně načítaly kódy jednotlivých instrukcí a následně se pro každou instrukci zavolala nativní funkce, která danou instrukci vykonala, většinou s parametry uloženými v zásobníkovém rámci nebo v zásobníku operandů (bližší popis bude uveden v následujících kapitolách).
Naprogramování naivního interpretru pro bajtkód virtuálního stroje Javy není příliš složité, protože samotná instrukční sada je poměrně jednoduchá. Moderní interpretry, například již v tomto seriálu popsaný JamVM, jsou navíc naprogramovány takovým způsobem, aby byl jejich přenos na další procesorové architektury snadný a přitom se bajtkód interpretoval co nejefektivnějším způsobem, ideálně s ručně optimalizovanou vnitřní smyčkou (naprogramovanou v případě ručně prováděných optimalizací většinou assembleru).
Ovšem i přes veškerou snahu programátorů interpretrů nemůže běh programů napsaných v Javě a spouštěných v JVM s interpretovaným bajtkódem v rychlosti soutěžit s nativními aplikacemi. Právě z tohoto důvodu vznikly takzvané just-in-time (JIT) překladače, které dokážou přeložit buď jen určitou sekvenci instrukcí JVM nebo i celý bajtkód do nativního strojového kódu, který je následně spuštěn. Některé JIT překladače, například stále populární HotSpot původně vyvinutý společností Sun Microsystems, používají adaptivní překlad, v němž je bajtkód analyzován v době běhu, takže má JIT překladač k dispozici mnohem více informací, než při statickém překladu. HotSpot navíc umožňuje pomocí takzvaný sond (probes sledovat, který kód je překládán a jaký typ překladu je přitom použit, to je však již poměrně pokročilá a nepříliš často používaná technologie. Ovšem nezávisle na tom, zda jsou instrukce JVM „pouze“ interpretovány, nebo je nejdříve použit JIT, musí být vždy dodrženy všechny vlastnosti jazyka Java i JVM. JIT například může odstranit kontrolu mezí při indexování polí, ale jen tehdy, když je zřejmé, že nedojde k překročení těchto mezí.
V této kapitole si ve stručnosti připomeneme základní vlastnosti bajtkódu virtuálního stroje Javy. Struktura i vlastnosti JVM jsou přesně popsány ve specifikaci JVM, včetně přesného formátu všech datových typů (znaků, celých čísel, čísel s plovoucí řádovou tečkou). Díky dodržování této specifikace různými výrobci virtuálního stroje Javy je zaručeno, že programy napsané v Javě jsou přenositelné na počítače s mnohdy velmi rozdílnou architekturou: od smartphonů s jednojádrovými mikroprocesory až po superpočítače s velkým množstvím výpočetních uzlů a mnohdy s několika desítkami tisíc procesorových jader. Různé nekompatibility, s nimiž programátoři bojovali především v minulosti (ale samozřejmě i dnes, i když v mnohem menší míře), bývají způsobeny buď chybami v implementaci některého virtuálního stroje (a nedodržení specifikace lze považovat za chybu) či nekompatibilitami ve standardních knihovnách, popř. chybami v nativních knihovnách, které lze z JVM volat.
Specifikace virtuálního stroje Javy popisuje některé jeho části velmi precizně (například se jedná o již zmíněné datové typy či o formát instrukcí nebo o strukturu zásobníkového rámce), ovšem u některých dalších částí jsou popsány jen základní vlastnosti a zůstává pouze na tvůrci konkrétní JVM, jakým způsobem se bude daná část virtuálního stroje ve skutečnosti implementovat. Asi nejtypičtějším příkladem je specifikace haldy (heap), u níž se sice předpokládá využití automatické správy paměti, ale nikde již není řečeno, jaký konkrétní algoritmus správy paměti se má použít (v současnosti se využívá hned několik algoritmů) ani jaký formát má být použit pro ukládání referencí na objekty. Díky tomu bylo možné Javu jednoduše přenést z původní 32bitové architektury jak na 16bitové procesory, tak i na procesory pracující s 64bitovými ukazateli (u nichž lze navíc v případě potřeby využít i komprimované ukazatele na objekty) – to vše bez nutnosti zásahu do zdrojových kódů Javovských programů i bez nutnosti jejich rekompilace.
Virtuální stroj jazyka Java obsahuje instrukce, které pracují s operandy několika datových typů. Na rozdíl od mnoha fyzických procesorů se v případě JVM provádí kontroly, zda jsou operace skutečně aplikovány na správné operandy. Není například možné, aby se operace součtu prováděla s jedním operandem typu int a druhým operandem typu long – takový bajtkód by byl při svém načítání odmítnut a vůbec by nebyl spuštěn (to však jinými slovy znamená, že bajtkód je zbytečně redundantní, zejména v porovnání s bajtkódy jazyků Lua a Python). Zajímavé je, že jen velmi málo instrukcí JVM podporuje práci s datovými typy boolean, byte, short a char. Proměnné a parametry metod těchto typů musí být například před provedením některé aritmetické operace nejprve převedeny na typ int pomocí konverzních instrukcí (těch existuje celkem patnáct).
Většina instrukcí virtuálního stroje Javy pracuje s operandy uloženými na takzvaném zásobníku operandů (operand stack). Zásobník operandů (v tomto případě se již jedná o skutečný zásobník typu LIFO – Last In, First Out) je vytvářen v čase běhu aplikace pro každou zavolanou metodu, což mj. znamená, že je při spuštění metody vždy prázdný (zásobník operandů je podle specifikace součástí zásobníkového rámce, jeho konkrétní umístění však je libovolné). Již v čase překladu zdrojového kódu je pro každou metodu zjištěno, jak velká oblast paměti má být pro zásobník operandů vyhrazena a samozřejmě je prováděna kontrola, zda se v době běhu aplikace tato velikost nepřekročí (to by se nemělo u validního bajtkódu stát).
Virtuální stroj Javy kontroluje typy operandů uložených na zásobník operandů a zajišťuje, že se nad těmito operandy budou provádět pouze typově bezpečné operace. V praxi to například znamená, že není možné na zásobník uložit dvě hodnoty typu float a následně provést instrukci iadd, protože tato instrukce vyžaduje, aby na zásobníku byly uloženy dvě hodnoty typu int (i když float i int mají shodnou bitovou šířku).
6. Příklady metod přeložených do bajtkódu JVM
Základní vlastnosti bajtkódu virtuálního stroje Javy si ukážeme na způsobu překladu testovací třídy obsahující několik statických metod. Tím, že jsou všechny testované metody statické, je zajištěno, že se jim nebude předávat další (ve zdrojovém kódu skrytý) parametr this, jiný významnější rozdíl mezi statickými a nestatickými metodami v bajtkódu nenajdeme. První dvě metody nop1() a nop2() jsou bezparametrické, mají prázdné tělo a nevrací žádnou hodnotu, takže lze předpokládat, že jejich bajtkód bude velmi jednoduchý. Další metoda, která se jmenuje answer(), je taktéž bezparametrická, ale vrací konstantní celočíselnou hodnotu 42. Následuje přetížená metoda add(), jejíž jednotlivé varianty akceptují různé celočíselné i reálné hodnoty; poslední varianta této metody spojuje řetězce (a to stejným operátorem +). Na příkladu metod add() si povšimněte toho, že operace + není pro datové typy byte a short uzavřena (výsledkem je hodnota typu int), což vychází z vlastností bajtkódu. Ostatně ani další aritmetické operace pro tyto datové typy taktéž nejsou uzavřeny.
Následuje další testovací metoda pojmenovaná isNegative() obsahující podmíněný příkaz. Ten by zde ve skutečnosti nemusel být uveden, neboť celé tělo metody lze zkrátit do jediného výrazu, jehož výsledek by byl vracen příkazem return, ovšem my potřebujeme zjistit, jakým způsobem se do bajtkódu JVM překládají konstrukce typu if-then-else. Ve zdrojovém kódu metody fibonacciIter() jsou použity jak podmínky, tak i počítaná programová smyčka typu for a v poslední testované metodě fibonacciRecursive() je použita rekurze, tj. metoda přímo volá samu sebe. Podívejme se nyní na celý zdrojový kód testovací třídy, který je mimochodem dostupný na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/b91d2ad8f871/bytecode/Java/Test1.java:
/** * Trida s nekolika jednoduchymi statickymi metodami * pro otestovani zakladnich vlastnosti bajtkodu JVM. */ public class Test { /** * Prazdna metoda bez parametru. */ public static void nop1() { } /** * Taktez prazdna metoda bez parametru. */ public static void nop2() { return; } /** * Metoda bez parametru vracejici konstantu. */ public static int answer() { return 42; } /** * Soucet dvou celych cisel typu byte. */ public static byte add(byte x, byte y) { return (byte)(x+y); } /** * Soucet dvou celych cisel typu short. */ public static short add(short x, short y) { return (short)(x+y); } /** * Soucet dvou celych cisel typu int. */ public static int add(int x, int y) { return x+y; } /** * Soucet dvou celych cisel typu long. */ public static long add(long x, long y) { return x+y; } /** * Soucet dvou celych cisel typu float. */ public static float add(float x, float y) { return x+y; } /** * Soucet dvou celych cisel typu double. */ public static double add(double x, double y) { return x+y; } /** * Spojeni dvou retezcu. */ public static String add(String x, String y) { return x+y; } /** * Metoda s podminkou. */ public static boolean isNegative(int x) { if (x < 0) { return true; } return false; } /** * Metoda s podminkou a se smyckou. */ public static long fibonacciIter(int n) { if (n <= 1) { return n; } long result = 0; long n1 = 0; long n2 = 1; for(n--; n > 0; n--) { result = n1 + n2; n1 = n2; n2 = result; } return result; } /** * Metoda s rekurzi. */ public static long fibonacciRecursive(int n) { if (n <= 1) { return n; } else { return fibonacciRecursive(n-1) + fibonacciRecursive(n-2); } } /** * Vse je nutne otestovat. */ public static void main(String[] args) { nop1(); nop2(); System.out.println(answer()); System.out.println(add((byte)1, (byte)2)); System.out.println(add((short)1, (short)2)); System.out.println(add(1, 2)); System.out.println(add(1L, 2L)); System.out.println(add(1f, 2f)); System.out.println(add(1., 2.)); System.out.println(add("Hello ", "world!")); System.out.println(isNegative(-10)); for (int n=0; n <= 10; n++) { System.out.println(n + "\t" + fibonacciIter(n) + "\t" + fibonacciRecursive(n)); } } }
Bajtkód přeložených metod je vypsán pod tímto odstavcem.
V metodě nop1() je použita pouze jediná instrukce return pro návrat z těla metody:
public static void nop1(); Code: 0: return ; pouze návrat z metody
Totéž platí pro metodu nop2():
public static void nop2(); Code: 0: return ; pouze návrat z metody
V metodě answer() je s využitím instrukce ireturn vrácena hodnota uložená na vrcholu zásobníku operandů (TOS):
public static int answer(); Code: 0: bipush 42 ; uložit na zásobník konstantu 42 2: ireturn ; výskok z metody s předáním návratové hodnoty
Metoda add() využívá pro typ byte automatický převod hodnotu argumentu či lokální proměnné na typ int, ovšem výsledek se musí zpětně konvertovat:
public static byte add(byte, byte); Code: 0: iload_0 ; uložit první parametr na zásobník (+ provést konverzi na int) 1: iload_1 ; uložit druhý parametr na zásobník (+ provést konverzi na int) 2: iadd ; provést součet 3: i2b ; zpětná konverze na byte 4: ireturn ; výskok z metody s předáním návratové hodnoty
Totéž platí v případě použití celočíselného datového typu short:
public static short add(short, short); Code: 0: iload_0 ; uložit první parametr na zásobník (+ provést konverzi na int) 1: iload_1 ; uložit druhý parametr na zásobník (+ provést konverzi na int) 2: iadd ; provést součet 3: i2s ; zpětná konverze na short 4: ireturn ; výskok z metody s předáním návratové hodnoty
Součet dvou čísel typu int s využitím zásobníku operandů je podporován přímo instrukcí bajtkódu:
public static int add(int, int); Code: 0: iload_0 ; uložit první parametr na zásobník 1: iload_1 ; uložit druhý parametr na zásobník 2: iadd ; provést součet 3: ireturn ; výskok z metody s předáním návratové hodnoty
Součet dvou čísel typu long s využitím zásobníku operandů je taktéž podporován přímo instrukcí bajtkódu:
public static long add(long, long); Code: 0: lload_0 ; uložit první parametr na zásobník 1: lload_2 ; uložit druhý parametr na zásobník 2: ladd ; provést součet 3: lreturn ; výskok z metody s předáním návratové hodnoty
Totéž platí pro argumenty či proměnné typu float:
public static float add(float, float); Code: 0: fload_0 ; uložit první parametr na zásobník 1: fload_1 ; uložit druhý parametr na zásobník 2: fadd ; provést součet 3: freturn ; výskok z metody s předáním návratové hodnoty
Totéž platí pro argumenty či proměnné typu double:
public static double add(double, double); Code: 0: dload_0 ; uložit první parametr na zásobník 1: dload_2 2: dadd ; provést součet 3: dreturn ; výskok z metody s předáním návratové hodnoty
Spojení dvou řetězců je naproti tomu realizováno přes instanci třídy StringBuilder a metody append(). Výsledek je nutné převést na řetězec metodou toString():
public static java.lang.String add(java.lang.String, java.lang.String); Code: ; tato lokální proměnná se použije při spojování řetězců 0: new #2; //class java/lang/StringBuilder 3: dup ; zavolat konstruktor třídy StringBuilder 4: invokespecial #3; //Method java/lang/StringBuilder."init":()V ; zavolat metodu append() třídy StringBuilder pro první parametr 7: aload_0 8: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; ; zavolat metodu append() třídy StringBuilder pro druhý parametr 11: aload_1 12: invokevirtual #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; ; zpětný převod na řetězec 15: invokevirtual #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 18: areturn ; výskok z metody s předáním návratové hodnoty
V metodě isNegative() je použita instrukce pro podmíněný skok ifge. Povšimněte si toho, že pravdivostní hodnoty jsou v bajtkódu reprezentovány celočíselnými konstantami 0 a 1:
public static boolean isNegative(int); Code: 0: iload_0 ; uložit parametr na zásobník 1: ifge 6 ; podmíněný skok na lokální adresu 6 při splnění podmínky 4: iconst_1 ; 1==true 5: ireturn ; výskok z metody s předáním návratové hodnoty true 6: iconst_0 ; 0==false 7: ireturn ; výskok z metody s předáním návratové hodnoty false
Nejsložitější je bajtkód metody fibonacciIter(), v níž je použito několika podmíněných skoků i jednoho skoku nepodmíněného:
public static long fibonacciIter(int); Code: 0: iload_0 ; uložit první parametr na zásobník 1: iconst_1 ; n se bude porovnávat s hodnotou 1 2: if_icmpgt 8; podmíněný skok na instrukci na indexu 8 5: iload_0 ; pro n menší nebo rovno 1 se vrátí přímo n 6: i2l ; konverze n na long 7: lreturn ; výskok z metody s předáním návratové hodnoty false 8: lconst_0 ; příprava pro zahájení počítané programové smyčky 9: lstore_1 10: lconst_0 11: lstore_3 12: lconst_1 13: lstore 5 15: iinc 0, -1 18: iload_0 ; zde začíná programová smyčka 19: ifle 39 ; test na ukončení programové smyčky 22: lload_3 23: lload 5 25: ladd 26: lstore_1 27: lload 5 29: lstore_3 30: lload_1 31: lstore 5 33: iinc 0, -1 36: goto 18 ; skok na začátek programové smyčky 39: lload_1 40: lreturn ; výskok z metody s předáním návratové hodnoty false
Bajtkód metody fibonacciRecursive() poměrně přesně odpovídá své zdrojové předloze:
public static long fibonacciRecursive(int); Code: 0: iload_0 ; uložit první parametr na zásobník 1: iconst_1 ; n se bude porovnávat s hodnotou 1 2: if_icmpgt 8; podmíněný skok na instrukci na indexu 8 5: iload_0 ; pro n menší nebo rovno 1 se vrátí přímo n 6: i2l ; konverze n na long 7: lreturn ; výskok z metody s předáním návratové hodnoty false 8: iload_0 9: iconst_1 10: isub ; provedení operace n-1 ; rekurzivní volání metody 11: invokestatic #6; //Method fibonacciRecursive:(I)J 14: iload_0 15: iconst_2 16: isub ; provedení operace n-2 ; rekurzivní volání metody 17: invokestatic #6; //Method fibonacciRecursive:(I)J 20: ladd ; sečíst výsledek (návratovou hodnotu) obou rekurzivně volaných metod 21: lreturn ; výskok z metody s předáním návratové hodnoty false
7. Bajtkód využívaný jazykem Lua
Dalším bajtkódem, s nímž se v dnešním článku alespoň ve stručnosti seznámíme, je bajtkód využívaný programovacím jazykem Lua. Bajtkód tohoto jazyka se v mnoha ohledech odlišuje od bajtkódu JVM. Pravděpodobně nejnápadnějším rozdílem mezi bajtkódem JVM a bajtkódem jazyka Lua je fakt, že se v Lua VM nepoužívá zásobník operandů, protože indexy operandů jsou přímo součástí instrukčního slova. I formát instrukčních kódů je od JVM velmi odlišný, protože zatímco v případě bajtkódu JVM je kód instrukce uložen v celém bajtu (s několika málo výjimkami), je u Lua VM kód instrukce uložen v pouhých šesti bitech, zatímco zbylých 26 bitů instrukčního slova je rezervováno pro uložení indexů operandů či konstant. Bytekód Lua VM taktéž obsahuje spíše vysokoúrovňové instrukce, které dobře reflektují vlastnosti tohoto programovacího jazyka. Existují například instrukce pro implementaci programové smyčky for, instrukce pro práci s (asociativními) poli tvořícími nejdůležitější strukturovaný datový typ jazyka Lua a dokonce se v bajtkódu nachází instrukce pro vytvoření uzávěru (closure) a pro tail call.
Instrukce mohou mít jeden z následujících formátů:
iABC
# | Označení | Délka bitového pole | Význam |
---|---|---|---|
1 | i | 6 | kód instrukce |
2 | A | 8 | index či hodnota prvního operandu |
3 | B | 9 | index či hodnota druhého operandu |
4 | C | 9 | index či hodnota třetího operandu |
iABx
# | Označení | Délka bitového pole | Význam |
---|---|---|---|
1 | i | 6 | kód instrukce |
2 | A | 8 | index či hodnota prvního operandu |
3 | Bx | 18 | index či hodnota druhého operandu |
iAsBx
# | Označení | Délka bitového pole | Význam |
---|---|---|---|
1 | i | 6 | kód instrukce |
2 | A | 8 | index či hodnota prvního operandu |
3 | sBx | 18 | index či hodnota druhého operandu (zde se znaménkem) |
iAx
# | Označení | Délka bitového pole | Význam |
---|---|---|---|
1 | i | 6 | kód instrukce |
2 | Ax | 26 | index či hodnota prvního (jediného) operandu |
Podrobnosti si vysvětlíme příště.
8. Příklady funkcí přeložených do bajtkódu jazyka Lua
Pro porovnání bajtkódů JVM a Lua VM vytvoříme sadu funkcí, které se do co největší míry podobají metodám použitým ve třídě Test1.java. Jedním z velkých rozdílů mezi jazyky Java a Lua je dynamická povaha datových typů v Lue, což například znamená, že se původní přetížená metoda add() musí implementovat funkcí add() pro čísla a funkcí addStr() pro řetězce:
-- -- Modul s nekolika jednoduchymi funkcemi -- pro otestovani zakladnich vlastnosti bajtkodu jazyka Lua -- -- -- Prazdna funkce bez parametru. -- function nop1() end -- -- Taktez prazdna funkce bez parametru. -- function nop2() return end -- -- Funkce bez parametru vracejici konstantu. -- function answer() return 42 end -- -- Soucet dvou cisel. -- function add(x, y) return x+y end -- -- Spojeni dvou retezcu. -- function addStr(x, y) return x..y end -- -- Funkce s podminkou. -- function isNegative(x) if x < 0 then return true end return false end -- -- Funkce s podminkou a se smyckou. -- function fibonacciIter(n) if n <= 1 then return n end local result = 0 local n1 = 0 local n2 = 1 for i = n-1, 1, -1 do result = n1 + n2 n1 = n2 n2 = result end return result end -- -- Funkce s rekurzi. -- function fibonacciRecursive(n) if n <= 1 then return n else return fibonacciRecursive(n-1) + fibonacciRecursive(n-2) end end -- -- Vse je nutne otestovat. -- function main() nop1() nop2() print(answer()) print(add(1, 2)) print(addStr("Hello ", "world!")) print(isNegative(-10)) for n = 0, 10 do print(n .. "\t" .. fibonacciIter(n) .. "\t" .. fibonacciRecursive(n)) end end main()
Podívejme se nyní na vygenerovaný bajtkód:
Funkce nop1() obsahuje pouze jedinou instrukci pro návrat z funkce. První a druhý parametr instrukce RETURN určuje, které registry budou použity pro návratové hodnoty (může jich být více):
function <Test.lua:9,10> (1 instruction at 0x90a7c88) 0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions 1 [10] RETURN 0 1
U funkce nop2() poněkud překvapivě narazíme na dvojici instrukcí RETURN, protože překladač provádí překlad bez optimalizací (což oceníme při ladění):
function <Test.lua:15,17> (2 instructions at 0x90a7de0) 0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions 1 [16] RETURN 0 1 2 [17] RETURN 0 1
Ve funkci answer() se vrací konstanta 42 a opět zde můžeme vidět automaticky vytvořenou koncovou instrukci RETURN (ve skutečnosti RETURN nil):
function <Test.lua:22,24> (3 instructions at 0x90a7f08) 0 params, 2 slots, 0 upvalues, 0 locals, 1 constant, 0 functions 1 [23] LOADK 0 -1 ; 42 2 [23] RETURN 0 2 3 [24] RETURN 0 1
Součet dvou parametrů předaných funkci add() je velmi jednoduchý – sečtou se hodnoty parametru číslo 0 s hodnotou parametru číslo 1, výsledek se uloží do registru 2, který je následně vrácen první instrukcí RETURN:
function <Test.lua:29,31> (3 instructions at 0x90a7d88) 2 params, 3 slots, 0 upvalues, 2 locals, 0 constants, 0 functions 1 [30] ADD 2 0 1 2 [30] RETURN 2 2 3 [31] RETURN 0 1
V bajtkódu nalezneme i instrukci pro konkatenaci řetězců:
function <Test.lua:36,38> (5 instructions at 0x90a8308) 2 params, 4 slots, 0 upvalues, 2 locals, 0 constants, 0 functions 1 [37] MOVE 2 0 2 [37] MOVE 3 1 3 [37] CONCAT 2 2 3 4 [37] RETURN 2 2 5 [38] RETURN 0 1
V bajtkódu funkce isNegative() si povšimněte způsobu načtení pravdivostních konstant do registru 1 i způsobu provedení podmíněného skoku:
function <Test.lua:43,48> (7 instructions at 0x90a8500) 1 param, 2 slots, 0 upvalues, 1 local, 1 constant, 0 functions 1 [44] LT 0 0 -1 ; - 0 2 [44] JMP 0 2 ; to 5 3 [45] LOADBOOL 1 1 0 4 [45] RETURN 1 2 5 [47] LOADBOOL 1 0 0 6 [47] RETURN 1 2 7 [48] RETURN 0 1
Bajtkód funkce fibonacciIter() je relativně krátký (v porovnání s JVM), protože se zde využívají speciální instrukce FORPREP a FORLOOP, jimiž je implementována programová smyčka typu for:
function <Test.lua:53,69> (16 instructions at 0x90a7fa0) 1 param, 8 slots, 0 upvalues, 8 locals, 3 constants, 0 functions 1 [54] LE 0 0 -1 ; - 1 2 [54] JMP 0 1 ; to 4 3 [55] RETURN 0 2 4 [58] LOADK 1 -2 ; 0 5 [59] LOADK 2 -2 ; 0 6 [60] LOADK 3 -1 ; 1 7 [62] SUB 4 0 -1 ; - 1 8 [62] LOADK 5 -1 ; 1 9 [62] LOADK 6 -3 ; -1 10 [62] FORPREP 4 3 ; to 14 11 [63] ADD 1 2 3 12 [64] MOVE 2 3 13 [65] MOVE 3 1 14 [62] FORLOOP 4 -4 ; to 11 15 [68] RETURN 1 2 16 [69] RETURN 0 1
Způsob rekurzivního volání funkce si vyžádá podrobnější vysvětlení, které bude uvedeno příště:
function <Test.lua:74,80> (13 instructions at 0x90a87d8) 1 param, 4 slots, 1 upvalue, 1 local, 3 constants, 0 functions 1 [75] LE 0 0 -1 ; - 1 2 [75] JMP 0 2 ; to 5 3 [76] RETURN 0 2 4 [76] JMP 0 8 ; to 13 5 [78] GETTABUP 1 0 -2 ; _ENV "fibonacciRecursive" 6 [78] SUB 2 0 -1 ; - 1 7 [78] CALL 1 2 2 8 [78] GETTABUP 2 0 -2 ; _ENV "fibonacciRecursive" 9 [78] SUB 3 0 -3 ; - 2 10 [78] CALL 2 2 2 11 [78] ADD 1 1 2 12 [78] RETURN 1 2 13 [80] RETURN 0 1
Bajtkód používaný Lua VM je (alespoň podle mého názoru) nejenom čitelnější, ale i kratší, a to mj. i kvůli existenci specializovaných instrukcí pro implementaci programové smyčky for i díky existenci aritmetických a logických instrukcí využívajících tříadresový kód (není potřeba složitě manipulovat s hodnotami ukládanými na zásobník operandů).
9. Bajtkód využívaný jazykem Python
Posledním v současnosti používaným bajtkódem, o němž se v dnešním článku zmíníme, je bajtkód využívaný programovacím jazykem Python, konkrétně jeho původní verzí CPython (kromě tohoto bajtkódu lze najít i další bajtkódy Pythonu určené pro jiné VM, například Mamba atd.). Již v předchozích kapitolách jsme si řekli, že bajtkód JVM je poměrně nízkoúrovňový, zejména v porovnání s bajtkódem používaným v programovacím jazyku Lua (resp. přesněji řečeno virtuálním strojem tohoto jazyka). Totéž platí, a to dokonce ještě ve větší míře, i pro bajtkód jazyka Python. Ten je opět založen na zásobníku operandů, ovšem mnohé instrukce pracující s jedním či dvěma operandy (samozřejmě uloženými na zásobníku) ve skutečnosti mohou volat metody objektů a nikoli pouze provádět operace nad primitivními datovými typy. Platí to především pro všechny „aritmetické“ operace, například i pro operátor +, který se překládá do instrukce BINARY_ADD.
To například znamená, že se jednoduchá funkce add() se dvěma operandy:
def add(x, y): return x+y
přeloží do následující čtveřice instrukcí bajtkódu:
add: 28 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 BINARY_ADD 7 RETURN_VALUE
Tuto funkci lze ovšem volat jak s číselnými parametry, tak i s řetězci, n-ticemi, seznamy či objekty s implementovanou metodou __add__, takže instrukce BINARY_ADD není zcela porovnatelná například s JVM instrukcemi iadd, ladd atd. operujícími pouze nad konkrétním primitivním datovým typem.:
print(add(1, 2)) print(add(1., 2)) print(add("Hello ", "world!")) print(add([1,2,3], [4,5,6])) print(add((1,2,3), (4,5,6)))
Kromě toho může bajtkód Pythonu obsahovat i instrukce pro snazší tvorbu smyček (BREAK_LOOP, CONTINUE_LOOP) i pro práci s kolekcemi (LIST_APPEND, MAP_ADD, BUILD_SLICE apod).
10. Příklady funkcí přeložených do bajtkódu Pythonu
Podobně jako tomu bylo v případě Javy i programovacího jazyka Lua, i pro Python byla vytvořena demonstrační aplikace s funkcemi, které se snaží co nejvíce přiblížit původním zdrojovým kódům v Javě a Lue (právě proto zde nejsou použity některé pro Python typické idiomy). Podívejme se nejdříve na zdrojový kód tohoto příkladu, který ve své závěrečné části obsahuje funkci pro disassembling bajtkódu:
# # Modul s nekolika jednoduchymi funkcemi # pro otestovani zakladnich vlastnosti bajtkodu jazyka Python # # # Prazdna funkce bez parametru. # def nop1(): pass # # Taktez prazdna funkce bez parametru. # def nop2(): return # # Funkce bez parametru vracejici konstantu. # def answer(): return 42 # # Soucet dvou cisel. # def add(x, y): return x+y # # Funkce s podminkou. # def isNegative(x): if x < 0: return True return False # # Funkce s podminkou a se smyckou. # def fibonacciIter(n): if n <= 1: return n result = 0 n1 = 0 n2 = 1 for i in xrange(n-1, 0, -1): result = n1 + n2 n1 = n2 n2 = result return result # # Funkce s rekurzi. # def fibonacciRecursive(n): if n <= 1: return n else: return fibonacciRecursive(n-1) + fibonacciRecursive(n-2) # # Vse je nutne otestovat. # def main(): nop1() nop2() print(answer()) print(add(1, 2)) print(add("Hello ", "world!")) print(isNegative(-10)) for n in xrange(0,11): print(str(n) + "\t" + str(fibonacciIter(n)) + "\t" + str(fibonacciRecursive(n))) #main() def disassemble(): from dis import dis print("\nnop1:") dis(nop1) print("\nnop2:") dis(nop2) print("\nanswer:") dis(answer) print("\nadd:") dis(add) print("\nisNegative:") dis(isNegative) print("\nfibonacciIter:") dis(fibonacciIter) print("\nfibonacciRecursive:") dis(fibonacciRecursive) disassemble()
Opět se podívejme, jak bude vypadat bajtkód vygenerovaný překladačem Pythonu, resp. přesněji řečeno CPythonu:
Na překladu funkce nop1() pravděpodobně nenajdeme nic překvapivého:
nop1: 10 0 LOAD_CONST 0 (None) 3 RETURN_VALUE
Stejným způsobem je přeložena i funkce nop2(), což je pochopitelné:
nop2: 16 0 LOAD_CONST 0 (None) 3 RETURN_VALUE
V bajtkódu funkce answer() se na zásobník nejdříve uloží číselná konstanta 42, která je následně instrukcí RETURN_VALUE vrácena volající funkci:
answer: 22 0 LOAD_CONST 1 (42) 3 RETURN_VALUE
Ve funkci add() se používá instrukce BINARY_ADD, která však ve skutečnosti může pracovat nejenom s čísly, ale i s řetězci, n-ticemi apod., což je velký rozdíl oproti bajtkódu JVM, což jsme ostatně mohli vidět v předchozích kapitolách:
add: 28 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 BINARY_ADD 7 RETURN_VALUE
V bajtkódu funkce isNegative() je zajímavá především kombinace instrukcí COMPARE_OP (s operandem <) a JUMP_IF_FALSE. Instrukce bajtkódu Pythonu evidentně nejsou pojmenovány s ohledem na jejich ruční zápis :-):
isNegative: 34 0 LOAD_FAST 0 (x) 3 LOAD_CONST 1 (0) 6 COMPARE_OP 0 (<) 9 JUMP_IF_FALSE 5 (to 17) 12 POP_TOP 35 13 LOAD_GLOBAL 0 (True) 16 RETURN_VALUE >> 17 POP_TOP 36 18 LOAD_GLOBAL 1 (False) 21 RETURN_VALUE
I v bajtkódu funkce fibonacciIter() nalezneme dvojici instrukcí COMPARE_OP s JUMP_IF_FALSE. Kromě toho si povšimněte instrukce FOR_ITER, která pro zadaný iterátor uložený na zásobníku vytvoří základ pro programovou smyčku for:
fibonacciIter: 42 0 LOAD_FAST 0 (n) 3 LOAD_CONST 1 (1) 6 COMPARE_OP 1 (<=) 9 JUMP_IF_FALSE 5 (to 17) 12 POP_TOP 43 13 LOAD_FAST 0 (n) 16 RETURN_VALUE >> 17 POP_TOP 45 18 LOAD_CONST 2 (0) 21 STORE_FAST 1 (result) 46 24 LOAD_CONST 2 (0) 27 STORE_FAST 2 (n1) 47 30 LOAD_CONST 1 (1) 33 STORE_FAST 3 (n2) 49 36 SETUP_LOOP 52 (to 91) 39 LOAD_GLOBAL 0 (xrange) 42 LOAD_FAST 0 (n) 45 LOAD_CONST 1 (1) 48 BINARY_SUBTRACT 49 LOAD_CONST 2 (0) 52 LOAD_CONST 3 (-1) 55 CALL_FUNCTION 3 58 GET_ITER >> 59 FOR_ITER 28 (to 90) 62 STORE_FAST 4 (i) 50 65 LOAD_FAST 2 (n1) 68 LOAD_FAST 3 (n2) 71 BINARY_ADD 72 STORE_FAST 1 (result) 51 75 LOAD_FAST 3 (n2) 78 STORE_FAST 2 (n1) 52 81 LOAD_FAST 1 (result) 84 STORE_FAST 3 (n2) 87 JUMP_ABSOLUTE 59 >> 90 POP_BLOCK 54 >> 91 LOAD_FAST 1 (result) 94 RETURN_VALUE
V bajtkódu funkce fibonacciRecursive() si povšimněte zejména instrukce pojmenované LOAD_GLOBAL, kterou je možné využít pro uložení hodnoty globálního symbolu na zásobník:
fibonacciRecursive: 60 0 LOAD_FAST 0 (n) 3 LOAD_CONST 1 (1) 6 COMPARE_OP 1 (<=) 9 JUMP_IF_FALSE 5 (to 17) 12 POP_TOP 61 13 LOAD_FAST 0 (n) 16 RETURN_VALUE >> 17 POP_TOP 63 18 LOAD_GLOBAL 0 (fibonacciRecursive) 21 LOAD_FAST 0 (n) 24 LOAD_CONST 1 (1) 27 BINARY_SUBTRACT 28 CALL_FUNCTION 1 31 LOAD_GLOBAL 0 (fibonacciRecursive) 34 LOAD_FAST 0 (n) 37 LOAD_CONST 2 (2) 40 BINARY_SUBTRACT 41 CALL_FUNCTION 1 44 BINARY_ADD 45 RETURN_VALUE 46 LOAD_CONST 0 (None) 49 RETURN_VALUE
Vzhledem ke značným rozdílům mezi bajtkódem Python VM a bajtkódem JVM se budeme podrobnějšími rozdíly zabývat v navazující části tohoto seriálu, kde se zmíníme i o možnosti překladu Pythonu či zdrojových kódů napsaných v jazyce Lua do bajtkódu JVM.
11. Repositář se zdrojovými kódy všech tří dnešních demonstračních příkladů
Všechny tři dnešní demonstrační příklady Test1.java, Test1.lua i Test1.py jsou uloženy do Mercurial repositáře umístěného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/:
12. Odkazy na Internetu
- Python Bytecode: Fun With Dis
http://akaptur.github.io/blog/2013/08/14/python-bytecode-fun-with-dis/ - Python's Innards: Hello, ceval.c!
http://tech.blog.aknin.name/category/my-projects/pythons-innards/ - Byterun
https://github.com/nedbat/byterun - Python Byte Code Instructions
http://document.ihg.uni-duisburg.de/Documentation/Python/lib/node56.html - Python Byte Code Instructions
https://docs.python.org/3.2/library/dis.html#python-bytecode-instructions - Programovací jazyk Lua
http://palmknihy.cz/web/kniha/programovaci-jazyk-lua-12651.htm - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Lua 5.2 sources – lopcodes.h
http://www.lua.org/source/5.2/lopcodes.h.html - Lua 5.2 sources – lopcodes.c
http://www.lua.org/source/5.2/lopcodes.c.html - dis – Python module
https://docs.python.org/2/library/dis.html - Comparison of Python virtual machines
http://polishlinux.org/apps/cli/comparison-of-python-virtual-machines/ - O-code
http://en.wikipedia.org/wiki/O-code_machine - BCPL
http://en.wikipedia.org/wiki/BCPL - The BCPL Cintcode System and Cintpos User Guide by Martin Richards
http://www.cl.cam.ac.uk/users/mr/bcplman.pdf - Bootstrapping the BCPL Compiler using INTCODE
http://www.gtoal.com/languages/bcpl/amiga/bcpl/booting.txt - p-code machine
http://en.wikipedia.org/wiki/P-code_machine - ucsd-psystem-vm 0.11 (a portable virtual machine for the UCSD p-System)
http://ucsd-psystem-vm.sourceforge.net/ - Introduction to Smalltalk bytecodes
http://marianopeck.wordpress.com/2011/05/21/introduction-to-smalltalk-bytecodes/ - Audio File Formats.
http://sox.sourceforge.net/AudioFormats-11.html - TestSounds.com: pure digital sounds to test your audio
http://www.testsounds.com/ - Test Tones (20hz – 20khz)
http://mdf1.tripod.com/test-tones.html - WAV (Wikipedia)
http://en.wikipedia.org/wiki/WAV - WAVE PCM soundfile format
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ - Audio Interchange File Format
http://en.wikipedia.org/wiki/Aiff - Musical Instrument Digital Interface,
http://en.wikipedia.org/wiki/Musical_Instrument_Digital_Interface - A MIDI Pedalboard Encode,
http://www.pykett.org.uk/a_midi_pedalboard_encoder.htm - MIDI Note Number, Frequency Table,
http://tonalsoft.com/pub/news/pitch-bend.aspx - Note names, MIDI numbers and frequencies,
http://www.phys.unsw.edu.au/jw/notes.html - The MIDI Specification,
http://www.gweep.net/~prefect/eng/reference/protocol/midispec.html - Essentials of the MIDI protocol,
http://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html - General MIDI,
http://en.wikipedia.org/wiki/General_MIDI - Obecné MIDI (General MIDI),
http://www-kiv.zcu.cz/~herout/html_sbo/midi/5.html - Custom Chips: Paula
http://www.amiga-hardware.com/showhardware.cgi?HARDID=1460 - Big Book of Amiga Hardware
http://www.amiga-resistance.info/bboahfaq/ - Amiga Hardware Database
http://amiga.resource.cx/ - ExoticA
http://www.exotica.org.uk/wiki/Main_Page - The absolute basics of Amiga audio
http://www.sufo.estates.co.uk/amiga/amimus.html - Wikipedia: Tracker
http://en.wikipedia.org/wiki/Tracker - Wikipedia: Trackers
http://en.wikipedia.org/wiki/Trackers - Ultimate Soundtracker
http://en.wikipedia.org/wiki/Ultimate_Soundtracker - Protracker
http://en.wikipedia.org/wiki/ProTracker - Impulse Tracker
http://en.wikipedia.org/wiki/Impulse_Tracker - Scream Tracker
http://en.wikipedia.org/wiki/ScreamTracker - MikMod for Java
http://jmikmod.berlios.de/ - List of audio trackers
http://en.wikipedia.org/wiki/List_of_audio_trackers - Wikipedia: Module File
http://en.wikipedia.org/wiki/Module_file - Wikipedia: Chiptune
http://en.wikipedia.org/wiki/Chiptune - SDL_mixer 2.0
http://www.libsdl.org/projects/SDL_mixer/ - SDLJava: package sdljava.ttf
http://sdljava.sourceforge.net/docs/api/sdljava/ttf/package-summary.html#package_description - SDLJava: class sdljava.ttf.SDLTTF
http://sdljava.sourceforge.net/docs/api/sdljava/ttf/SDLTTF.html - SDLJava: class sdljava.ttf.SDLTrueTypeFont
http://sdljava.sourceforge.net/docs/api/sdljava/ttf/SDLTrueTypeFont.html - SDL_ttf Documentation
http://www.libsdl.org/projects/SDL_ttf/docs/ - SDL_ttf 2.0 (není prozatím součástí SDLJava)
http://www.libsdl.org/projects/SDL_ttf/ - SDL_ttf doc
http://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_frame.html - SDL 1.2 Documentation: SDL_Surface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsurface.html - SDL 1.2 Documentation: SDL_PixelFormat
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlpixelformat.html - SDL 1.2 Documentation: SDL_LockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdllocksurface.html - SDL 1.2 Documentation: SDL_UnlockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlunlocksurface.html - SDL 1.2 Documentation: SDL_LoadBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlloadbmp.html - SDL 1.2 Documentation: SDL_SaveBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsavebmp.html - SDL 1.2 Documentation: SDL_BlitSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlblitsurface.html - SDL 1.2 Documentation: SDL_VideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlvideoinfo.html - SDL 1.2 Documentation: SDL_GetVideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlgetvideoinfo.html - glDrawArrays
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawArrays.xml - glDrawElements
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawElements.xml - glDrawArraysInstanced
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawArraysInstanced.xml - glDrawElementsInstanced
http://www.opengl.org/sdk/docs/man4/xhtml/glDrawElementsInstanced.xml - Root.cz: Seriál Grafická knihovna OpenGL
http://www.root.cz/serialy/graficka-knihovna-opengl/ - Root.cz: Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
http://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/ - Best Practices for Working with Vertex Data
https://developer.apple.com/library/ios/documentation/3ddrawing/conceptual/opengles_programmingguide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html - Class BufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/image/BufferStrategy.html - Class Graphics
http://docs.oracle.com/javase/1.5.0/docs/api/java/awt/Graphics.html - Double Buffering and Page Flipping
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html - BufferStrategy and BufferCapabilities
http://docs.oracle.com/javase/tutorial/extra/fullscreen/bufferstrategy.html - Java:Tutorials:Double Buffering
http://content.gpwiki.org/index.php/Java:Tutorials:Double_Buffering - Double buffer in standard Java AWT
http://www.codeproject.com/Articles/2136/Double-buffer-in-standard-Java-AWT - Java 2D: Hardware Accelerating – Part 1 – Volatile Images
http://www.javalobby.org/forums/thread.jspa?threadID=16840&tstart=0 - Java 2D: Hardware Accelerating – Part 2 – Buffer Strategies
http://www.javalobby.org/java/forums/t16867.html - How does paintComponent work?
http://stackoverflow.com/questions/15544549/how-does-paintcomponent-work - A Swing Architecture Overview
http://www.oracle.com/technetwork/java/architecture-142923.html - Class javax.swing.JComponent
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html - Class java.awt.Component
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html - Class java.awt.Component.BltBufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.BltBufferStrategy.html - Class java.awt.Component.FlipBufferStrategy
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.FlipBufferStrategy.html - Metoda java.awt.Component.isDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/java/awt/Component.html#isDoubleBuffered() - Metoda javax.swing.JComponent.isDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#isDoubleBuffered() - Metoda javax.swing.JComponent.setDoubleBuffered()
http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#setDoubleBuffered(boolean) - Javadoc – třída GraphicsDevice
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsDevice.html - Javadoc – třída GraphicsEnvironment
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsEnvironment.html - Javadoc – třída GraphicsConfiguration
http://docs.oracle.com/javase/7/docs/api/java/awt/GraphicsConfiguration.html - Javadoc – třída DisplayMode
http://docs.oracle.com/javase/7/docs/api/java/awt/DisplayMode.html - Lesson: Full-Screen Exclusive Mode API
http://docs.oracle.com/javase/tutorial/extra/fullscreen/ - Full-Screen Exclusive Mode
http://docs.oracle.com/javase/tutorial/extra/fullscreen/exclusivemode.html - Display Mode
http://docs.oracle.com/javase/tutorial/extra/fullscreen/displaymode.html - Using the Full-Screen Exclusive Mode API in Java
http://www.developer.com/java/other/article.php/3609776/Using-the-Full-Screen-Exclusive-Mode-API-in-Java.htm - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html