Pohled pod kapotu JVM (8.část - instrukce určené pro řízení běhu programu)

31. 1. 2012
Doba čtení: 23 minut

Sdílet

V dnešním článku o jazyce Java i JVM se již potřetí vrátíme k instrukčnímu souboru zpracovávanému virtuálním strojem Javy. Popíšeme si instrukce sloužící k řízení běhu programu. Jedná se o instrukce pro ukončení metody s předáním návratové hodnoty, nepodmíněné a podmíněné skoky a taktéž o instrukce pro rozvětvení.

Obsah

1. Instrukce sloužící pro ukončení metody s předáním návratové hodnoty této metody

2. První demonstrační příklad – návrat z metody s předáním návratové hodnoty

3. Instrukce nepodmíněného skoku + druhý demonstrační příklad

4. Test hodnoty jednoho operandu s podmíněným skokem v případě splnění podmínky

5. Třetí demonstrační příklad – použití podmíněného skoku testujícího jeden operand

6. Podmíněné skoky řízené hodnotou reference + čtvrtý demonstrační příklad

7. Porovnání dvou operandů s podmíněným skokem

8. Pátý demonstrační příklad – porovnání dvou operandů s podmíněným sko­kem

9. Odkazy na Internetu

1. Instrukce sloužící pro ukončení metody s předáním návratové hodnoty této metody

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji tohoto jazyka si popíšeme další skupinu instrukcí používaných v bajtkódu. Zatímco minule a předminule jsme se zabývali především instrukcemi sloužícími pro přesuny hodnot a taktéž instrukcemi pro provádění aritmetických a bitových operací, dnes se zaměříme na instrukce sloužící pro řízení běhu programu. Jedná se především o instrukce, pomocí nichž se ukončuje aktivní metoda společně se zajištěním návratové hodnoty, která je předána volající metodě. Další množinou řídicích instrukcí jsou nepodmíněné a podmíněné skoky, které jsou navíc doplněny i dvojicí poměrně komplexních instrukcí nazvaných tableswitch a lookupswitch. Tato dvojice instrukcí se používá pro implementaci rozvětvení, jenž je v programovacím jazyku Java zapisováno pomocí konstrukce switch (v Javě 7 je však situace poněkud složitější, protože je v ní umožněn i rozeskok na základě porovnávání řetězce s řetězcovými literály – konstantami).

Popišme si nejdříve instrukce, které slouží pro ukončení metody s případným předáním návratové hodnoty volající metodě (caller). Po provedení těchto instrukcí dojde ke zrušení celého zásobníkového rámce metody, z níž se vyskakuje, a řízení se předá volající metodě. Připomeňme si, že volající metoda si může návratovou hodnotu vyzvednout z vrcholu svého zásobníku operandů. Použití dále vypsaných instrukcí typu *return je jedinou možností, jak může volaná metoda modifikovat obsah zásobníku operandů metody volající – v ostatních případech jsou totiž zásobníky operandů (i datová oblast) obou metod od sebe izolovány, což přispívá jak k větší bezpečnosti, tak i k oddělení jednotlivých částí kódu. To samozřejmě zjednodušuje práci just-in-time překladače při provádění optimalizací. Ukončení metody s uložením návratové hodnoty zabezpečuje šestice instrukcí *return, která je vypsaná v následující tabulce:

# Instrukce Opkód Datový typ na TOS Operace
1 ireturn 0×AC int získání návratové hodnoty typu int z TOS zásobníku operandů + návrat z metody
2 lreturn 0×AD long získání návratové hodnoty typu long z TOS zásobníku operandů + návrat z metody
3 freturn 0×AE float získání návratové hodnoty typu float z TOS zásobníku operandů + návrat z metody
4 dreturn 0×AF double získání návratové hodnoty typu double z TOS zásobníku operandů + návrat z metody
5 areturn 0×B0 reference získání návratové hodnoty typu reference na objekt z TOS zásobníku operandů + návrat z metody
6 return 0×B1 × pouze návrat z metody, žádná hodnota se nevrací

