Pohled pod kapotu JVM – závěrečné porovnání JVM, Lua VM a Python VM (1/2)

23. 9. 2014
Doba čtení: 26 minut

Sdílet

V dnešní části seriálu (nejenom) o programovacím jazyku Java a JVM bude provedeno závěrečné porovnání trojice virtuálních strojů: JVM, Lua VM a Python VM. Zaměříme se především na porovnání struktury bajtkódu, přesněji řečeno instrukční sady všech tří porovnávaných virtuálních strojů.

Obsah

1. Pohled pod kapotu JVM - závěrečné porovnání JVM, Lua VM a Python VM (1/2)

  1.1 JVM

  1.2 Lua VM

  1.3 Python VM

2. Základní porovnání bajtkódů všech tří virtuálních strojů

3. Aritmetické operace

  3.1 JVM

  3.2 Lua VM

  3.3 Python VM

4. Logické operace

  4.1 JVM

  4.2 Lua VM

  4.3 Python VM

5. Aritmetické a bitové posuny

  5.1 JVM

  5.2 Lua VM

  5.3 Python VM

6. Konverze mezi základními datovými typy

7. Instrukce pro operace s registry popř. zásobníkem operandů

  7.1 JVM

  7.2 Lua VM

  7.3 Python VM

8. Podmíněné a nepodmíněné skoky, větvení kódu

  8.1 JVM

  8.2 Lua VM

  8.3 Python VM

9. Obsah následující části seriálu

10. Odkazy na Internetu

1. Pohled pod kapotu JVM - závěrečné porovnání JVM, Lua VM a Python VM (1/2)

V dnešní a taktéž i navazující části seriálu o JVM i o dalších dvou virtuálních strojích bude provedeno závěrečné porovnání vlastností a možností trojice virtuálních strojů, tj. JVM, Lua VM i Python VM. Kromě obecného porovnání těchto tří virtuálních strojů se zaměříme i na vzájemné srovnání struktury bajtkódu, přesněji řečeno instrukční sady všech tří porovnávaných VM. Následuje shrnutí základních vlastností virtuálního stroje Javy, virtuálního stroje jazyka Lua a konečně i virtuálního stroje Pythonu (CPythonu):

1.1 JVM

První typ bajtkódu 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ů.

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

1.2 Lua VM

Bajtkód programovacího jazyka Lua 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. Bajtkó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 poleVýznam
1i6kód instrukce
2A8index či hodnota prvního operandu
3B9index či hodnota druhého operandu
4C9index či hodnota třetího operandu

iABx

#OznačeníDélka bitového poleVýznam
1i6kód instrukce
2A8index či hodnota prvního operandu
3Bx18index či hodnota druhého operandu

iAsBx

#OznačeníDélka bitového poleVýznam
1i6kód instrukce
2A8index či hodnota prvního operandu
3sBx18index či hodnota druhého operandu (zde se znaménkem)

iAx

#OznačeníDélka bitového poleVýznam
1i6kód instrukce
2Ax26index či hodnota prvního (jediného) operandu

1.3 Python VM

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í podkapitole 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).

2. Základní porovnání bajtkódů všech tří virtuálních strojů

Tři nejdůležitější společné vlastnosti ale i rozdíly mezi trojicí popisovaných virtuálních strojů jsou vypsány v následující tabulce:

#VlastnostJVMLua VMPython VM
1Typ VMzásobníkovýregistrovýzásobníkový
2Typový systém VMstriktnídynamickýdynamický
3Výjimky v bajtkóduanoneano

Typ VM má velký vliv jak na způsob implementace všech aritmetických a logických operací, tak i na způsob předávání parametrů volaným funkcím či metodám. Typový systém taktéž do značné míry ovlivňuje vlastnost VM i bajtkódu – existenci konverzních instrukcí, možnost použití jedné instrukce pro větší množství datových typů atd. Podpora výjimek v bajtkódu do značné míry ovlivňuje i syntaxi programovacího jazyka postaveného nad danou VM. Ve druhé tabulce jsou vypsány další důležité vlastnosti popisovaných VM:

