Hlavní navigace

Pohled pod kapotu JVM (7.část - další instrukce zpracovávané virtuálním strojem Javy)

24. 1. 2012
Doba čtení: 24 minut

Sdílet

V dnešním článku o jazyce Java budeme pokračovat v popisu instrukčního souboru zpracovávaného virtuálním strojem. Popíšeme si instrukce používané pro provádění aritmetických a bitových operací a také instrukce sloužící pro porovnání dvou hodnot. Na těchto instrukcích je mj. založeno i řízení běhu programů.

Obsah

1. Instrukce určené pro provádění aritmetických operací

2. První demonstrační příklad: použití aritmetických operací v bajtkódu

3. Speciální aritmetická operace iinc a s ní související druhý demonstrační příklad

4. Instrukce určené pro provádění bitových operací

5. Třetí demonstrační příklad: bitové operace

6. Čtvrtý demonstrační příklad: bitové posuny a booleovské operace

7. Instrukce určené pro porovnání dvou hodnot

8. Pátý demonstrační příklad: porovnávací (komparační) operace

9. Odkazy na Internetu

1. Instrukce určené pro provádění aritmetických operací

V předchozí části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje tohoto jazyka jsme si řekli základní informace o instrukčním souboru JVM. Víme již, že tento instrukční soubor byl navržen s ohledem na to, aby byl výsledný bajtkód uložený v souborech .class co nejmenší, takže instrukční sada JVM není příliš ortogonální a navíc některé instrukce obsahují přímo ve svém operačním kódu i svůj operand: konstantu či index. Operační kód každé instrukce je uložen v jednom bajtu, za nímž – podle typu instrukce – mohou následovat další bajty s konstantou, popř. s indexem ukazujícím do constant poolu. Z 256 možných operačních kódů je jich v současných verzích virtuálního stroje Javy obsazeno zhruba 250 (záleží totiž na konkrétní verzi JVM), přičemž minule jsme si popsali zhruba jednu čtvrtinu všech instrukcí. Dnes si popíšeme další množinu instrukcí: bude se z velké části jednat o instrukce pracující s daty uloženými na zásobníku operandů, s jehož funkcí jsme se podrobně seznámili v předminulé a minulé části tohoto seriálu.

Začněme popisem aritmetických instrukcí, které jsou velmi jednoduché. Jedná se většinou 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 má primitivní datové typy :-):

# Instrukce Opkód Operand 1 Operand 2 Operace Poznámka
1 iadd 0×60 int int součet oba původní operandy jsou ze zásobníku operandů odstraněny
2 ladd 0×61 long long součet -//-
3 fadd 0×62 float float součet -//-
4 dadd 0×63 double double součet -//-
5 isub 0×64 int int rozdíl -//-
6 lsub 0×65 long long rozdíl -//-
7 fsub 0×66 float float rozdíl -//-
8 dsub 0×67 double double rozdíl -//-
9 imul 0×68 int int součin -//-
10 lmul 0×69 long long součin -//-
11 fmul 0×6A float float součin -//-
12 dmul 0×6B double double součin -//-
13 idiv 0×6C int int podíl -//-
14 ldiv 0×6D long long podíl -//-
15 fdiv 0×6E float float podíl -//-
16 ddiv 0×6F double double podíl -//-
17 irem 0×70 int int zbytek po dělení -//-
18 lrem 0×71 long long zbytek po dělení -//-
19 frem 0×72 float float zbytek po dělení -//-
20 drem 0×73 double double zbytek po dělení -//-
21 ineg 0×74 int   změna znaménka původní operand je ze zásobníku operandů odstraněn
22 lneg 0×75 long   změna znaménka původní operand je ze zásobníku operandů odstraněn
23 fneg 0×76 float   změna znaménka původní operand je ze zásobníku operandů odstraněn
24 dneg 0×77 double   změna znaménka původní operand je ze zásobníku operandů odstraněn

2. První demonstrační příklad: použití aritmetických operací v bajtkódu

Pojďme si nyní ukázat, jakým způsobem překladač Javy generuje aritmetické instrukce popsané v předchozí kapitole. Zdrojový kód testovacího příkladu bude velmi jednoduchý – bude se jednat o několik statických metod s jedinou lokální proměnnou, do níž se uloží výsledek aritmetické operace. Jedinou poněkud složitější metodou bude výpočet determinantu matice o velikosti 2×2 prvky:

class test1 {
 
    static void add(int a, int b) {
        int d = a + b;
    }
 
    static void subtract1(long a, long b) {
        long c = a - b;
    }
 
    static void subtract2(long a, long b) {
        long c = a + -b;
    }
 
    static void multiply(float a, float b) {
        float c = a * b;
    }
 
    static void divide(double a, double b) {
        double c = a / b;
    }
 
    static void rem(double a, double b) {
        double c = a % b;
    }
 
    /**
     * Determinant matice 2x2 prvky
     */
    static double det(double a11, double a12, double a21, double a22) {
        return a11*a22 - a12*a21;
    }
 
}

První metoda pojmenovaná add() je v bajtkódu implementována přímočaře:

static void add(int, int);
  Code:
   0:   iload_0      // uložit první parametr metody na zásobník operandů
   1:   iload_1      // uložit druhý parametr metody na zásobník operandů
   2:   iadd         // provést operaci součtu s odstraněním obou operandů
   3:   istore_2     // výsledek operace se uloží do první lokální proměnné
   4:   return       // návrat do volající metody

Ve druhé metodě subtract1() stojí za povšimnutí především způsob indexování parametrů, které jsou typu long. Každý takový parametr (či lokální proměnná) zabere na zásobníkovém rámci dvě pozice, proto má první parametr index 0, ale druhý parametr již index 2 a první lokální proměnná (uložená ihned za druhým parametrem) index 4:

static void subtract1(long, long);
  Code:
   0:   lload_0      // uložit první parametr metody na zásobník operandů
   1:   lload_2      // uložit druhý parametr metody na zásobník operandů
   2:   lsub         // provést operaci rozdílu s odstraněním obou operandů
   3:   lstore  4    // výsledek operace se uloží do první lokální proměnné
   5:   return       // návrat do volající metody

Na příkladu metody subtract2() je patrné, že překladač vlastně neprovedl žádnou optimalizaci, takže jsou pro něj výrazy a-b a a+ -b rozdílné:

static void subtract2(long, long);
  Code:
   0:   lload_0      // uložit první parametr metody na zásobník operandů
   1:   lload_2      // uložit druhý parametr metody na zásobník operandů
   2:   lneg         // změna znaménka prvku na TOS
   3:   ladd         // provést operaci součtu s odstraněním obou operandů
   4:   lstore  4    // výsledek operace se uloží do první lokální proměnné
   6:   return       // návrat do volající metody

V případě metody multiply() by nás neměl její bajtkód ničím překvapit:

static void multiply(float, float);
  Code:
   0:   fload_0      // uložit první parametr metody na zásobník operandů
   1:   fload_1      // uložit druhý parametr metody na zásobník operandů
   2:   fmul         // provést operaci součinu s odstraněním obou operandů
   3:   fstore_2     // výsledek operace se uloží do první lokální proměnné
   4:   return       // návrat do volající metody

Ani v případě metody divide() by nás nic nemělo překvapit, tedy až na fakt, že se parametry typu double zpracovávají podobně, jako parametry typu long:

static void divide(double, double);
  Code:
   0:   dload_0      // uložit první parametr metody na zásobník operandů
   1:   dload_2      // uložit druhý parametr metody na zásobník operandů
   2:   ddiv         // provést operaci podílu s odstraněním obou operandů
   3:   dstore  4    // výsledek operace se uloží do první lokální proměnné
   5:   return       // návrat do volající metody

Výpočet zbytku po dělení je prakticky stejný jako samotné dělení, až na jednu rozdílnou instrukci:

static void rem(double, double);
  Code:
   0:   dload_0      // uložit první parametr metody na zásobník operandů
   1:   dload_2      // uložit druhý parametr metody na zásobník operandů
   2:   drem         // provést operaci výpočtu zbytku s odstraněním obou operandů
   3:   dstore  4    // výsledek operace se uloží do první lokální proměnné
   5:   return       // návrat do volající metody