Virtuální stroj jazyka Java samozřejmě kontroluje, zda se na zásobníku operandů nachází operand daného typu. Tyto kontroly jsou prováděny i u dalších instrukcí, jimiž se budeme zabývat v následujících kapitolách. V případě, že je bajtkód porušen, není vůbec dovoleno ho spustit. Podívejte se ostatně sami, co se stane, když se v hexa editoru zamění kód instrukce ireturn (0×AC) za freturn (0×AE):

$ java Test1
Exception in thread "main" java.lang.VerifyError: (class: Test1, method: x signature: ()I) Expecting to find float on stack

Verifikátor bajtkódu v tomto případě velmi rychle zjistil, že signatura metody uložená v constant poolu neodpovídá obsahu bajtkódu. V signatuře je totiž uvedeno ()I, tj. má se jednat o metodu bez parametrů vracející hodnotu typu int, ale v bajtkódu je nalezen operační kód instrukce freturn (metoda tedy ve skutečnosti vrací hodnotu typu float).

2. První demonstrační příklad – návrat z metody s předáním návratové hodnoty

Podobně jako tomu bylo i v předchozích částech tohoto seriálu, i dnes si ukážeme způsob použití většiny popsaných instrukcí na jednoduchých příkladech. První demonstrační příklad obsahuje několik statických metod, které vrací hodnoty různých typů s využitím konstrukce return xxx;, popř. pouze return;. Samozřejmě platí, že metoda může být v některých případech ukončena i bez explicitního uvedení konstrukce return, přeložena však bude stejným způsobem, jakoby se return použil:

class Test1 {
    static void returnVoid() {
        return;
    }
 
    static byte returnByte() {
        return -1;
    }
 
    static char returnChar() {
        return 'a';
    }
 
    static short returnShort() {
        return 0;
    }
 
    static int returnInt() {
        return 1;
    }
 
    static long returnLong() {
        return 2L;
    }
 
    static float returnFloat() {
        return 3.0f;
    }
 
    static double returnDouble() {
        return 4.0;
    }
 
    static Object returnReference() {
        return null;
    }
 
}

Jednotlivé metody deklarované v demonstračním příkladu se přeloží způsobem uvedeným v následujících odstavcích:

Metoda s návratovou hodnotou typu void používá instrukci return:

static void returnVoid();
  Code:
   0:   return       // prosté ukončení metody bez předání návratové hodnoty

Metoda s návratovou hodnotou typu byte ve skutečnosti pracuje s návratovou hodnotou typu int (opět zde můžeme vidět, že byte není v kontextu bajtkódu zcela plnohodnotným datovým typem):

static byte returnByte();
  Code:
   0:   iconst_m1    // instrukce s operandem (konstantou) uloženou přímo v bajtkódu
   1:   ireturn      // ukončení metody s předáním návratové hodnoty typu int

Totéž platí pro metodu vracející hodnotu typu char (ve skutečnosti je použit typ int):

static char returnChar();
  Code:
   0:   bipush  97   // uložení ASCII kódu znaku 'a' na zásobník operandů
   2:   ireturn      // ukončení metody s předáním návratové hodnoty typu int

A samozřejmě i pro metodu vracející hodnotu typu short (ve skutečnosti je opět použit typ int):

static short returnShort();
  Code:
   0:   iconst_0     // instrukce s operandem (konstantou) uloženou přímo v bajtkódu
   1:   ireturn      // ukončení metody s předáním návratové hodnoty typu int

Návrat hodnoty typu int se již obejde bez větších překvapení:

static int returnInt();
  Code:
   0:   iconst_1     // instrukce s operandem uloženým přímo v bajtkódu
   1:   ireturn      // ukončení metody s předáním návratové hodnoty typu int

Návrat hodnoty typu long:

static long returnLong();
  Code:
   0:   ldc2_w  #2;  // long 2l - konstanta uložená v constant poolu
   3:   lreturn      // ukončení metody s předáním návratové hodnoty typu long

Instrukce freturn je využita v případě návratové hodnoty typu float:

static float returnFloat();
  Code:
   0:   ldc     #4;  // float 3.0f - konstanta uložená v constant poolu
   2:   freturn      // ukončení metody s předáním návratové hodnoty typu float

Instrukce dreturn je využita v případě návratové hodnoty typu double:

static double returnDouble();
  Code:
   0:   ldc2_w  #5;  // double 4.0d - konstanta uložená v constant poolu
   3:   dreturn      // ukončení metody s předáním návratové hodnoty typu double

Pro vrácení reference, neboli libovolného objektu, se používá instrukce areturn:

static java.lang.Object returnReference();
  Code:
   0:   aconst_null  // instrukce s operandem uloženým přímo v bajtkódu
   1:   areturn      // ukončení metody s předáním návratové hodnoty typu reference

3. Instrukce nepodmíněného skoku + druhý demonstrační příklad

V této kapitole se budeme zabývat pouze jedinou instrukcí. Jedná se o instrukci nepodmíněného skoku, jejíž jméno je goto. Podobně jako podmíněné skoky popsané v následujících kapitolách, má i instrukce goto několik podstatných omezení – skok lze totiž provést pouze v rámci těla jedné metody, není tedy možné skočit na libovolné místo v bajtkódu. Toto omezení bylo zavedeno ze dvou důvodů – zajišťuje se tím větší bezpečnost a taktéž se tím zjednodušuje práce JIT překladače, který při optimalizacích generovaného nativního binárního kódu může pracovat s izolovaným stavovým prostorem (má totiž jistotu, že když danou metodu celou přeloží, není nutné vyhledávat, z jakých dalších metod jsou do právě přeložené metody prováděny skoky – jednoduše to není možné). Instrukce goto existuje ve dvou variantách – „krátké“ a „dlouhé“. Tyto varianty se od sebe odlišují pouze počtem bajtů, které se v bajtkódu použijí pro uložení adresy cíle skoku. Buď je možné použít 16bitovou adresu (vyhovuje prakticky všem rozumně dlouhým metodám) nebo adresu 32bitovou (to se obecně příliš často nepoužívá, protože existují další omezení na maximální počet 65536 instrukcí v jedné metodě):

# Instrukce Opkód Operandy Popis
1 goto 0×A7 highbyte, lowbyte přímý skok na adresu uloženou v dvojici operandů: highbyte*256+low­byte
2 goto_w 0×C8 byte1,byte2,byte3 byte4 přímý skok na adresu uloženou ve čtveřici operandů: byte1*224+byte2*216+by­te3*28+byte4

Podívejme se na velmi jednoduchý demonstrační příklad s trojicí statických metod, v nichž je použita nekonečná smyčka:

class Test2 {
 
    static void loop1() {
        while (true) {
        }
    }
 
    static void loop2(int x) {
        while (true) {
            x++;
        }
    }
 
    static void loop3(float x) {
        do {
            x++;
        } while (true);
    }
 
}

Ve všech třech případech se nekonečná smyčka přeloží s využitím instrukce goto (povšimněte si, že adresa skoku je skutečně lokální):

static void loop1();
  Code:
   0:   goto    0      // nekonečná smyčka bez těla - je pouze proveden skok na tu samou instrukci
static void loop2(int);
  Code:
   0:   iinc    0, 1   // tělo nekonečné smyčky
   3:   goto    0      // skok na začátek nekonečné smyčky
static void loop3(float);
  Code:
   0:   fload_0        // začátek těla nekonečné smyčky
   1:   fconst_1
   2:   fadd
   3:   fstore_0
   4:   goto    0      // skok na začátek nekonečné smyčky