#VlastnostJVMLua VMPython VM
1Skokyv rámci metodyv rámci funkcev rámci funkce
2Aritmetické instrukceanoanoano
3Logické instrukceanočástečněano
4Bitové instrukceanočástečněano
5Bitové posuny a rotaceanoneano
6Přetížení operátorů ve VMneneano
7Konverzní instrukceanonene
     
8Programové smyčkyřešeno skokyspeciální instrukcespeciální instrukce
9Synchronizační instrukceanonene
10Strukturované datové typypoletabulkyseznamy+n-tice
11Výjimky v bajtkóduanoneano
     
12Podpora pro volání funkcí v bajtkóduneanoano
13Podpora pro volání metod v bajtkóduanoanoano
14Volání statických metod v bajtkóduanoanočástečně
15Kontrola počtu parametrů při překladuanonev závislosti na deklaraci
16Kontrola počtu parametrů při načítání/běhuanoneano
17Předávání dat do funkcípřes zásobníkpřes registrypřes zásobník
     
18Podpora deklarace typu parametrů při překladuanonene
19Podpora kontroly typu parametrů při překladuanonene
20Podpora proměnného počtu parametrůanoanoano
21Podpora pojmenovaných parametrůneneano
22Podpora proměnného počtu návratových hodnotneanonepřímo
     
23Funkce je plnohodnotný datový typNe pro Javu<=7anoano
24Podpora uzávěrů (closures)Ne pro Javu<=7anoano

V následujících sedmi kapitolách si popíšeme způsob implementace různých operací v bajtkódech JVM, Lua VM i Python VM. Uvidíme, že některé operace jsou prováděny podobným způsobem, ovšem mnohé další operace – například způsob implementace programových smyček – se mnohdy zásadním způsobem odlišuje.

3. Aritmetické operace

Začněme popisem aritmetických instrukcí, které jsou velmi jednoduché a snadno pochopitelné.

3.1 JVM

V případě JVM se většinou jedná o instrukce pracující s dvojicí operandů uložených na zásobníku operandů, Tyto instrukce slouží pro implementaci pěti základních (binárních) aritmetických operací – součtu, rozdílu, součinu, podílu a výpočtu zbytku po dělení. Vzhledem k tomu, že každá tato operace existuje ve čtyřech variantách pro datové typy int, long, float a double, jsou binární aritmetické operace implementovány dvaceti instrukcemi. Další čtyři instrukce – změna znaménka – však pracují pouze s jedním operandem. Virtuální stroj Javy v čase běhu aplikace nebo v čase verifikace bajtkódu testuje, zda se všechny aritmetické operace provádí se správným typem operandů. Povšimněte si, že instrukční soubor JVM neobsahuje žádné aritmetické operace pro operandy typu byte, short či char – operandy těchto typů jsou vždy převedeny na int. Navíc se u celočíselných operací netestuje přetečení, což je možná u vysokoúrovňového jazyka poněkud překvapující (ono je ostatně při poněkud jednostranném pohledu překvapující i to, že Java vůbec obsahuje primitivní datové typy :-):

#InstrukceOpkódOperand 1Operand 2OperacePoznámka
1iadd0x60int int součetoba původní operandy jsou ze zásobníku operandů odstraněny
2ladd0x61long long součet-//-
3fadd0x62float float součet-//-
4dadd0x63doubledoublesoučet-//-
5isub0x64int int rozdíl-//-
6lsub0x65long long rozdíl-//-
7fsub0x66float float rozdíl-//-
8dsub0x67doubledoublerozdíl-//-
9imul0x68int int součin-//-
10lmul0x69long long součin-//-
11fmul0x6Afloat float součin-//-
12dmul0x6Bdoubledoublesoučin-//-
13idiv0x6Cint int podíl-//-
14ldiv0x6Dlong long podíl-//-
15fdiv0x6Efloat float podíl-//-
16ddiv0x6Fdoubledoublepodíl-//-
17irem0x70int int zbytek po dělení-//-
18lrem0x71long long zbytek po dělení-//-
19frem0x72float float zbytek po dělení-//-
20drem0x73doubledoublezbytek po dělení-//-
21ineg0x74int  změna znaménkapůvodní operand je ze zásobníku operandů odstraněn
22lneg0x75long  změna znaménkapůvodní operand je ze zásobníku operandů odstraněn
23fneg0x76float  změna znaménkapůvodní operand je ze zásobníku operandů odstraněn
24dneg0x77double změna znaménkapůvodní operand je ze zásobníku operandů odstraněn

