Hlavní navigace

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

Pavel Tišnovský

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

Doba čtení: 26 minut

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

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:

# Vlastnost JVM Lua VM Python VM
1 Typ VM zásobníkový registrový zásobníkový
2 Typový systém VM striktní dynamický dynamický
3 Výjimky v bajtkódu ano ne ano

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:

# Vlastnost JVM Lua VM Python VM
1 Skoky v rámci metody v rámci funkce v rámci funkce
2 Aritmetické instrukce ano ano ano
3 Logické instrukce ano částečně ano
4 Bitové instrukce ano částečně ano
5 Bitové posuny a rotace ano ne ano
6 Přetížení operátorů ve VM ne ne ano
7 Konverzní instrukce ano ne ne
         
8 Programové smyčky řešeno skoky speciální instrukce speciální instrukce
9 Synchronizační instrukce ano ne ne
10 Strukturované datové typy pole tabulky seznamy+n-tice
11 Výjimky v bajtkódu ano ne ano
         
12 Podpora pro volání funkcí v bajtkódu ne ano ano
13 Podpora pro volání metod v bajtkódu ano ano ano
14 Volání statických metod v bajtkódu ano ano částečně
15 Kontrola počtu parametrů při překladu ano ne v závislosti na deklaraci
16 Kontrola počtu parametrů při načítání/běhu ano ne ano
17 Předávání dat do funkcí přes zásobník přes registry přes zásobník
         
18 Podpora deklarace typu parametrů při překladu ano ne ne
19 Podpora kontroly typu parametrů při překladu ano ne ne
20 Podpora proměnného počtu parametrů ano ano ano
21 Podpora pojmenovaných parametrů ne ne ano
22 Podpora proměnného počtu návratových hodnot ne ano nepřímo
         
23 Funkce je plnohodnotný datový typ Ne pro Javu<=7 ano ano
24 Podpora uzávěrů (closures) Ne pro Javu<=7 ano ano

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

# Instrukce Opkód Operand 1 Operand 2 Operace Poznámka
1 iadd 0x60 int int součet oba původní operandy jsou ze zásobníku operandů odstraněny
2 ladd 0x61 long long součet -//-
3 fadd 0x62 float float součet -//-
4 dadd 0x63 double double součet -//-
5 isub 0x64 int int rozdíl -//-
6 lsub 0x65 long long rozdíl -//-
7 fsub 0x66 float float rozdíl -//-
8 dsub 0x67 double double rozdíl -//-
9 imul 0x68 int int součin -//-
10 lmul 0x69 long long součin -//-
11 fmul 0x6A float float součin -//-
12 dmul 0x6B double double součin -//-
13 idiv 0x6C int int podíl -//-
14 ldiv 0x6D long long podíl -//-
15 fdiv 0x6E float float podíl -//-
16 ddiv 0x6F double double podíl -//-
17 irem 0x70 int int zbytek po dělení -//-
18 lrem 0x71 long long zbytek po dělení -//-
19 frem 0x72 float float zbytek po dělení -//-
20 drem 0x73 double double zbytek po dělení -//-
21 ineg 0x74 int   změna znaménka původní operand je ze zásobníku operandů odstraněn
22 lneg 0x75 long   změna znaménka původní operand je ze zásobníku operandů odstraněn
23 fneg 0x76 float   změna znaménka původní operand je ze zásobníku operandů odstraněn
24 dneg 0x77 double   změna znaménka pů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:

# Instrukce Opkód Operand 1 Operand 2 Operace
1 ADD 13 int/float int/float součet
2 SUB 14 int/float int/float rozdíl
3 MUL 15 int/float int/float součin
4 DIV 16 int/float int/float podíl
5 MOD 17 int/float int/float podíl modulo
6 POW 18 int/float int/float umocnění
7 UNM 19 int/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ů):

# Instrukce Operand 1 Operand 2 Operace
1 BINARY_ADD číslo/objekt číslo/objekt součet
2 BINARY_SUBTRACT číslo/objekt číslo/objekt rozdíl
3 BINARY_MULTIPLY číslo/objekt číslo/objekt součin
4 BINARY_DIVIDE číslo/objekt číslo/objekt podíl
5 BINARY_MODULO číslo/objekt číslo/objekt podíl modulo
6 BINARY_POWER číslo/objekt číslo/objekt umocnění
7 UNARY_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.

# Instrukce Opkód Operand 1 Operand 2 Operace Poznámka
1 iand 0x7E int int bitový součin -//-
2 land 0x7F long long bitový součin -//-
3 ior 0x80 int int bitový součet -//-
4 lor 0x81 long long bitový součet -//-
5 ixor 0x82 int int nonekvivalence -//-
6 lxor 0x83 long long nonekvivalence -//-

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:

# Instrukce Opkód Operand 1 Operand 2 Operace
1 NOT 20 int/float × negace
2 TESTSET 28 int/float porovnávaná hodnota test 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:

# Instrukce Operand 1 Operand 2 Operace
1 BINARY_AND číslo/objekt číslo/objekt bitový/logický součin
2 BINARY_OR číslo/objekt číslo/objekt bitový/logický součet
3 BINARY_XOR číslo/objekt číslo/objekt bitová/logická nonekvivalence
7 UNARY_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:

# Instrukce Opkód Operand 1 Operand 2 Operace Poznámka
1 ishl 0x78 int int aritmetický/bitový posun doleva oba původní operandy ze zásobníku operandů jsou odstraněny
2 lshl 0x79 long int aritmetický/bitový posun doleva -//-
3 ishr 0x7A int int aritmetický posun doprava -//-
4 lshr 0x7B long int aritmetický posun doprava -//-
5 iushr 0x7C int int bitový posun doprava -//-
6 lushr 0x7D long int 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:

# Instrukce Operand 1 Operand 2 Operace
1 BINARY_LSHIFT číslo/objekt číslo/objekt posun doleva
2 BINARY_RSHIFT číslo/objekt číslo/objekt posun 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-> char byte short int long float double
char              
byte              
short              
int i2c i2b i2s   i2l i2f i2d
long       l2i   l2f l2d
float       f2i f2l   f2d
double       d2i d2l d2f  

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

# Instrukce Opkód Popis
1 pop 57 odstraní jednu položku z TOS (platí pro int, float, referenci na objekt)
2 pop2 58 odstraní jednu (long, double) položku či dvě (int, float, reference) položky ze zásobníku
3 dup 59 zduplikuje (zkopíruje) položku z TOS (platí pro int, float, referenci na objekt)
4 dup_x1 5A zduplikuje (zkopíruje) položku z TOS, ale vloží i o dvě pozice níž do zásobníku
5 dup_x2 5B zduplikuje (zkopíruje) položku z TOS a vloží i o dvě či tři pozice níž (v závislosti na bitové šířce)
6 dup2 5C duplikace jedné (long, double) či dvou (int, float, reference) položek
7 dup2_x1 5D kombinace vlastností instrukcí dup2 a dup_x1 (vložení prvku o dvě místa níže)
8 dup2_x2 5E kombinace vlastností instrukcí dup2 a dup_x2
9 swap 5F prohodí 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:

# Instrukce Opkód Operand 1 Operand 2 Operace
1 MOVE 0 cílový registr zdrojový registr př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:

# Instrukce Operand 1 Operand 2 Operace
1 POP_TOP implicitní implicitní odstraní položku ze zásobníku operandů
2 ROT_TWO implicitní implicitní rotace (swap) dvou položek
3 ROT_THREE implicitní implicitní rotace (roll) tří položek
4 ROT_FOUR implicitní implicitní rotace čtyř položek
5 DUP_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:

# Instrukce Opkód Operandy Podmínka Operace
1 goto 0xA7 highbyte, lowbyte   přímý skok na adresu uloženou v dvojici operandů: highbyte*256+lowbyte
2 goto_w 0xC8 byte1,byte2,byte3 byte4   přímý skok na adresu uloženou ve čtveřici operandů: byte1*224+byte2*216+byte3*28+byte4
3 ifeq 0x99 highbyte, lowbyte TOS=0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
4 ifne 0x9A highbyte, lowbyte TOS≠0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
5 iflt 0x9B highbyte, lowbyte TOS<0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
6 ifge 0x9C highbyte, lowbyte TOS≥0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
7 ifgt 0x9D highbyte, lowbyte TOS>0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
8 ifle 0x9E highbyte, lowbyte TOS≤0 skok na lokální adresu highbyte*256+lowbyte při splnění podmínky
9 ifnull 0xC6 highbyte, lowbyte TOS=null skok na adresu highbyte*256+lowbyte při splnění podmínky
10 ifnonnull 0xC7 highbyte, lowbyte TOS≠null skok 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í:

# Instrukce Opkód Operand 1 Operand 2 Výsledek
1 lcmp 0x94 long long 1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
2 fcmpl 0x95 float 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
3 fcmpg 0x96 float 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
4 dcmpl 0x97 double double 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
5 dcmpg 0x98 double double 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

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

# Instrukce Opkód Operandy Podmínka Operace
1 if_icmpeq 0x9F highbyte, lowbyte value1=value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
2 if_icmpne 0xA0 highbyte, lowbyte value1≠value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
3 if_icmplt 0xA1 highbyte, lowbyte value1<value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
4 if_icmpge 0xA2 highbyte, lowbyte value1≥value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
5 if_icmpgt 0xA3 highbyte, lowbyte value1>value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
6 if_icmple 0xA4 highbyte, lowbyte value1≤value2 skok 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:

# Instrukce Opkód Operandy Podmínka Operace
1 if_acmpeq 0xA5 highbyte, lowbyte value1=value2 skok na adresu highbyte*256+lowbyte při splnění podmínky
2 if_acmpne 0xA6 highbyte, lowbyte value1≠value2 skok 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í:

# Instrukce Opkód Operace
1 EQ 24 přeskok další instrukce za podmínky ((RK[B] == RK[C]) ~= A)
2 LT 25 přeskok další instrukce za podmínky ((RK[B] < RK[C]) ~= A)
3 LE 26 přeskok další instrukce za podmínky ((RK[B] <= RK[C]) ~= A)
4 TEST 27 přeskok další instrukce za podmínky not (R[A] <=> C)
     
5 JMP 23 relativní 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:

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

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
Našli jste v článku chybu?