Překladač Javy však může v některých případech instrukci skoku vynechat, a to tehdy, pokud statickou analýzou zjistí, že se ve skutečnosti smyčka nikdy neprovede (resp. se provede pouze jedna její iterace). Viz též následující demonstrační příklad:

class Test3 {
    static void none(int x, int y, int z) {
        while(true) {              // vnější programová smyčka
            z++;
            while(true) {          // prostřední programová smyčka
                y++;
                while(true) {      // vnitřní programová smyčka
                   x++;
                }
            }
        }
    }
}

Při překladu tohoto zdrojového kódu se vygeneruje skok pouze pro nejvnitřnější smyčku:

static void none(int, int, int);
  Code:
   0:   iinc    2, 1     // tento příkaz zbyl z vnější smyčky: z++
   3:   iinc    1, 1     // tento příkaz zbyl z prostřední smyčky: y++
   6:   iinc    0, 1     // tělo vnitřní smyčky: x++
   9:   goto    6        // implementace vnitřní smyčky

Instrukce goto je použita i v mnoha dalších případech. S některými z nich se setkáme v navazujících kapitolách.

4. Test hodnoty jednoho operandu s podmíněným skokem v případě splnění podmínky

Nyní se již konečně dostáváme k zajímavějšímu tématu – k podmíněným skokům. V instrukčním kódu virtuálního stroje jazyka Java je k dispozici poměrně velké množství typů různých podmíněných skoků. V této kapitole si popíšeme skoky, které se provedou resp. neprovedou na základě testu hodnoty jediného operandu, který je uložen na vrcholu zásobníku operandů (TOS). Ve všech případech se přitom musí jednat o operand typu int, který je po provedení testu ze zásobníku operandů odstraněn (samozřejmě nezávisle na tom, jak test ve skutečnosti dopadl). Instrukce podmíněného skoku nejdříve na základě operačního kódu instrukce zjistí, zda je operand nulový, nenulový, větší než nula, menší než nula, větší nebo roven nule popř. naopak menší nebo roven nule. Pokud je daná podmínka splněna, je proveden skok na šestnáctibitovou lokální adresu uloženou za operačním kódem instrukce; v opačném případě se pokračuje v provádění instrukce uložené ihned za podmíněným skokem. Všech šest variant podmíněných skoků pracujících s jediným operandem typu int je vypsáno v následující tabulce:

# Instrukce Opkód Operandy Podmínka Operace
1 ifeq 0×99 highbyte, lowbyte TOS=0 skok na lokální adresu highbyte*256+low­byte při splnění podmínky
2 ifne 0×9A highbyte, lowbyte TOS≠0 skok na lokální adresu highbyte*256+low­byte při splnění podmínky
3 iflt 0×9B highbyte, lowbyte TOS<0 skok na lokální adresu highbyte*256+low­byte při splnění podmínky
4 ifge 0×9C highbyte, lowbyte TOS≥0 skok na lokální adresu highbyte*256+low­byte při splnění podmínky
5 ifgt 0×9D highbyte, lowbyte TOS>0 skok na lokální adresu highbyte*256+low­byte při splnění podmínky
6 ifle 0×9E highbyte, lowbyte TOS≤0 skok na lokální adresu highbyte*256+low­byte při splnění podmínky

Možná vás nyní napadá otázka, jaké instrukce se použijí v případě potřeby testu hodnoty proměnných typu long, float nebo double. V tomto případě 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í. Tyto instrukce již známe, protože byly uvedeny v závěrečné kapitole předchozí části tohoto seriál. Jedná se o následující pětici instrukcí:

# 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

5. Třetí demonstrační příklad – použití podmíněného skoku testujícího jeden operand

V následujícím demonstračním příkladu bude ukázáno jak použití všech šesti typů podmíněných skoků, tak i využití skoku nepodmíněného (goto), který je použit mj. i při implementaci jazykové konstrukce if-then-else:

class Test4 {
 