3.2 Lua VM

V bajtkódu Lua VM je situace mnohem jednodušší, neboť zde najdeme jen sedm aritmetických instrukcí. Tyto instrukce pracují s obsahem vybraných dvou či tří registrů. Většina aritmetických instrukci pracuje se dvěma zdrojovými registry a jedním registrem cílovým; výjimkou je jen poslední instrukce, která má pouze jeden zdrojový a jeden cílový registr:

#InstrukceOpkódOperand 1Operand 2Operace
1ADD13int/floatint/floatsoučet
2SUB14int/floatint/floatrozdíl
3MUL15int/floatint/floatsoučin
4DIV16int/floatint/floatpodíl
5MOD17int/floatint/floatpodíl modulo
6POW18int/floatint/floatumocnění
7UNM19int/float×změna znaménka

Zajímavá je existence instrukce POW, protože tato aritmetická operace se používá pouze minimálně.

3.3 Python VM

Podobně jednoduchá je i situace ve virtuálním stroji Python VM, až na ten rozdíl, že zde aritmetické instrukce pracují s hodnotami umístěnými na zásobníku operandů, podobně jako je tomu i v JVM. Všechny instrukce navíc mohou pracovat i pro různé typy objektů (to odpovídá přetížení příslušných operandů):

#InstrukceOperand 1Operand 2Operace
1BINARY_ADDčíslo/objektčíslo/objektsoučet
2BINARY_SUBTRACTčíslo/objektčíslo/objektrozdíl
3BINARY_MULTIPLYčíslo/objektčíslo/objektsoučin
4BINARY_DIVIDEčíslo/objektčíslo/objektpodíl
5BINARY_MODULOčíslo/objektčíslo/objektpodíl modulo
6BINARY_POWERčíslo/objektčíslo/objektumocnění
7UNARY_NEGATIVEčíslo/objekt×změna znaménka

I zde nalezneme instrukci BINARY_POWER podobnou instrukci POW z Lua VM.

4. Logické operace

Druhou skupinou instrukcí jsou instrukce, pomocí nichž se implementují téměř všechny základní bitové a logické operace.

4.1 JVM

Nejprve se zmíníme o bitových operacích v JVM. Programovací jazyk Java obsahuje několik bitových operací, které lze provádět nad datovými typy int či long. Jedná se o bitový součin (operace AND prováděná bit po bitu), bitový součet, nonekvivalenci a negaci (opět prováděnou bit po bitu). V instrukčním souboru však nalezneme pouze první tři bitové instrukce: bitový součin, bitový součet a nonekvivalenci, protože negace se provádí pomocí dvou instrukcí, konkrétně s využitím nonekvivalence proti masce 0xffffffff (popř. obdobné konstanty typu long). Tyto instrukce jsou použity i pro provádění logických operací: logického součtu, součinu, nonekvivalence a negace, takže do instrukčního souboru JVM nebylo nutné pro datový typ boolean přidávat žádné další specializované instrukce.

#InstrukceOpkódOperand 1Operand 2OperacePoznámka
1iand0x7Eint int bitový součin-//-
2land0x7Flonglongbitový součin-//-
3ior 0x80int int bitový součet-//-
4lor 0x81longlongbitový součet-//-
5ixor0x82int int nonekvivalence-//-
6lxor0x83longlongnonekvivalence-//-

4.2 Lua VM

Velmi zajímavé je, že v Lua VM najdeme pouze dvě instrukce sloužící pro implementaci logických funkcí. První instrukce slouží k negaci hodnoty uložené ve vybraném registru, druhá instrukce pro porovnání, zda se hodnota uložená v registru rovná zapsané konstantě. Tato druhá instrukce se typicky kombinuje s přeskokem následující instrukce:

#InstrukceOpkódOperand 1Operand 2Operace
1NOT20int/float×negace
2TESTSET28int/floatporovnávaná hodnotatest hodnoty uložené v registru

4.3 Python VM

