Obsah
1. Pohled pod kapotu JVM - závěrečné porovnání JVM, Lua VM a Python VM (1/2)
2. Základní porovnání bajtkódů všech tří virtuálních strojů
5. Aritmetické a bitové posuny
6. Konverze mezi základními datovými typy
7. Instrukce pro operace s registry popř. zásobníkem operandů
8. Podmíněné a nepodmíněné skoky, větvení kódu
9. Obsah následující části seriálu
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
- Python Byte Code Instructions
https://docs.python.org/release/2.5.2/lib/bytecodes.html - Python 2.x: funkce range()
https://docs.python.org/2/library/functions.html#range - Python 2.x: typ iterátor
https://docs.python.org/2/library/stdtypes.html#iterator-types - Python break, continue and pass Statements
http://www.tutorialspoint.com/python/python_loop_control.htm - Python Bytecode: Fun With Dis
http://akaptur.github.io/blog/2013/08/14/python-bytecode-fun-with-dis/ - Python's Innards: Hello, ceval.c!
http://tech.blog.aknin.name/category/my-projects/pythons-innards/ - Byterun
https://github.com/nedbat/byterun - Python Byte Code Instructions
http://document.ihg.uni-duisburg.de/Documentation/Python/lib/node56.html - Python Byte Code Instructions
https://docs.python.org/3.2/library/dis.html#python-bytecode-instructions - dis - Python module
https://docs.python.org/2/library/dis.html - Comparison of Python virtual machines
http://polishlinux.org/apps/cli/comparison-of-python-virtual-machines/ - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators - Programming in Lua 9.1 – Coroutine Basics,
http://www.lua.org/pil/9.1.html - Wikipedia CZ: Koprogram,
http://cs.wikipedia.org/wiki/Koprogram - Wikipedia EN: Coroutine,
http://en.wikipedia.org/wiki/Coroutine - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Programming in Lua: 6 - More about Functions
http://www.lua.org/pil/6.html - Lua Lanes,
http://kotisivu.dnainternet.net/askok/bin/lanes/ - Programming in Lua: 6.1 - Closures
http://www.lua.org/pil/6.1.html - Programming in Lua: 9.1 - Coroutine Basics
http://www.lua.org/pil/9.1.html - Programming in Lua: Numeric for
http://www.lua.org/pil/4.3.4.html - Programming in Lua: break and return
http://www.lua.org/pil/4.4.html - Programming in Lua: Tables
http://www.lua.org/pil/2.5.html - Programming in Lua: Table Constructors
http://www.lua.org/pil/3.6.html - Programovací jazyk Lua
http://palmknihy.cz/web/kniha/programovaci-jazyk-lua-12651.htm - Lua: Tables Tutorial
http://lua-users.org/wiki/TablesTutorial - Lua: Control Structure Tutorial
http://lua-users.org/wiki/ControlStructureTutorial - Lua Types Tutorial
http://lua-users.org/wiki/LuaTypesTutorial - Goto Statement in Lua
http://lua-users.org/wiki/GotoStatement - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Lua 5.2 sources - lopcodes.h
http://www.lua.org/source/5.2/lopcodes.h.html - Lua 5.2 sources - lopcodes.c
http://www.lua.org/source/5.2/lopcodes.c.html - For-each Loop in Java
http://www.leepoint.net/notes-java/flow/loops/foreach.html - For Loop (Wikipedia)
http://en.wikipedia.org/wiki/For_loop - Heinz Rutishauser
http://en.wikipedia.org/wiki/Heinz_Rutishauser - Parrot
http://www.parrot.org/ - Parrot languages
http://www.parrot.org/languages - Parrot Primer
http://docs.parrot.org/parrot/latest/html/docs/intro.pod.html - Parrot Opcodes
http://docs.parrot.org/parrot/latest/html/ops.html - Parrot VM
http://en.wikibooks.org/wiki/Parrot_Virtual_Machine - Parrot Assembly Language
http://www.perl6.org/archive/pdd/pdd06_pasm.html - Parrot Reference: Chapter 11 - Perl 6 and Parrot Essentials
http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html - O-code
http://en.wikipedia.org/wiki/O-code_machine - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap - The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) - Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html