    static String iftest(int x) {
        if (x==0) {
            return "zero";
        }
        if (x>0) {
            return "positive";
        }
        if (x<0) {
            return "negative";
        }
        if (x>=0) {
            return "positive or zero";
        }
        if (x<=0) {
            return "negative or zero";
        }
        return null;
    }
 
    static void ifelse1(int x, int y) {
        if (x==0) {
            y++;
        }
        else {
            y--;
        }
    }
 
    static void ifelse2(int x, int y) {
        if (x>=0) {
            y++;
        }
        else {
            y--;
        }
    }
 
}

Metoda iftest() je přeložena přímočaře jako sekvence podmíněných skoků. Všimněte si, že podmíněné skoky používají podmínky, které jsou přesně opačné, než podmínky zapsané ve zdrojovém textu, což je logické, protože skoky jsou zde použity pro přeskočení bloku if, nikoli pro jeho provedení:

static java.lang.String iftest(int);
  Code:
   0:   iload_0
   1:   ifne    7      // opačná podmínka než je podmínka zapsaná ve zdrojovém kódu
   4:   ldc #2;        // String "zero"
   6:   areturn        // ukončení metody s návratovou hodnotou typu String
 
   7:   iload_0
   8:   ifle    14     // opačná podmínka než je podmínka zapsaná ve zdrojovém kódu
   11:  ldc #3;        // String "positive"
   13:  areturn        // ukončení metody s návratovou hodnotou typu String
 
   14:  iload_0
   15:  ifge    21     // opačná podmínka než je podmínka zapsaná ve zdrojovém kódu
   18:  ldc #4;        // String "negative"
   20:  areturn        // ukončení metody s návratovou hodnotou typu String
 
   21:  iload_0
   22:  iflt    28     // opačná podmínka než je podmínka zapsaná ve zdrojovém kódu
   25:  ldc #5;        // String "positive or zero"
   27:  areturn        // ukončení metody s návratovou hodnotou typu String
 
   28:  iload_0
   29:  ifgt    35     // opačná podmínka než je podmínka zapsaná ve zdrojovém kódu
   32:  ldc #6;        // String "negative or zero"
   34:  areturn        // ukončení metody s návratovou hodnotou typu String
 
   35:  aconst_null
   36:  areturn        // ukončení metody s návratovou hodnotou null

U obou zbývajících metod je použit jak podmíněný skok, tak i skok nepodmíněný pro přeskočení větve else:

static void ifelse1(int, int);
  Code:
   0:   iload_0
   1:   ifne    10         // opačná podmínka než je podmínka zapsaná ve zdrojovém textu
   4:   iinc    1, 1       // větev "if"
   7:   goto    13         // konec větve "if" s přeskočením větve "else"
   10:  iinc    1, -1      // větev "else"
   13:  return
static void ifelse2(int, int);
  Code:
   0:   iload_0
   1:   iflt    10         // opačná podmínka než je podmínka zapsaná ve zdrojovém textu
   4:   iinc    1, 1       // větev "if"
   7:   goto    13         // konec větve "if" s přeskočením větve "else"
   10:  iinc    1, -1      // větev "else"
   13:  return

Pro zajímavost se ještě podívejme, jak se změní bajtkód metody iftest v případě, že se namísto proměnné typu int použije proměnná typu float:

    static String iftest(float x) {
        if (x==0) {
            return "zero";
        }
        if (x>0) {
            return "positive";
        }
        if (x<0) {
            return "negative";
        }
        if (x>=0) {
            return "positive or zero";
        }
        if (x<=0) {
            return "negative or zero";
        }
        return null;
    }

Zde se již musí vygenerovat instrukce typu fcmp*, z čehož vyplývá i složitější a samozřejmě též delší bajtkód:

static java.lang.String iftest(float);
  Code:
   0:   fload_0
   1:   fconst_0       // porovnání bude provedeno s konstantou 0.0f
   2:   fcmpl
   3:   ifne    9
   6:   ldc     #2;    // String "zero"
   8:   areturn        // ukončení metody s návratovou hodnotou String
 
   9:   fload_0
   10:  fconst_0       // porovnání bude provedeno s konstantou 0.0f
   11:  fcmpl
   12:  ifle    18
   15:  ldc     #3;    // String "positive"
   17:  areturn        // ukončení metody s návratovou hodnotou String
 
   18:  fload_0
   19:  fconst_0       // porovnání bude provedeno s konstantou 0.0f
   20:  fcmpg
   21:  ifge    27
   24:  ldc     #4;    // String "negative"
   26:  areturn        // ukončení metody s návratovou hodnotou String
 
   27:  fload_0
   28:  fconst_0       // porovnání bude provedeno s konstantou 0.0f
   29:  fcmpl
   30:  iflt    36
   33:  ldc     #5;    // String "positive or zero"
   35:  areturn        // ukončení metody s návratovou hodnotou String
 
   36:  fload_0
   37:  fconst_0       // porovnání bude provedeno s konstantou 0.0f
   38:  fcmpg
   39:  ifgt    45
   42:  ldc     #6;    // String "negative or zero"
   44:  areturn        // ukončení metody s návratovou hodnotou String
 
   45:  aconst_null
   46:  areturn        // ukončení metody s návratovou hodnotou null

6. Podmíněné skoky řízené hodnotou reference + čtvrtý demonstrační příklad

Všechny podmíněné skoky popsané ve čtvrté kapitole jsou určeny pro práci s operandy typu int. Díky existenci instrukcí typu [lfd]cmp je navíc možné provést test na aktuální hodnotu operandů typu long, floatdouble. Jak již víme z předchozích dvou částí tohoto seriálu, není nutné, aby existovaly podobné instrukce i pro datové typy boolean, byte, char a short, protože operandy těchto typů jsou automaticky převedeny na datový typ int. Ovšem ve skutečnosti pracuje virtuální stroj Javy ještě s jedním datovým typem. Tím je reference. Parametry, lokální proměnné či operandy uložené na zásobníku operandů, které jsou datového typu reference, mohou buď obsahovat speciální hodnotu NULL nebo musí obsahovat (většinou nepřímý) ukazatel na platný objekt vytvořený na haldě (heap). Od běžných ukazatelů známých například z céčka či C++ se reference odlišují taktéž v tom ohledu, že jim není možné přímo přiřadit hodnotu a nelze s nimi provádět ukazatelovou aritmetiku (navíc je možné na 64bitových platformách používat takzvané komprimované ukazatele s délkou 32 bitů).

Vzhledem k tomu, že s referencemi není možné provádět plnohodnotnou ukazatelovou aritmetiku, není umožněn ani jejich převod na celé číslo (či naopak). Reference však lze navzájem porovnávat pomocí instrukcí, o nichž se zmíníme v navazující kapitole. Ovšem velmi často se v programovém kódu vyskytne podmínka typu object==null či naopak object!=null, kterou je vhodné nějakým optimálním způsobem přeložit do instrukcí bajtkódu. Porovnávání s konstantou null vytvořenou pomocí instrukce aconst_null a uloženou na zásobník operandů je sice samozřejmě možné, ale zbytečně složité. Z tohoto důvodu byly do bajtkódu přidány další dva podmíněné skoky, které přímo testují, zda je referenci umístěné na vrcholu zásobníku operandů (TOS) přiřazen platný ukazatel na existující objekt, nebo zda tato reference obsahuje speciální hodnotu null. Tyto dva podmíněné skoky se jmenují přímočaře ifnull a ifnonnull a jsou společně se svými operačními kódy vypsány v následující tabulce:

# Instrukce Opkód Operandy Podmínka Operace
1 ifnull 0×C6 highbyte, lowbyte TOS=null skok na adresu highbyte*256+low­byte při splnění podmínky
2 ifnonnull 0×C7 highbyte, lowbyte TOS≠null skok na adresu highbyte*256+low­byte při splnění podmínky