V Python VM opět najdeme celou sadu (očekávaných) instrukcí pro provádění logických operací, včetně operace XOR:

#InstrukceOperand 1Operand 2Operace
1BINARY_ANDčíslo/objektčíslo/objektbitový/logický součin
2BINARY_OR číslo/objektčíslo/objektbitový/logický součet
3BINARY_XORčíslo/objektčíslo/objektbitová/logická nonekvivalence
7UNARY_NOTčíslo/objekt×negace

5. Aritmetické a bitové posuny

5.1 JVM

Navíc programovací jazyk Java obsahuje i operace aritmetického a bitového posunu doleva a doprava, které mají svůj obraz v instrukční sadě. Posun doleva odpovídá operátoru << (nezávisle na znaménku prvního operandu), posun doprava pak dvojici operátorů >> a >>> (v céčku se rozlišují typy signed a unsigned, proto druhý z těchto operátorů nemusí být v tomto jazyku použit). Mimochodem: ze specifikace JVM vyplývá, že druhý operand v instrukcích posunu je nejprve maskován konstantou 0x1f v případě typu int či 0x3f u datového typu long. Jinými slovy to znamená, že pro posun je použito pouze dolních 5 či 6 bitů. Taktéž si povšimněte, že u instrukcí pro bitový či aritmetický posun je druhým operandem vždy hodnota typu int, a to i v případě, že se posouvá hodnota typu long:

#InstrukceOpkódOperand 1Operand 2OperacePoznámka
1ishl 0x78int int aritmetický/bitový posun dolevaoba původní operandy ze zásobníku operandů jsou odstraněny
2lshl 0x79longint aritmetický/bitový posun doleva-//-
3ishr 0x7Aint int aritmetický posun doprava-//-
4lshr 0x7Blongint aritmetický posun doprava-//-
5iushr0x7Cint int bitový posun doprava-//-
6lushr0x7Dlongint bitový posun doprava-//-

5.2 Lua VM

Jak je tomu v Lua VM? Zde je situace poněkud zvláštní, protože instrukce pro bitové či aritmetické posuny zde (Lua 5.1 a Lua 5.2) vůbec nenajdeme, i když existují minimálně dva patche, které tyto operace přidávají jak do jazyka, tak i do bajtkódu (v Lua 5.3 je již podpora pro posuvy zahrnuta, ovšem stále se nejedná o finální vydání).

5.3 Python VM

V Python VM nalezneme následující instrukce pro posuny:

#InstrukceOperand 1Operand 2Operace
1BINARY_LSHIFTčíslo/objektčíslo/objektposun doleva
2BINARY_RSHIFTčíslo/objektčíslo/objektposun doprava

6. Konverze mezi základními datovými typy

Další skupinou instrukcí, kterou lze v některých typech virtuálních strojů nalézt, jsou instrukce sloužící pro konverzi dat, přesněji řečeno pro konverzi mezi hodnotami primitivních datových typů. Konverzní instrukce nalezneme prakticky jen v JVM, takže tato kapitola může být relativně krátká. Programovací jazyk Java sice patří mezi jazyky silně typované, ale konverze mezi některými datovými typy jsou prováděny automaticky (příkladem může být konverze byte-→int) a jiné lze explicitně zapsat. Navíc se konverzní instrukce objevují v bajtkódu JVM například při vytváření návratové hodnoty metody v případě, že tato hodnota nemá typ int, long, float či double. Ovšem ne všechny kombinace konverzí datových typů jsou v instrukční sadě přítomny, takže některé konverze je ve skutečnosti nutné provádět pomocí dvojice instrukcí (například se může jednat o konverzi z float na short a podobně). Podívejme se ostatně na tabulku obsahující všechny konverzní instrukce. V prvním sloupci je uveden datový typ, z něhož je konverze prováděna a v prvním řádku naopak výsledný datový typ:

z/na->charbyteshortintlongfloatdouble
char        
byte        
short        
inti2ci2bi2s i2li2fi2d
long    l2i l2fl2d
float    f2if2l f2d
double   d2id2ld2f 

7. Instrukce pro operace s registry popř. zásobníkem operandů

