Hlavní navigace

Pohled pod kapotu JVM – porovnání bajtkódu virtuálního stroje Javy s Lua a Pythonem

20. 5. 2014
Doba čtení: 39 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy začneme porovnávat tři vzájemně rozdílné bajtkódy – bajtkód JVM, bajtkód využívaný virtuálním strojem jazyka Lua a konečně bajtkód, který je použit Pythonem (přesněji řečeno CPythonem).

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ů

12. Odkazy na Internetu

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 byteshort.

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 CoberturaEMMA 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, yz 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.wor­dpress.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 MicroJavaPicoJava, 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, shortchar. 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ž floatint 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()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 byteshort 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.or­g/people/ptisnovs/jvm-tools/file/b91d2ad8f871/by­tecode/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 FORPREPFORLOOP, 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_OPJUMP_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:

UX DAy - tip 2

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.luaTest1.py jsou uloženy do Mercurial repositáře umístěného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/:

12. Odkazy na Internetu

  1. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  2. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  3. Byterun
    https://github.com/nedbat/byterun
  4. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Pyt­hon/lib/node56.html
  5. Python Byte Code Instructions
    https://docs.python.org/3­.2/library/dis.html#python-bytecode-instructions
  6. Programovací jazyk Lua
    http://palmknihy.cz/web/kni­ha/programovaci-jazyk-lua-12651.htm
  7. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  8. Lua 5.2 sources – lopcodes.h
    http://www.lua.org/source/5­.2/lopcodes.h.html
  9. Lua 5.2 sources – lopcodes.c
    http://www.lua.org/source/5­.2/lopcodes.c.html
  10. dis – Python module
    https://docs.python.org/2/li­brary/dis.html
  11. Comparison of Python virtual machines
    http://polishlinux.org/ap­ps/cli/comparison-of-python-virtual-machines/
  12. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  13. BCPL
    http://en.wikipedia.org/wiki/BCPL
  14. The BCPL Cintcode System and Cintpos User Guide by Martin Richards
    http://www.cl.cam.ac.uk/u­sers/mr/bcplman.pdf
  15. Bootstrapping the BCPL Compiler using INTCODE
    http://www.gtoal.com/langu­ages/bcpl/amiga/bcpl/bootin­g.txt
  16. p-code machine
    http://en.wikipedia.org/wiki/P-code_machine
  17. ucsd-psystem-vm 0.11 (a portable virtual machine for the UCSD p-System)
    http://ucsd-psystem-vm.sourceforge.net/
  18. Introduction to Smalltalk bytecodes
    http://marianopeck.wordpres­s.com/2011/05/21/introduc­tion-to-smalltalk-bytecodes/
  19. Audio File Formats.
    http://sox.sourceforge.net/Au­dioFormats-11.html
  20. TestSounds.com: pure digital sounds to test your audio
    http://www.testsounds.com/
  21. Test Tones (20hz – 20khz)
    http://mdf1.tripod.com/test-tones.html
  22. WAV (Wikipedia)
    http://en.wikipedia.org/wiki/WAV
  23. WAVE PCM soundfile format
    https://ccrma.stanford.edu/cou­rses/422/projects/WaveFor­mat/
  24. Audio Interchange File Format
    http://en.wikipedia.org/wiki/Aiff
  25. Musical Instrument Digital Interface,
    http://en.wikipedia.org/wi­ki/Musical_Instrument_Digi­tal_Interface
  26. A MIDI Pedalboard Encode,
    http://www.pykett.org.uk/a_mi­di_pedalboard_encoder.htm
  27. MIDI Note Number, Frequency Table,
    http://tonalsoft.com/pub/news/pitch-bend.aspx
  28. Note names, MIDI numbers and frequencies,
    http://www.phys.unsw.edu.au­/jw/notes.html
  29. The MIDI Specification,
    http://www.gweep.net/~pre­fect/eng/reference/protocol/mi­dispec.html
  30. Essentials of the MIDI protocol,
    http://ccrma.stanford.edu/~cra­ig/articles/linuxmidi/mis­c/essenmidi.html
  31. General MIDI,
    http://en.wikipedia.org/wi­ki/General_MIDI
  32. Obecné MIDI (General MIDI),
    http://www-kiv.zcu.cz/~herout/html_sbo/mi­di/5.html
  33. Custom Chips: Paula
    http://www.amiga-hardware.com/showhardware­.cgi?HARDID=1460
  34. Big Book of Amiga Hardware
    http://www.amiga-resistance.info/bboahfaq/
  35. Amiga Hardware Database
    http://amiga.resource.cx/
  36. ExoticA
    http://www.exotica.org.uk/wi­ki/Main_Page
  37. The absolute basics of Amiga audio
    http://www.sufo.estates.co­.uk/amiga/amimus.html
  38. Wikipedia: Tracker
    http://en.wikipedia.org/wiki/Tracker
  39. Wikipedia: Trackers
    http://en.wikipedia.org/wiki/Trackers
  40. Ultimate Soundtracker
    http://en.wikipedia.org/wi­ki/Ultimate_Soundtracker
  41. Protracker
    http://en.wikipedia.org/wi­ki/ProTracker
  42. Impulse Tracker
    http://en.wikipedia.org/wi­ki/Impulse_Tracker
  43. Scream Tracker
    http://en.wikipedia.org/wi­ki/ScreamTracker
  44. MikMod for Java
    http://jmikmod.berlios.de/
  45. List of audio trackers
    http://en.wikipedia.org/wi­ki/List_of_audio_trackers
  46. Wikipedia: Module File
    http://en.wikipedia.org/wi­ki/Module_file
  47. Wikipedia: Chiptune
    http://en.wikipedia.org/wiki/Chiptune
  48. SDL_mixer 2.0
    http://www.libsdl.org/pro­jects/SDL_mixer/
  49. SDLJava: package sdljava.ttf
    http://sdljava.sourceforge­.net/docs/api/sdljava/ttf/pac­kage-summary.html#package_description
  50. SDLJava: class sdljava.ttf.SDLTTF
    http://sdljava.sourceforge­.net/docs/api/sdljava/ttf/SDLTTF­.html
  51. SDLJava: class sdljava.ttf.SDLTrueTypeFont
    http://sdljava.sourceforge­.net/docs/api/sdljava/ttf/SDLTru­eTypeFont.html
  52. SDL_ttf Documentation
    http://www.libsdl.org/pro­jects/SDL_ttf/docs/
  53. SDL_ttf 2.0 (není prozatím součástí SDLJava)
    http://www.libsdl.org/pro­jects/SDL_ttf/
  54. SDL_ttf doc
    http://www.libsdl.org/pro­jects/SDL_ttf/docs/SDL_ttf_fra­me.html
  55. SDL 1.2 Documentation: SDL_Surface
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsurface.html
  56. SDL 1.2 Documentation: SDL_PixelFormat
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlpixelformat.html
  57. SDL 1.2 Documentation: SDL_LockSurface
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdllocksurface.html
  58. SDL 1.2 Documentation: SDL_UnlockSurface
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlunlocksurface.html
  59. SDL 1.2 Documentation: SDL_LoadBMP
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlloadbmp.html
  60. SDL 1.2 Documentation: SDL_SaveBMP
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsavebmp.html
  61. SDL 1.2 Documentation: SDL_BlitSurface
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlblitsurface.html
  62. SDL 1.2 Documentation: SDL_VideoInfo
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlvideoinfo.html
  63. SDL 1.2 Documentation: SDL_GetVideoInfo
    http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlgetvideoinfo.html
  64. glDrawArrays
    http://www.opengl.org/sdk/doc­s/man4/xhtml/glDrawArrays­.xml
  65. glDrawElements
    http://www.opengl.org/sdk/doc­s/man4/xhtml/glDrawElemen­ts.xml
  66. glDrawArraysInstanced
    http://www.opengl.org/sdk/doc­s/man4/xhtml/glDrawArraysIn­stanced.xml
  67. glDrawElementsInstanced
    http://www.opengl.org/sdk/doc­s/man4/xhtml/glDrawElemen­tsInstanced.xml
  68. Root.cz: Seriál Grafická knihovna OpenGL
    http://www.root.cz/serialy/graficka-knihovna-opengl/
  69. 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/
  70. Best Practices for Working with Vertex Data
    https://developer.apple.com/li­brary/ios/documentation/3ddra­wing/conceptual/opengles_pro­grammingguide/Techniquesfor­WorkingwithVertexData/Techni­quesforWorkingwithVertexDa­ta.html
  71. Class BufferStrategy
    http://docs.oracle.com/ja­vase/6/docs/api/java/awt/i­mage/BufferStrategy.html
  72. Class Graphics
    http://docs.oracle.com/ja­vase/1.5.0/docs/api/java/aw­t/Graphics.html
  73. Double Buffering and Page Flipping
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/doublebuf.html
  74. BufferStrategy and BufferCapabilities
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/bufferstrategy.html
  75. Java:Tutorials:Double Buffering
    http://content.gpwiki.org/in­dex.php/Java:Tutorials:Dou­ble_Buffering
  76. Double buffer in standard Java AWT
    http://www.codeproject.com/Ar­ticles/2136/Double-buffer-in-standard-Java-AWT
  77. Java 2D: Hardware Accelerating – Part 1 – Volatile Images
    http://www.javalobby.org/fo­rums/thread.jspa?threadID=16840&tstar­t=0
  78. Java 2D: Hardware Accelerating – Part 2 – Buffer Strategies
    http://www.javalobby.org/ja­va/forums/t16867.html
  79. How does paintComponent work?
    http://stackoverflow.com/qu­estions/15544549/how-does-paintcomponent-work
  80. A Swing Architecture Overview
    http://www.oracle.com/technet­work/java/architecture-142923.html
  81. Class javax.swing.JComponent
    http://docs.oracle.com/ja­vase/6/docs/api/javax/swin­g/JComponent.html
  82. Class java.awt.Component
    http://docs.oracle.com/ja­vase/6/docs/api/java/awt/Com­ponent.html
  83. Class java.awt.Component.BltBufferStrategy
    http://docs.oracle.com/ja­vase/6/docs/api/java/awt/Com­ponent.BltBufferStrategy.html
  84. Class java.awt.Component.FlipBufferStrategy
    http://docs.oracle.com/ja­vase/6/docs/api/java/awt/Com­ponent.FlipBufferStrategy­.html
  85. Metoda java.awt.Component.isDoubleBuffered()
    http://docs.oracle.com/ja­vase/6/docs/api/java/awt/Com­ponent.html#isDoubleBuffe­red()
  86. Metoda javax.swing.JComponent.is­DoubleBuffered()
    http://docs.oracle.com/ja­vase/6/docs/api/javax/swin­g/JComponent.html#isDouble­Buffered()
  87. Metoda javax.swing.JComponent.set­DoubleBuffered()
    http://docs.oracle.com/ja­vase/6/docs/api/javax/swin­g/JComponent.html#setDouble­Buffered(boolean)
  88. Javadoc – třída GraphicsDevice
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsDevice.html
  89. Javadoc – třída GraphicsEnvironment
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsEnvironment.html
  90. Javadoc – třída GraphicsConfiguration
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Grap­hicsConfiguration.html
  91. Javadoc – třída DisplayMode
    http://docs.oracle.com/ja­vase/7/docs/api/java/awt/Dis­playMode.html
  92. Lesson: Full-Screen Exclusive Mode API
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/
  93. Full-Screen Exclusive Mode
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/exclusivemode.html
  94. Display Mode
    http://docs.oracle.com/ja­vase/tutorial/extra/fullscre­en/displaymode.html
  95. Using the Full-Screen Exclusive Mode API in Java
    http://www.developer.com/ja­va/other/article.php/3609776/U­sing-the-Full-Screen-Exclusive-Mode-API-in-Java.htm
  96. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  97. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  98. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  99. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  100. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  101. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  102. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  103. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  104. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  105. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  106. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  107. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  108. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  109. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  110. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  111. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  112. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  113. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  114. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  115. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  116. ASM Home page
    http://asm.ow2.org/
  117. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  118. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  119. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  120. BCEL Home page
    http://commons.apache.org/bcel/
  121. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  122. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  123. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  124. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  125. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  126. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  127. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  128. Javassist
    http://www.jboss.org/javassist/
  129. Byteman
    http://www.jboss.org/byteman
  130. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  131. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  132. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  133. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  134. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  135. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  136. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  137. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  138. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  139. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  140. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  141. Cobertura
    http://cobertura.sourceforge.net/
  142. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html

Byl pro vás článek přínosný?