Ukažme si použití těchto dvou instrukcí na velmi jednoduchém demonstračním příkladu, který obsahuje pouze dvě statické metody. Jedna z metod vrací pravdivostní hodnotu true v případě, že byl metodě předán nullový objekt, druhá metoda vrací ve stejném případě hodnotu false:

class Test4 {
 
    static boolean nullp(Object object) {
        return object == null;
    }
 
    static boolean notnullp(Object object) {
        return object != null;
    }
 
}

Následuje výpis bajtkódu vygenerovaného pro obě výše vypsané metody:

static boolean nullp(java.lang.Object);
  Code:
   0:   aload_0          // načtení prvního parametru metody
   1:   ifnonnull   8    // podmínka je oproti zdrojovému kódu negována
   4:   iconst_1         // odpovídá pravdivostní hodnotě true
   5:   goto    9
   8:   iconst_0         // odpovídá pravdivostní hodnotě false
   9:   ireturn          // konec metody s uložením návratové hodnoty

Sekvence instrukcí pro druhou metodu je prakticky stejná, jako předchozí sekvence, pouze je otočena podmínka ve druhé instrukci:

static boolean notnullp(java.lang.Object);
  Code:
   0:   aload_0          // načtení prvního parametru metody
   1:   ifnull  8        // podmínka je oproti zdrojovému kódu negována
   4:   iconst_1         // odpovídá pravdivostní hodnotě true
   5:   goto    9
   8:   iconst_0         // odpovídá pravdivostní hodnotě false
   9:   ireturn          // konec metody s uložením návratové hodnoty

7. Porovnání dvou operandů s podmíněným skokem

Z teoretického hlediska by podmíněné skoky popsané v předchozích kapitolá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 0×9F highbyte, lowbyte value1=value2 skok na adresu highbyte*256+low­byte při splnění podmínky
2 if_icmpne 0×A0 highbyte, lowbyte value1≠value2 skok na adresu highbyte*256+low­byte při splnění podmínky
3 if_icmplt 0×A1 highbyte, lowbyte value1<value2 skok na adresu highbyte*256+low­byte při splnění podmínky
4 if_icmpge 0×A2 highbyte, lowbyte value1≥value2 skok na adresu highbyte*256+low­byte při splnění podmínky
5 if_icmpgt 0×A3 highbyte, lowbyte value1>value2 skok na adresu highbyte*256+low­byte při splnění podmínky
6 if_icmple 0×A4 highbyte, lowbyte value1≤value2 skok na adresu highbyte*256+low­byte 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 0×A5 highbyte, lowbyte value1=value2 skok na adresu highbyte*256+low­byte při splnění podmínky
2 if_acmpne 0×A6 highbyte, lowbyte value1≠value2 skok na adresu highbyte*256+low­byte při splnění podmínky

8. Pátý demonstrační příklad – porovnání dvou operandů s podmíněným skokem

Všechny demonstrační příklady, které jsme si až doposud ukazovali, byly co nejjednodušší; ideálně takové, že se v nich nevyskytovaly žádné neznámé instrukce. Ovšem v tomto okamžiku již známe zhruba sedmdesát procent všech instrukcí, které se v instrukčním souboru virtuálního stroje Javy vyskytují, takže si můžeme ukázat poněkud složitější příklad, v němž budou využity jak podmíněné skoky popsané v předchozí kapitole, tak i nepodmíněný skok goto, o němž jsme se zmínili v kapitole třetí. Zápis zdrojového kódu programu v jazyce Java sice nevypadá příliš složitě, ve skutečnosti se ale kvůli existenci dvou do sebe vnořených počítaných smyček a konstrukcí continue a break vygenerovaný kód poněkud „zašmodrchá“ (popravdě řečeno i JIT překladač bude mít s tímto kódem dosti práce):