Před provedením aritmetických, logických i bitových instrukcí jakož i před zavoláním jiné funkce popř. metody je mnohdy nutné data vložit do vhodných registrů a/nebo na správné místo v zásobníku operandů. K tomu slouží instrukce popsané v této kapitole.

7.1 JVM

JVM je zásobníkový virtuální stroj, proto jeho instrukční sada obsahuje několik instrukcí, které se nápadně podobají slovům používaným v programovacím jazyku Forth (ono se ostatně není čemu divit, protože se jedná skutečně o základní „zásobníkové“ instrukce). Důležité je, že tyto instrukce již nepotřebují být rozděleny podle toho s jakými daty pracují, protože každému prvku uloženém na zásobníku operandů je stejně přiřazen datový tag, který je těmito instrukcemi využíván. Zajímavé je, že podobný princip nebyl použit například u aritmetických instrukcí (bavil jsem se o tom s jedním vývojářem VM, kterému se to také příliš nelíbilo, ale už si nepamatoval, kdo vlastně s návrhem instrukční sady před více než patnácti lety u společnosti Sun přišel):

#InstrukceOpkódPopis
1pop57odstraní jednu položku z TOS (platí pro int, float, referenci na objekt)
2pop258odstraní jednu (long, double) položku či dvě (int, float, reference) položky ze zásobníku
3dup59zduplikuje (zkopíruje) položku z TOS (platí pro int, float, referenci na objekt)
4dup_x15Azduplikuje (zkopíruje) položku z TOS, ale vloží i o dvě pozice níž do zásobníku
5dup_x25Bzduplikuje (zkopíruje) položku z TOS a vloží i o dvě či tři pozice níž (v závislosti na bitové šířce)
6dup25Cduplikace jedné (long, double) či dvou (int, float, reference) položek
7dup2_x15Dkombinace vlastností instrukcí dup2 a dup_x1 (vložení prvku o dvě místa níže)
8dup2_x25Ekombinace vlastností instrukcí dup2 a dup_x2
9swap5Fprohodí dva prvky ležící na vrcholu zásobníku (platí pro int, float, referenci na objekt)

7.2 Lua VM

Už z tabulek uvedených v předchozích kapitolách bylo patrné, že bajtkód Lua VM je velmi elegantní a navíc pojatý minimalisticky. To platí i pro instrukce pro přenosy dat, protože vše zařizuje jediná instrukce pojmenovaná MOVE, která slouží k přenosu dat mezi libovolnými dvěma pracovními registry. U registrového virtuálního stroje není žádná další instrukce zapotřebí, i když by v některých případech bylo vhodné mít instrukci typu SWAP:

#InstrukceOpkódOperand 1Operand 2Operace
1MOVE0cílový registrzdrojový registrpřesun dat ze zdrojového do cílového registru

7.3 Python VM

Python VM je, podobně jako JVM, virtuální stroj postavený na zásobníku operandů, z čehož také vyplývá, že pro manipulaci s operandy zde nalezneme podobné instrukce, jako v JVM:

#InstrukceOperand 1Operand 2Operace
1POP_TOP implicitníimplicitníodstraní položku ze zásobníku operandů
2ROT_TWO implicitníimplicitnírotace (swap) dvou položek
3ROT_THREEimplicitníimplicitnírotace (roll) tří položek
4ROT_FOUR implicitníimplicitnírotace čtyř položek
5DUP_TOP implicitníimplicitníduplikace položky na zásobníku operandů

8. Podmíněné a nepodmíněné skoky, větvení kódu

Všechny popisované virtuální stroje obsahují instrukce pro provádění podmíněných a nepodmíněných skoků. Tyto velmi důležité instrukce si popíšeme v této kapitole.

8.1 JVM

V JVM nalezneme velké množství instrukcí pro provádění podmíněných i nepodmíněných skoků. Základní instrukce spadající do této kategorie jsou popsány v následující tabulce:

#InstrukceOpkódOperandyPodmínkaOperace
1goto0xA7highbyte, lowbyte přímý skok na adresu uloženou v dvojici operandů: highbyte*256+lowbyte
2goto_w0xC8byte1,byte2,byte3 byte4 přímý skok na adresu uloženou ve čtveřici operandů: byte1*224+byte2*216+byte3*28+byte4
3ifeq0x99highbyte, lowbyteTOS=0skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
4ifne0x9Ahighbyte, lowbyteTOS≠0skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
5iflt0x9Bhighbyte, lowbyteTOS<0skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
6ifge0x9Chighbyte, lowbyteTOS≥0skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
7ifgt0x9Dhighbyte, lowbyteTOS>0skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
8ifle0x9Ehighbyte, lowbyteTOS≤0skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
9ifnull 0xC6highbyte, lowbyteTOS=nullskok na adresu highbyte*256+lowbyte při splnění podmínky
10ifnonnull0xC7highbyte, lowbyteTOS≠nullskok na adresu highbyte*256+lowbyte při splnění podmínky

V případě potřeby testu hodnoty proměnných typu long, float nebo double nezbývá nic jiného než využít instrukce, které porovnají dvě hodnoty daného typu (typicky se jedná o proměnnou a konstantu) a uloží na TOS hodnotu 0, 1 nebo -1 na základě výsledku tohoto porovnání. Jedná se o následující pětici instrukcí:

#InstrukceOpkódOperand 1Operand 2Výsledek
1lcmp 0x94long long 1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
2fcmpl0x95float float 1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
-1 když operand 1 je NaN
-1 když operand 2 je NaN
3fcmpg0x96float float 1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
1 když operand 1 je NaN
1 když operand 2 je NaN
4dcmpl0x97doubledouble1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
-1 když operand 1 je NaN
-1 když operand 2 je NaN
5dcmpg0x98doubledouble1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
1 když operand 1 je NaN
1 když operand 2 je NaN

Z teoretického hlediska by podmíněné skoky popsané v předchozích dvou odstavcích měly ve všech případech postačovat. V praxi – například při implementaci počítaných programových smyček – je však vhodné umět efektivně provést podmíněný skok na základě porovnání dvou operandů, nikoli na základě porovnání jednoho operandu vůči nule. Samozřejmě je možné nejdříve oba operandy od sebe odečíst a poté provést skok na základě výsledku tohoto rozdílu (což se podobá systému používanému u mnohých typů mikroprocesorů), to však vyžaduje zbytečně dlouhou sekvenci instrukcí. Z tohoto důvodu se v instrukčním souboru JVM nachází i instrukce, které porovnají dvojici operandů typu int uloženou na nejvrchnějších dvou pozicích zásobníku operandů a skok vykonají na základě toho, zda je první operand větší, menší či roven operandu druhému (oba operandy jsou navíc ze zásobníku odstraněny):

#InstrukceOpkódOperandyPodmínkaOperace
1if_icmpeq0x9Fhighbyte, lowbytevalue1=value2skok na adresu highbyte*256+lowbyte při splnění podmínky
2if_icmpne0xA0highbyte, lowbytevalue1≠value2skok na adresu highbyte*256+lowbyte při splnění podmínky
3if_icmplt0xA1highbyte, lowbytevalue1<value2skok na adresu highbyte*256+lowbyte při splnění podmínky
4if_icmpge0xA2highbyte, lowbytevalue1≥value2skok na adresu highbyte*256+lowbyte při splnění podmínky
5if_icmpgt0xA3highbyte, lowbytevalue1>value2skok na adresu highbyte*256+lowbyte při splnění podmínky
6if_icmple0xA4highbyte, lowbytevalue1≤value2skok na adresu highbyte*256+lowbyte při splnění podmínky

Kromě porovnání dvou operandů typu int je taktéž možné porovnat dvě reference. Ovšem vzhledem k neexistenci skutečné ukazatelové aritmetiky (viz výše) lze dvě reference porovnat pouze na rovnost nebo nerovnost, nikoli již na to, zda je jedna reference (resp. hodnota ukazatele) „větší“ nebo „menší“ než druhá. Z tohoto důvodu pro porovnávání dvou referencí existují pouze dvě instrukce vypsané v následující tabulce:

#InstrukceOpkódOperandyPodmínkaOperace
1if_acmpeq0xA5highbyte, lowbytevalue1=value2skok na adresu highbyte*256+lowbyte při splnění podmínky
2if_acmpne0xA6highbyte, lowbytevalue1≠value2skok na adresu highbyte*256+lowbyte při splnění podmínky