Z posledního výpisu je patrné, jakým způsobem překladač Javy dokáže využít zásobník v případě, že se má provést složitější výpočet s operátory majícími různou prioritu. Povšimněte si, že se mezivýsledek prvního součinu prostě nechá uložený na zásobníku operandů, vypočte se druhý součin a posléze se (bez využití mezipaměti) oba výsledky od sebe odečtou:

static double det(double, double, double, double);
  Code:
   0:   dload_0      // uložit první parametr metody na zásobník operandů
   1:   dload   6    // uložit třetí parametr metody na zásobník operandů
   3:   dmul         // vypočítat součin (oba operandy se odstraní)
 
   // zásobník operandů nyní obsahuje pouze TOS s výsledkem součinu
 
   4:   dload_2      // uložit druhý parametr metody na zásobník operandů
   5:   dload   4    // uložit čtvrtý parametr metody na zásobník operandů
   7:   dmul         // vypočítat součin (oba operandy se odstraní)
 
   // zásobník operandů nyní obsahuje dva prvky: výsledky obou součinů
 
   8:   dsub         // vypočítat rozdíl obou prvků
 
   // zásobník operandů nyní obsahuje pouze TOS s rozdílem
 
   9:   dreturn      // návrat do volající metody s výsledkem double

3. Speciální aritmetická operace iinc a s ní související druhý demonstrační příklad

Ve skutečnosti není seznam aritmetických instrukcí vypsaný v první kapitole zcela úplný, protože v instrukčním souboru JVM můžeme najít ještě jednu poněkud specializovanou aritmetickou instrukci nazvanou iinc. Tato instrukce slouží pro zvýšení hodnoty lokální proměnné (či parametru metody) typu int o hodnotu konstanty uložené za operačním kódem (typicky se jedná o jedničku, například při implementaci některých počítaných smyček). Vzhledem k tomu, že se tato hodnota mění přímo v oblasti lokálních proměnných, je instrukce iinc jedinou aritmetickou instrukcí, která nepracuje s daty uloženými na zásobníku operandů. Instrukce iinc existuje ve dvou variantách: základní a „široké“. V základní variantě je za operačním kódem uložen jeden bajt představující index do oblasti lokálních proměnných (kde jsou i parametry metody). Za tímto bajtem je uložena osmibitová konstanta (se znaménkem), o níž se má hodnota lokální proměnné zvýšit.

V „široké“ variantě je index do oblasti lokálních proměnných šestnáctibitový a i konstanta pro přičtení je šestnáctibitová, tj. představuje hodnotu ležící v rozsahu –32786 až 32767. Operační kódy jsou vždy stejné, liší se jen tím, zda se před instrukcí iinc nachází prefix wide:

# Instrukce Opkód Data 1 Data 2 Operace
1 iinc 0×84 8bit index 8bit konstanta přičtení osmibitové konstanty k proměnné
2 iinc_w 0×c4+0×84 16bit index 16bit konstanta přičtení šestnáctibitové konstanty k proměnné

Podívejme se na jednoduchý demonstrační příklad, v němž jsou navíc ukázány některé limity instrukce iinc, zejména její omezení na použití 16bitových konstant:

class test2 {
 
    static void inc(int a) {
        a++;
        a+=10;
        a--;
        a+=1000;
        a-=1000;
        a+=100000;
        a-=100000;
    }
 
}

Statická metoda inc() se přeloží následovně (poznámky jsou samozřejmě dopsány ručně):

static void inc(int);
  Code:
   // příkaz a++:
   0:   iinc    0, 1       // 0=index proměnné, 1=konstanta pro přičtení
 
   // příkaz a+=10;
   3:   iinc    0, 10
 
   // příkaz a--:
   6:   iinc    0, -1
 
   // příkaz a+=1000; (konstanta je větší než 127)
   9:   iinc_w 0, 1000
 
   // příkaz a-=1000; (konstanta je menší než -128)
   15:  iinc_w 0, -1000
 
   // příkaz a+=100000; (konstanta je větší než 32767, nelze použít iinc)
   21:  iload_0
   22:  ldc     #2; //int 100000 uložený na constant poolu
   24:  iadd
   25:  istore_0
 
   // příkaz a-=100000; (konstanta je větší než 32767, nelze použít iinc)
   26:  iload_0
   27:  ldc     #2; //int 100000 uložený na constant poolu
   29:  isub
   30:  istore_0
 
   31:  return      // návrat z metody
}