bitcoin školení listopad 24

class Test5 {
 
    static void cmpInstr() {
        for (int y = 0; y < 10; y++) {
            if (y < 5) continue;
            for (int x = 0; x < 10; x++) {
                if (x == 5) break;
            }
        }
    }
 
}

Následuje výpis vygenerované sekvence instrukcí s ručně dopsanými poznámkami:

static void cmpInstr();
  Code:
   0:   iconst_0        // inicializace počitadla vnější smyčky
   1:   istore_0        // jedná se o první lokální proměnnou (s viditelností jen uvnitř smyčky)
 
   2:   iload_0         // podmínka ukončení vnější smyčky
   3:   bipush  10      // konstanta představující hodnotu počitadla, při jejímž dosažení se smyčka ukončí
   5:   if_icmpge   44  // počitadlo dosáhlo mezní hodnoty - skok ZA konec vnější smyčky
 
   8:   iload_0         // implementace podmínky "if (y < 5) continue;"
   9:   iconst_5        // konstanta, s níž je hodnota počitadla srovnávána
   10:  if_icmpge   16
   13:  goto    38      // skok ZA konec vnitřní smyčky
 
   16:  iconst_0        // inicializace počitadla vnitřní smyčky
   17:  istore_1        // jedná se o druhou lokální proměnnou (s viditelností jen uvnitř smyčky)
 
   18:  iload_1         // podmínka ukončení vnitřní smyčky
   19:  bipush  10      // konstanta představující hodnotu počitadla, při jejímž dosažení se smyčka ukončí
   21:  if_icmpge   38  // počitadlo dosáhlo mezní hodnoty - skok ZA konec vnitřní smyčky
 
   24:  iload_1         // implementace podmínky "if (x == 5) break;"
   25:  iconst_5        // konstanta, s níž je hodnota počitadla srovnávána
   26:  if_icmpne   32
   29:  goto    38      // skok ZA konec vnitřní smyčky
 
   32:  iinc    1, 1    // zvýšení počitadla vnitřní smyčky
   35:  goto    18      // další iterace vnitřní smyčky
 
   38:  iinc    0, 1    // zvýšení počitadla vnější smyčky
   41:  goto    2       // další iterace vnější smyčky
 
   44:  return

Vzhledem k tomu, že celý dnešní článek byl zaměřený především na problematiku nepodmíněných i podmíněných skoků, bude zajímavé si semigraficky zvýraznit, kolik skoků je v naší demonstrační metodě vůbec použito. Pro samotnou implementaci počítané smyčky for je nutné použít dva skoky – podmíněný skok if_icmpge, který zajišťuje test na koncovou podmínku a nepodmíněný skok goto, jenž na konci smyčky zajistí skok na její začátek. Další dvojice if_icmpge+goto je použita pro implementaci konstrukcí break a continue:

static void cmpInstr();
             Code:
              0:   iconst_0
              1:   istore_0
+-----------> 2:   iload_0
|             3:   bipush  10
|             5:   if_icmpge   44 ------------+
|             8:   iload_0                    |
|             9:   iconst_5                   |
|             10:  if_icmpge   16 ----+       |
|   +-------- 13:  goto    38         |       |
|   |         16:  iconst_0      <----+       |
|   |         17:  istore_1                   |
|   |         18:  iload_1       <............|.....
|   |         19:  bipush  10                 |    :
|   |         21:  if_icmpge   38 --------+   |    :
|   |         24:  iload_1                |   |    :
|   |         25:  iconst_5               |   |    :
|   |         26:  if_icmpne   32 ----+   |   |    :
|   |   +---- 29:  goto    38         |   |   |    :
|   |   |     32:  iinc    1, 1  <----+   |   |    :
|   |   |     35:  goto    18    .........|...|....:
|   +---\===> 38:  iinc    0, 1  <--------+   |
+------------ 41:  goto    2                  |
              44:  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

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