8.2 Lua VM

Instrukcí pro skoky nalezneme v Lua VM naprosté minimum. Jedná se ve skutečnosti o jedinou instrukci JMP pro provedení relativního skoku v rámci jedné funkce. Kromě toho však v instrukční sadě nalezneme čtveřici instrukcí, které sice neprovedou podmíněný skok, ale podmíněný přeskok další instrukce. To je velmi snadné, protože v Lua VM – na rozdíl od JVM – mají všechny instrukce pevnou (konstantní) délku. Tyto instrukce s podmínkami jsou navíc velmi univerzální:

#InstrukceOpkódOperace
1EQ 24přeskok další instrukce za podmínky ((RK[B] == RK[C]) ~= A)
2LT 25přeskok další instrukce za podmínky ((RK[B] < RK[C]) ~= A)
3LE 26přeskok další instrukce za podmínky ((RK[B] <= RK[C]) ~= A)
4TEST27přeskok další instrukce za podmínky not (R[A] <=> C)
   
5JMP 23relativní skok

8.3 Python VM

Ve virtuálním stroji programovacího jazyka Python nalezneme šest instrukcí skoku – dvě instrukce pro provedení nepodmíněných skoků a tři instrukce pro provedení skoků podmíněných:

#InstrukceProváděná operace
1JUMP_ABSOLUTEskok na zadaný index (lokální adresu)
2JUMP_FORWARDskok na relativní index (je zadána delta oproti současné adrese)
   