I přes omezení na použití osmibitových a šestnáctibitových konstant je instrukce iinc velmi užitečná, protože má ve své standardní variantě délku pouze tři bajty a v široké variantě délku šesti bajtů (i s prefixem), což je obecně (ovšem ne vždy!) méně, než sekvence operací iload+ldc+iad­d/isub+istore.

4. Instrukce určené pro provádění bitových operací

Druhou skupinou instrukcí virtuálního stroje Javy, s níž se v dnešním článku seznámíme, jsou instrukce, pomocí nichž se implementují téměř všechny základní bitové a logické operace. Nejprve se zmíníme o bitových operacích. 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 0×ffffffff (popř. obdobné konstanty typu long). Jak uvidíme dále, jsou tyto instrukce 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.

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 0×1f v případě typu int či 0×3f 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 0×78 int int aritmetický/bitový posun doleva oba původní operandy ze zásobníku operandů jsou odstraněny
2 lshl 0×79 long int aritmetický/bitový posun doleva -//-
3 ishr 0×7A int int aritmetický posun doprava -//-
4 lshr 0×7B long int aritmetický posun doprava -//-
5 iushr 0×7C int int bitový posun doprava -//-
6 lushr 0×7D long int bitový posun doprava -//-
7 iand 0×7E int int bitový součin -//-
8 land 0×7F long long bitový součin -//-
9 ior 0×80 int int bitový součet -//-
10 lor 0×81 long long bitový součet -//-
11 ixor 0×82 int int nonekvivalence -//-
12 lxor 0×83 long long nonekvivalence -//-

5. Třetí demonstrační příklad: bitové operace

Na následujícím testovacím příkladu si ukážeme, jakým způsobem překladač Javy dokáže přeložit základní bitové operace prováděné nad proměnnými typu int a long. Příklad je velmi jednoduchý, protože obsahuje pouze osmici statických metod, z nichž každá obsahuje pouze jednu bitovou operaci. Povšimněte si především toho, jakým způsobem je přeložena unární operace bitové negace: v případě proměnné typu int je pro uložení hodnoty 0×ffffffff na zásobník operandů použita jednobajtová instrukce iconst_m1 (uložení konstanty –1 na zásobník), ovšem u proměnné typu long se již musela použít konstanta uložená na constant poolu:

class test3 {
 
    static void bitOpAnd(int a, int b) {
        int c = a & b;
    }
 
    static void bitOpOr(int a, int b) {
        int c = a | b;
    }
 
    static void bitOpXor(int a, int b) {
        int c = a ^ b;
    }
 
    static void bitOpNegate(int a) {
        int b = ~a;
    }
 
    static void bitOpAnd(long a, long b) {
        long c = a & b;
    }
 
    static void bitOpOr(long a, long b) {
        long c = a | b;
    }
 
    static void bitOpXor(long a, long b) {
        long c = a ^ b;
    }
 
    static void bitOpNegate(long a) {
        long b = ~a;
    }
 
}

Dissasemblovaný bajtkód třetího demonstračního příkladu vypadá následovně:

static void bitOpAnd(int, int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   iand          // provedení operace bitového součinu
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void bitOpOr(int, int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   ior           // provedení operace bitového součtu
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void bitOpXor(int, int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   ixor          // provedení operace nonekvivalence
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void bitOpNegate(int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iconst_m1     // načtení konstanty 0xffffffff
   2:   ixor          // a^0xffffffff == ~a
   3:   istore_1      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody

U proměnných typu long je opět jejich indexace rozdílná:

static void bitOpAnd(long, long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   land          // provedení operace bitového součinu
   3:   lstore  4     // uložení výsledku do lokální proměnné
   5:   return        // návrat z metody
static void bitOpOr(long, long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   lor           // provedení operace bitového součtu
   3:   lstore  4     // uložení výsledku do lokální proměnné
   5:   return        // návrat z metody
static void bitOpXor(long, long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   lxor          // provedení operace nonekvivalence
   3:   lstore  4     // uložení výsledku do lokální proměnné
   5:   return        // návrat z metody
static void bitOpNegate(long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   ldc2_w  #2;   // 0xffffffffffffffff
   4:   lxor          // a^0xffffffffffffffff == ~a
   5:   lstore_2      // uložení výsledku do lokální proměnné
   6:   return        // návrat z metody

6. Čtvrtý demonstrační příklad: bitové posuny a booleovské operace

Ve čtvrtém demonstračním příkladu si ukážeme použití bitových instrukcí pro implementaci booleovských operací. Taktéž si ukážeme, jak se přeloží výpočty, v nichž se vyskytují operandy pro provádění aritmetického či bitového posunu doleva a doprava. Zdrojový kód demonstračního příkladu je následující:

class test4 {
 
    static void boolOpAnd(boolean a, boolean b) {
        boolean c = a & b;
    }
 
    static void boolOpOr(boolean a, boolean b) {
        boolean c = a | b;
    }
 
    static void boolOpXor(boolean a, boolean b) {
        boolean c = a ^ b;
    }
 
    static void boolOpNot(boolean a) {
        boolean c = !a;
    }
 
    static void asl(int x, int shift) {
        int y = x << shift;
    }
 
    static void asr(int x, int shift) {
        int y = x >> shift;
    }
 
    static void lsr(int x, int shift) {
        int y = x >>> shift;
    }
 
    static void asl(long x, long shift) {
        long y = x << shift;
    }
 
    static void asr(long x, long shift) {
        long y = x >> shift;
    }
 
    static void lsr(long x, long shift) {
        long y = x >>> shift;
    }
 
}

První tři metody s booleovskými operacemi jsou přeloženy takovým způsobem, že se namísto hodnot typu boolean používají hodnoty typu int, přičemž platí identity false=0, true=1 (což mimochodem není přímo v Javě povoleno):

static void boolOpAnd(boolean, boolean);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   iand          // provedení operace bitového součinu (a tím pádem i booleovského součinu)
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void boolOpOr(boolean, boolean);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   ior           // provedení operace bitového součtu
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void boolOpXor(boolean, boolean);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   ixor          // provedení operace nonekvivalence
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody

Zdánlivě velmi jednoduchá operace logické negace je však přeložena pomocí podmíněného skoku(!), protože výsledkem musí být hodnota 0 nebo 1 a nikoli 0 nebo –1, jak by tomu bylo při použití negace bit po bitu (zkuste se zamyslet nad tím, proč není použita operace xor 0×01):

static void boolOpNot(boolean);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   ifne    8     // podmíněný skok (probereme v další části)
   4:   iconst_1      // jeden z výsledků - konstanta 1
   5:   goto    9     // nepodmíněný skok
   8:   iconst_0      // jeden z výsledků - konstanta 1
   9:   istore_1      // uložení výsledku do lokální proměnné
   10:  return        // návrat z metody

Bitové a aritmetické posuny jsou přeloženy přímočaře (alespoň v případě použití proměnných typu int):

static void asl(int, int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   ishl          // aritmetická/bitová operace posunu doleva
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void asr(int, int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   ishr          // aritmetická operace posunu doprava
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody
static void lsr(int, int);
  Code:
   0:   iload_0       // načtení prvního parametru metody
   1:   iload_1       // načtení druhého parametru metody
   2:   iushr         // bitová operace posunu doprava
   3:   istore_2      // uložení výsledku do lokální proměnné
   4:   return        // návrat z metody

V případě, že je druhý operand posunu typu long, je tento operand automaticky (a bez varování) nejdříve převeden na typ int pomocí instrukce l2i, kterou jsme si již popsali v předchozí části tohoto seriálu:

static void asl(long, long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   l2i           // převod druhého parametru na typ int
   3:   lshl          // aritmetická/bitová operace posunu doleva
   4:   lstore  4     // uložení výsledku do lokální proměnné
   6:   return        // návrat z metody
static void asr(long, long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   l2i           // převod druhého parametru na typ int
   3:   lshr          // aritmetická operace posunu doprava
   4:   lstore  4     // uložení výsledku do lokální proměnné
   6:   return        // návrat z metody
static void lsr(long, long);
  Code:
   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   l2i           // převod druhého parametru na typ int
   3:   lushr         // bitová operace posunu doprava
   4:   lstore  4     // uložení výsledku do lokální proměnné
   6:   return        // návrat z metody

7. Instrukce určené pro porovnání dvou hodnot

Do třetí skupiny instrukcí, s nimiž se dnes seznámíme, patří pouze pět instrukcí, které slouží k porovnání dvojice numerických hodnot a jejichž výsledkem je hodnota typu int 0, 1, popř.  –1. Porovnávané numerické hodnoty mohou být typu long, float či double, nikoli však (možná poněkud překvapivě) typu int. To ve skutečnosti ani není zapotřebí, protože porovnávací instrukce ve většině případů slouží pouze k přípravě na podmíněný skok. Instrukcemi podmíněných skoků se budeme zabývat v další části tohoto seriálu, zde si jen řekněme, že existuje šestice podmíněných skoků založených na porovnání hodnoty typu int s nulou (tj. test na nulovost, nenulovost, kladnost, zápornost, popř. test na rozumnou kombinaci těchto vlastností). V následující tabulce je vypsáno všech pět porovnávacích instrukcí. Povšimněte si, že instrukce končící na „l“ se od instrukcí končících na „g“ liší ve způsobu zpracování speciální hodnoty NaN (not-a-number):

# Instrukce Opkód Operand 1 Operand 2 Výsledek Poznámka
1 lcmp 0×94 long long 1 když operand 1 > operand 2
0 když operand 1 == operand 2
-1 když operand 1 < operand 2
2 fcmpl 0×95 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 0×96 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 0×97 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 0×98 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

Jen na okraj si připomeňme, že v Javě při porovnávání speciální hodnoty NaN s jakoukoli jinou hodnotou vždy dostaneme výsledek false (a to i v případě, že je druhou hodnotou taktéž NaN):

float a = Float.NaN;
float b = Float.NaN;
 
System.out.println(a<b);
System.out.println(a>b);
System.out.println(a==b);
System.out.println(a<0);
System.out.println(a>0);
System.out.println(a==0);

Výsledkem je ve všech třech případech stejný text:

false
false
false
false
false
false

8. Pátý demonstrační příklad: porovnávací (komparační) operace

V posledním demonstračním příkladu si ukážeme použití porovnávacích operací. Tento příklad se bude poněkud lišit od všech předchozích demonstračních příkladů v tom ohledu, že v něm budou použity instrukce, jejichž význam si podrobněji popíšeme až v následující části tohoto seriálu. Zdrojový kód je jednoduchý – na základě porovnání dvou čísel typu long je nastavena hodnota šesti lokálních proměnných typu boolean. I takto jednoduché výpočty se však kupodivu přeloží pomocí podmíněných skoků:

class test4 {
 
    static void compare(long a, long b) {
        boolean eq = a == b;
        boolean ne = a != b;
        boolean lt = a < b;
        boolean le = a <= b;
        boolean gt = a > b;
        boolean ge = a >= b;
    }
 
}

Následuje okomentovaný bajtkód metody compare():

Instrukce provádějící příkaz: boolean eq = a == b;

   0:   lload_0       // načtení prvního parametru metody
   1:   lload_2       // načtení druhého parametru metody
   2:   lcmp          // porovnání obou hodnot s výsledkem 0, 1 nebo -1
   3:   ifne    10    // při opačné podmínce skok s výsledkem 0
   6:   iconst_1      // podmíněný skok nebyl proveden: výsledek = 1
   7:   goto    11    // nepodmíněný skok
   10:  iconst_0      // sem je proveden podmíněný skok z instrukce #3
   11:  istore  4     // uložení výsledku do lokální proměnné

Instrukce provádějící příkaz: boolean ne = a != b;

   13:  lload_0       // načtení prvního parametru metody
   14:  lload_2       // načtení druhého parametru metody
   15:  lcmp          // porovnání obou hodnot s výsledkem 0, 1 nebo -1
   16:  ifeq    23    // při opačné podmínce skok s výsledkem 0
   19:  iconst_1      // podmíněný skok nebyl proveden: výsledek = 1
   20:  goto    24    // nepodmíněný skok
   23:  iconst_0      // sem je proveden podmíněný skok z instrukce #16
   24:  istore  5     // uložení výsledku do lokální proměnné

Instrukce provádějící příkaz: boolean lt = a < b;

   26:  lload_0       // načtení prvního parametru metody
   27:  lload_2       // načtení druhého parametru metody
   28:  lcmp          // porovnání obou hodnot s výsledkem 0, 1 nebo -1
   29:  ifge    36    // při opačné podmínce skok s výsledkem 0
   32:  iconst_1      // podmíněný skok nebyl proveden: výsledek = 1
   33:  goto    37    // nepodmíněný skok
   36:  iconst_0      // sem je proveden podmíněný skok z instrukce #29
   37:  istore  6     // uložení výsledku do lokální proměnné

Instrukce provádějící příkaz: boolean le = a <= b;

   39:  lload_0       // načtení prvního parametru metody
   40:  lload_2       // načtení druhého parametru metody
   41:  lcmp          // porovnání obou hodnot s výsledkem 0, 1 nebo -1
   42:  ifgt    49    // při opačné podmínce skok s výsledkem 0
   45:  iconst_1      // podmíněný skok nebyl proveden: výsledek = 1
   46:  goto    50    // nepodmíněný skok
   49:  iconst_0      // sem je proveden podmíněný skok z instrukce #42
   50:  istore  7     // uložení výsledku do lokální proměnné

Instrukce provádějící příkaz: boolean gt = a > b;

   52:  lload_0       // načtení prvního parametru metody
   53:  lload_2       // načtení druhého parametru metody
   54:  lcmp          // porovnání obou hodnot s výsledkem 0, 1 nebo -1
   55:  ifle    62    // při opačné podmínce skok s výsledkem 0
   58:  iconst_1      // podmíněný skok nebyl proveden: výsledek = 1
   59:  goto    63    // nepodmíněný skok
   62:  iconst_0      // sem je proveden podmíněný skok z instrukce #55
   63:  istore  8     // uložení výsledku do lokální proměnné

Instrukce provádějící příkaz: boolean ge = a >= b;

   65:  lload_0       // načtení prvního parametru metody
   66:  lload_2       // načtení druhého parametru metody
   67:  lcmp          // porovnání obou hodnot s výsledkem 0, 1 nebo -1
   68:  iflt    75    // při opačné podmínce skok s výsledkem 0
   71:  iconst_1      // podmíněný skok nebyl proveden: výsledek = 1
   72:  goto    76    // nepodmíněný skok
   75:  iconst_0      // sem je proveden podmíněný skok z instrukce #68
   76:  istore  9     // uložení výsledku do lokální proměnné

Návrat z metody:

   78:  return

9. Odkazy na Internetu

  1. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mo­bilefish.com/tu­torials/java/ja­va_quickguide_jvm_in­struction_set­.html
  2. The JVM Instruction Set
    http://mpdebo­er.home.xs4all­.nl/scriptie/no­de14.html
  3. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  4. Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
    http://www.ro­ot.cz/clanky/vy­uziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/
  5. Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
    http://www.ro­ot.cz/clanky/jam­vm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/
  6. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/VMSp­ecTOC.doc.html
  7. The class File Format
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/Clas­sFile.doc.html
  8. javap – The Java Class File Disassembler
    http://docs.o­racle.com/java­se/1.4.2/docs/to­oldocs/window­s/javap.html
  9. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.di­e.net/man/1/j­avap-java-1.6.0-openjdk
  10. Using javap
    http://www.ide­velopment.info/da­ta/Programmin­g/java/miscella­neous_java/Usin­g_javap.html
  11. Examine class files with the javap command
    http://www.techre­public.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  12. BCEL Home page
    http://common­s.apache.org/bcel/
  13. BCEL Manual
    http://common­s.apache.org/bcel/ma­nual.html
  14. Byte Code Engineering Library (Wikipedia)
    http://en.wiki­pedia.org/wiki/BCEL
  15. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ib­m.com/developer­works/java/li­brary/j-dyn0414/
  16. Bytecode Engineering
    http://book.chi­naunix.net/spe­cial/ebook/Co­re_Java2_Volu­me2AF/0131118269/ch13lev­1sec6.html
  17. BCEL Tutorial
    http://www.smfsup­port.com/suppor­t/java/bcel-tutorial!/
  18. ASM Home page
    http://asm.ow2­.org/
  19. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2­.org/users.html
  20. ObjectWeb ASM (Wikipedia)
    http://en.wiki­pedia.org/wiki/Ob­jectWeb_ASM
  21. Java Bytecode BCEL vs ASM
    http://james.o­negoodcookie.com/2005/10­/26/java-bytecode-bcel-vs-asm/
  22. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2­.org/eclipse/in­dex.html
  23. aspectj (Eclipse)
    http://www.eclip­se.org/aspectj/
  24. Aspect-oriented programming (Wikipedia)
    http://en.wiki­pedia.org/wiki/As­pect_oriented_pro­gramming
  25. AspectJ (Wikipedia)
    http://en.wiki­pedia.org/wiki/As­pectJ
  26. EMMA: a free Java code coverage tool
    http://emma.sou­rceforge.net/
  27. Cobertura
    http://cobertu­ra.sourceforge­.net/
  28. FindBugs
    http://findbug­s.sourceforge­.net/
  29. GNU Classpath
    www.gnu.org/s/clas­spath/
  30. Java VMs Compared
    http://bugblog­ger.com/java-vms-compared-160/
  31. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp­.org/en/jsr/de­tail?id=223
  32. Scripting for the Java Platform
    http://java.sun­.com/developer/techni­calArticles/J2SE/Des­ktop/scriptin­g/
  33. Scripting for the Java Platform (Wikipedia)
    http://en.wiki­pedia.org/wiki/Scrip­ting_for_the_Ja­va_Platform
  34. Java Community Process
    http://en.wiki­pedia.org/wiki/Ja­va_Specificati­on_Request
  35. Java HotSpot VM Options
    http://www.ora­cle.com/technet­work/java/java­se/tech/vmopti­ons-jsp-140102.html
  36. Great Computer Language Shootout
    http://c2.com/cgi/wi­ki?GreatCompu­terLanguageSho­otout
  37. Java performance
    http://en.wiki­pedia.org/wiki/Ja­va_performance
  38. Trying the prototype
    http://mail.o­penjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  39. Better closures (for Java)
    http://blogs.sun­.com/jrose/en­try/better_clo­sures
  40. Lambdas in Java: An In-Depth Analysis
    http://www.in­foq.com/articles/lam­bdas-java-analysis
  41. Class ReflectiveOpe­rationExcepti­on
    http://downlo­ad.java.net/jdk7/doc­s/api/java/lan­g/ReflectiveO­perationExcep­tion.html
  42. Proposal: Indexing access syntax for Lists and Maps
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  43. Proposal: Elvis and Other Null-Safe Operators
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  44. Java 7 : Oracle pushes a first version of closures
    http://www.bap­tiste-wicht.com/2010/05­/oracle-pushes-a-first-version-of-closures/
  45. Groovy: An agile dynamic language for the Java Platform
    http://groovy­.codehaus.org/O­perators
  46. Better Strategies for Null Handling in Java
    http://www.sli­deshare.net/Step­han.Schmidt/bet­ter-strategies-for-null-handling-in-java
  47. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  48. Java Virtual Machine
    http://en.wiki­pedia.org/wiki/Ja­va_virtual_machi­ne
  49. ==, .equals(), compareTo(), and compare()
    http://leepoin­t.net/notes-java/data/expres­sions/22compa­reobjects.html
  50. New JDK7 features
    http://openjdk­.java.net/pro­jects/jdk7/fe­atures/
  51. Project Coin: Bringing it to a Close(able)
    http://blogs.sun­.com/darcy/en­try/project_co­in_bring_close
  52. CloseableFinder source code
    http://blogs.sun­.com/darcy/re­source/Projec­tCoin/Closeable­Finder.java
  53. Joe Darcy blog about JDK
    http://blogs.sun­.com/darcy
  54. Java 7 – more dynamics
    http://www.bap­tiste-wicht.com/2010/04­/java-7-more-dynamics/
  55. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun­.com/developer/techni­calArticles/Dyn­TypeLang/index­.html

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.