3POP_JUMP_IF_TRUE(podmíněný skok na základě obsahu TOS; obsah TOS je odstraněn
4POP_JUMP_IF_FALSE(podmíněný skok na základě obsahu TOS (opačná podmínka); obsah TOS je odstraněn
5JUMP_IF_TRUE_OR_POPpokud TOS==true, provede se skok, v opačném případě se TOS odstraní
6JUMP_IF_FALSE_OR_POPopačné chování, než je tomu v předchozí instrukci

bitcoin_skoleni

9. Obsah následující části seriálu

V následující části tohoto seriálu bude vzájemné porovnání vlastností bajtkódu virtuálního stroje Javy, Pythonu i programovacího jazyka Lua dokončeno. Zabývat se budeme především problematikou volání funkcí popř. volání metod, deklarace tříd a objektů a taktéž rozličnými způsoby přístupu k třídním atributům (statickým atributům) i k atributům objektů. Nezapomeneme ani na krátké porovnání způsobu práce s výjimkami.

10. Odkazy na Internetu

  1. Python Byte Code Instructions
    https://docs.python.org/release/2.5.2/lib/bytecodes.html
  2. Python 2.x: funkce range()
    https://docs.python.org/2/library/functions.html#range
  3. Python 2.x: typ iterátor
    https://docs.python.org/2/library/stdtypes.html#iterator-types
  4. Python break, continue and pass Statements
    http://www.tutorialspoint.com/python/python_loop_control.htm
  5. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/python-bytecode-fun-with-dis/
  6. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.name/category/my-projects/pythons-innards/
  7. Byterun
    https://github.com/nedbat/byterun
  8. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Python/lib/node56.html
  9. Python Byte Code Instructions
    https://docs.python.org/3.2/library/dis.html#python-bytecode-instructions
  10. dis - Python module
    https://docs.python.org/2/library/dis.html
  11. Comparison of Python virtual machines
    http://polishlinux.org/apps/cli/comparison-of-python-virtual-machines/
  12. Lambda the Ultimate: Coroutines in Lua,
    http://lambda-the-ultimate.org/node/438
  13. Coroutines Tutorial,
    http://lua-users.org/wiki/CoroutinesTutorial
  14. Lua Coroutines Versus Python Generators,
    http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators
  15. Programming in Lua 9.1 – Coroutine Basics,
    http://www.lua.org/pil/9.1.html
  16. Wikipedia CZ: Koprogram,
    http://cs.wikipedia.org/wiki/Koprogram
  17. Wikipedia EN: Coroutine,
    http://en.wikipedia.org/wiki/Coroutine
  18. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  19. Programming in Lua: 6 - More about Functions
    http://www.lua.org/pil/6.html
  20. Lua Lanes,
    http://kotisivu.dnainternet.net/askok/bin/lanes/
  21. Programming in Lua: 6.1 - Closures
    http://www.lua.org/pil/6.1.html
  22. Programming in Lua: 9.1 - Coroutine Basics
    http://www.lua.org/pil/9.1.html
  23. Programming in Lua: Numeric for
    http://www.lua.org/pil/4.3.4.html
  24. Programming in Lua: break and return
    http://www.lua.org/pil/4.4.html
  25. Programming in Lua: Tables
    http://www.lua.org/pil/2.5.html
  26. Programming in Lua: Table Constructors
    http://www.lua.org/pil/3.6.html
  27. Programovací jazyk Lua
    http://palmknihy.cz/web/kniha/programovaci-jazyk-lua-12651.htm
  28. Lua: Tables Tutorial
    http://lua-users.org/wiki/TablesTutorial
  29. Lua: Control Structure Tutorial
    http://lua-users.org/wiki/ControlStructureTutorial
  30. Lua Types Tutorial
    http://lua-users.org/wiki/LuaTypesTutorial
  31. Goto Statement in Lua
    http://lua-users.org/wiki/GotoStatement
  32. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  33. Lua 5.2 sources - lopcodes.h
    http://www.lua.org/source/5.2/lopcodes.h.html
  34. Lua 5.2 sources - lopcodes.c
    http://www.lua.org/source/5.2/lopcodes.c.html
  35. For-each Loop in Java
    http://www.leepoint.net/notes-java/flow/loops/foreach.html
  36. For Loop (Wikipedia)
    http://en.wikipedia.org/wiki/For_loop
  37. Heinz Rutishauser
    http://en.wikipedia.org/wiki/Heinz_Rutishauser
  38. Parrot
    http://www.parrot.org/
  39. Parrot languages
    http://www.parrot.org/languages
  40. Parrot Primer
    http://docs.parrot.org/parrot/latest/html/docs/intro.pod.html
  41. Parrot Opcodes
    http://docs.parrot.org/parrot/latest/html/ops.html
  42. Parrot VM
    http://en.wikibooks.org/wiki/Parrot_Virtual_Machine
  43. Parrot Assembly Language
    http://www.perl6.org/archive/pdd/pdd06_pasm.html
  44. Parrot Reference: Chapter 11 - Perl 6 and Parrot Essentials
    http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html
  45. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  46. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html
  47. The JVM Instruction Set
    http://mpdeboer.home.xs4all.nl/scriptie/node14.html
  48. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  49. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html
  50. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/developerworks/java/library/j-jtp10185/
  51. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README
  52. Java SE 6 Performance White Paper
    http://www.oracle.com/technetwork/java/6-performance-137236.html
  53. Lukas Stadler's Blog
    http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html
  54. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  55. PrintAssembly
    https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly
  56. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  57. 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
  58. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4
  59. 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
  60. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  61. ASM Home page
    http://asm.ow2.org/
  62. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  63. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wiki/ObjectWeb_ASM
  64. Java Bytecode BCEL vs ASM
    http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/
  65. BCEL Home page
    http://commons.apache.org/bcel/
  66. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  67. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/proper/commons-bcel/
  68. BCEL Manual
    http://commons.apache.org/bcel/manual.html
  69. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  70. BCEL Tutorial
    http://www.smfsupport.com/support/java/bcel-tutorial!/
  71. Bytecode Engineering
    http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html
  72. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  73. Javassist
    http://www.jboss.org/javassist/
  74. Byteman
    http://www.jboss.org/byteman
  75. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/developerworks/java/library/j-dyn0414/
  76. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
  77. The class File Format
    http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
  78. javap - The Java Class File Disassembler
    http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html
  79. javap-java-1.6.0-openjdk(1) - Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  80. Using javap
    http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html
  81. Examine class files with the javap command
    http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354
  82. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  83. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wiki/Aspect_oriented_programming
  84. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  85. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  86. Cobertura
    http://cobertura.sourceforge.net/
  87. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclasslib/overview.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.