Pohled pod kapotu JVM – práce s uzávěry v Lua VM

19. 8. 2014
Doba čtení: 21 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Problematikou volání funkcí a metod, kterou jsme si vysvětlovali v minulých částech seriálu, se budeme zabývat i dnes. Zatím jsme si popsali zejména způsob volání statických metod, nestatických metod a konstruktorů. Dnes se budeme zabývat zajímavější oblastí – podporou uzávěrů (closures) v Lua VM.

Obsah

1. Pohled pod kapotu JVM – práce s uzávěry v Lua VM

2. Uzávěry v programovacím jazyku Lua

3. „Externí lokální proměnné“ přístupné z uzávěrů

4. Demonstrační příklad Test30.lua: vytvoření a použití uzávěru

5. Překlad demonstračního příkladu Test30.lua do bajtkódu Lua VM

6. Demonstrační příklad Test31.lua: externí lokální proměnné většího množství uzávěrů

7. Překlad demonstračního příkladu Test31.lua do bajtkódu Lua VM

8. Demonstrační příklad Test32.lua: předání parametrů volanému uzávěru

9. Překlad demonstračního příkladu Test32.lua do bajtkódu Lua VM

10. Repositář se zdrojovými kódy všech tří dnešních demonstračních příkladů

11. Odkazy na Internetu

1. Pohled pod kapotu JVM – práce s uzávěry v Lua VM

Ve funkcionálních jazycích, mezi něž programovací jazyk Lua některými svými vlastnostmi bezesporu náleží, jsou funkce chápány jako plnohodnotný datový typ. To například znamená, že funkce mohou být ukládány do globálních i lokálních proměnných (tím vlastně dojde k pojmenování původně anonymní funkce), funkce mohou být předávány do jiných funkcí jako parametry, popř. lze funkce naopak použít jako návratovou hodnotu z jiných funkcí. V programovacím jazyku Lua se taktéž velmi často setkáme s ukládáním funkcí do tabulek – tímto způsobem lze tabulky použít jako základ pro tvorbu objektů. K tomu přispívá i „syntaktický cukr“ popsaný již v předchozí části tohoto seriálu: pokud je nějaká funkce uložena do tabulky nazvané tabulka pod jménem funkce (tj. prvek tabulky nese toto jméno), je možné funkci zavolat buď pomocí příkazu tabulka.funkce() nebo tabulka:funkce(), přičemž ve druhém případě bude do funkce automaticky předán parametr self (this), podobně jako je tomu například v Javě u nestatických metod.

Ovšem podpora funkcí jde v programovacím jazyku Lua ještě nad tento popsaný rámec, protože se zde setkáme ještě s další zajímavou a mnohdy velmi užitečnou technikou. Poměrně často se totiž ve funkcionálních jazycích používají takzvané uzávěry (closures), které byly poprvé navrženy a implementovány ve známém jazyku Scheme. V programovacím jazyku Lua se uzávěry konstruují pomocí anonymní (nepojmenované) funkce vytvořené uvnitř jiné funkce, přičemž tyto anonymní funkce mají přístup k lokálním proměnným „své“ vytvářející funkce. Vytvořené anonymní funkce (resp. odkazy na ně) je možné v jazyku Lua vracet volajícímu programu pomocí příkazu return, neboť funkce jsou v tomto jazyku plnohodnotným datovým typem, se kterým lze manipulovat podobně, jako s ostatními datovými typy (čísly, asociativními poli, pravdivostními hodnotami atd) – viz též předchozí odstavec.

2. Uzávěry v programovacím jazyku Lua

Zajímavá a přitom velmi důležitá je však jiná vlastnost uzávěrů – jelikož jsou uzávěry vytvořeny (a následně vráceny volajícímu kódu) uvnitř jiné funkce, mají mj. přístup i ke všem lokálním proměnným této funkce. Co se však stane v případě, že se na tyto proměnné budeme skutečně uvnitř uzávěru odkazovat, například budeme chtít číst či naopak modifikovat jejich hodnotu? Dokonce se můžeme ptát, zde je vůbec následující příklad korektní, tj. zda po svém spuštění nevypíše chybové hlášení při pokusu o přístup k proměnné y (u „normálních“ funkcí jsou totiž lokální proměnné vytvořeny na zásobníkovém rámci až ve chvíli volání této funkce, po opuštění funkce příkazem return se zásobníkový rámec a tím i lokální proměnné odstraní z operační paměti):

-- Funkce obsahujici lokalni promennou.
-- Tato funkce vraci anonymni funkci
-- jako svuj vysledek.
function generateClosure()
    -- lokalni promenna, ktera neni
    -- z okolniho programu dostupna
    local y = 1
    -- anonymni funkce vytiskne hodnotu
    -- lokalni promenne funkce "generateClosure"
    -- a potom se pokusi o zmenu jeji hodnoty:
    return function()
        print(y)
        y = y + 1
    end
end
 
-- ziskame uzaver, tj. instanci anonymni funkce (i s "externi lokalni promennou")
closure1 = generateClosure()
 
-- jake hodnoty se vytisknou?
closure1()
closure1()
closure1()
closure1()
 
-- finito

3. „Externí lokální proměnné“ přístupné z uzávěrů

Na první pohled by se mohlo zdát, že lokální proměnné funkcí ve všech případech zaniknou ve chvíli, kdy program opustí tělo této funkce (poněkud přesněji by bylo možné prohlásit, že lokální proměnná existuje jen v rámci svého lexikálního kontextu). Ovšem u většiny funkcionálních jazyků si každý vytvořený objekt (včetně funkce, zde speciálně uzávěru) uchovává odkazy na všechny proměnné, které jsou uvnitř objektu použity, nehledě na jejich lexikální kontext. To ovšem znamená, že pokud je uvnitř nějaké funkce nazvané generateClosure() (viz příklad uvedený v předchozí kapitole) vytvořena anonymní funkce (uzávěr) přistupující k lokálním proměnným funkce generateClosure() a tato anonymní funkce je vrácena příkazem return, jsou všechny odkazované lokální proměnné vytvořené uvnitř generateClosure() zachovány minimálně po tu dobu, po kterou existuje vrácený uzávěr (ten může být uložen jak do lokální, tak i do globální proměnné). Každé volání uzávěru může s těmito lokálními (a zdánlivě už neexistujícími) proměnnými pracovat, tj. číst i zapisovat do nich hodnoty. To znamená, že program uvedený v předchozí kapitole skutečně funguje – po svém spuštění vypíše posloupnost 1 – 2 – 3 – 4.

Vzhledem k tomuto chování není ve funkcionálních jazycích podporujících uzávěry obecně možné všechny lokální proměnné ukládat na zásobník (jeho rámec je po opuštění funkce zapomenut), ale je nutné využít spíše paměť alokovanou na haldě (heap), pro jejíž uvolňování je použita nějaká forma automatického uvolňování nepoužívané paměti (garbage collectoru). Vzhledem k tomu, že uzávěr pracuje skutečně s odkazem na lokální proměnnou vytvořenou v nadřazené funkci, vypíše následující program posloupnost 42 – 43 – 44 a 45, neboť poslední hodnota proměnné y před opuštěním funkce byla nastavena na 42:

-- Funkce obsahujici lokalni promennou.
-- Tato funkce vraci anonymni funkci
-- jako svuj vysledek.
function generateClosure()
    -- lokalni promenna, ktera neni
    -- z okolniho programu dostupna
    local y = 1
    -- anonymni funkce vytiskne hodnotu
    -- lokalni promenne funkce "generateClosure"
    -- a potom se pokusi o zmenu jeji hodnoty:
    local result = function()
        print(y)
        y = y + 1
    end
    -- po vytvoreni zarodku uzaveru
    -- zmenime hodnotu lokalni promenne
    y = 42
    -- vratime vytvorenou funkci - uzaver
    return result
end
 
-- ziskame uzaver, tj. instanci anonymni funkce
closure = generateClosure()
 
-- vytiskne se posloupnost hodnot 42, 43, 44 a 45
closure()
closure()
closure()
closure()
 
-- finito

4. Demonstrační příklad Test30.lua: vytvoření a použití uzávěru

V dnešním prvním demonstračním příkladu pojmenovaném Test30.lua je ve funkci createCounter() vytvořen uzávěr, tj. anonymní funkce, s níž je svázána externí lokální proměnná counter. Při každém zavolání tohoto uzávěru se hodnota proměnné counter zvýší o jedničku a nová hodnota je vrácena jako návratová hodnota uzávěru. Ve funkci main() je uzávěr nejprve vytvořen a následně třikrát zavolán:

--
-- Demonstracni priklad cislo 30.
--
-- Vytvareni a nasledne pouziti uzaveru (closure).
--
 
 
 
-- Vytvoreni a vraceni uzaveru, tj. funkce na niz je navazana
-- externi lokalni promenna - upvalue.
function createCounter()
    -- lokalni promenna, jejiz "zivotnost" presahuje
    -- pouhe zavolani a provedeni bloku funkce createCounter()
    local counter = 0
    -- navratovou hodnotou funkce createCounter() je anonymni
    -- funkce pracujici s promennou cnt, ktera je na tuto
    -- anonymni funkci navazana
    return function()
        -- counter se oznacuje jako "externi lokalni promenna"
        -- popr. v terminologii jazyka Lua "upvalue"
        counter = counter + 1
        return counter
    end
end
 
 
 
--
-- Spusteni testu.
--
function main()
    -- ziskame "instanci" anonymni funkce i na ni navazanou
    -- externi lokalni promennou "counter"
    -- --> closure
    local mycounter = createCounter()
    print(mycounter())
    print(mycounter())
    print(mycounter())
end
 
 
 
main()
 
 
 
--
-- Finito.
--

Po spuštění tohoto příkladu by se na standardním výstupu měla objevit následující sekvence čísel představujících vždy aktuální hodnotu externí lokální proměnné counter:

1
2
3

5. Překlad demonstračního příkladu Test30.lua do bajtkódu Lua VM

Velmi zajímavé bude zjistit, jakým způsobem se vlastně zdrojový kód demonstračního příkladu Test30.lua přeložil do bajtkódu Lua VM. Nejprve se podívejme na bajtkód funkce createCounter(). Ten je překvapivě jednoduchý:

createCounter():
function <Test30.lua:11,24> (4 instructions at 0x8de2cb0)
0 params, 2 slots, 0 upvalues, 1 local, 1 constant, 1 function
        1       [14]    LOADK           0 -1    ; inicializace lokální proměnné counter
        2       [23]    CLOSURE         1 0     ; vytvoření uzávěru, ten se uloží do registru R1
        3       [23]    RETURN          1 2     ; vrácení uzávěru
        4       [24]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (1) for 0x8de2cb0:
        1       0
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (1) for 0x8de2cb0:
        0       counter 2       5
 
; tabulka externích lokálních proměnných
upvalues (0) for 0x8de2cb0:

Na tomto bajtkódu je zajímavá především speciální instrukce CLOSURE, která slouží – nikoli překvapivě – k vytvoření uzávěru a uložení reference na vytvořený objekt do zadaného registru. Reference na uzávěr je součástí metadat funkce createCounter(), viz též doplňující informace pod samotným bajtkódem.

Samotná funkce tvořící uzávěr vypadá v bajtkódu následovně:

uzávěr:
function <Test30.lua:18,23&gh; (6 instructions at 0x8de2ee0)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
        1       [21]    GETUPVAL        0 0     ; přečíst hodnotu externí lokální proměnné counter do R0
        2       [21]    ADD             0 0 -1  ; zvýšit tuto hodnotu o jedničku
        3       [21]    SETUPVAL        0 0     ; uložit novou hodnotu do externí lokální proměnné
        4       [22]    GETUPVAL        0 0     ; znovu přečíst hodnotu externí lokální proměnné counter do R0
        5       [22]    RETURN          0 2     ; vrátit aktuální hodnotu počitadla
        6       [23]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (1) for 0x8de2ee0:
        1       1
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (0) for 0x8de2ee0:
 
; tabulka externích lokálních proměnných
upvalues (1) for 0x8de2ee0:
        0       counter 1       0

Opět se zde můžeme setkat s novinkou, konkrétně s dvojicí instrukcí GETUPVALSETUPVAL. Názvem „upval“ se v programovacím jazyku Lua myslí proměnné navázané na funkci/uzávěr; reference na tyto proměnné jsou součástí metadat, konkrétně tabulky upvalues (upvalues se však nepoužívají jen při implementaci uzávěrů, ale například i při práci s globálními symboly).

Samotné volání uzávěru již probíhá bez větších překvapení :-)

main():
function <Test30.lua:31,39> (15 instructions at 0x8de3108)
0 params, 3 slots, 1 upvalue, 1 local, 2 constants, 0 functions
        1       [35]    GETTABUP        0 0 -1  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "createCounter"
        2       [35]    CALL            0 1 2   ; vytvoření uzávěru, uložení reference do registru R0
 
        3       [36]    GETTABUP        1 0 -2  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        4       [36]    MOVE            2 0     ; reference na uzávěr je v registru R2
        5       [36]    CALL            2 1 0   ; volání uzávěru přes registr R0
        6       [36]    CALL            1 0 1   ; volání funkce print()
 
        7       [37]    GETTABUP        1 0 -2  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        8       [37]    MOVE            2 0     ; reference na uzávěr je v registru R2
        9       [37]    CALL            2 1 0   ; volání uzávěru přes registr R0
        10      [37]    CALL            1 0 1   ; volání funkce print()
 
        11      [38]    GETTABUP        1 0 -2  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        12      [38]    MOVE            2 0     ; reference na uzávěr je v registru R2
        13      [38]    CALL            2 1 0   ; volání uzávěru přes registr R0
        14      [38]    CALL            1 0 1   ; volání funkce print()
 
        15      [39]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (2) for 0x8de3108:
        1       "createCounter"
        2       "print"
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (1) for 0x8de3108:
        0       mycounter       3       16
 
; tabulka externích lokálních proměnných
upvalues (1) for 0x8de3108:
        0       _ENV    0       0

Z příkladu použití bajtkódu je pravděpodobně patrné, jak mocný a současně i jednoduchý bajtkód Lua VM je, například v porovnání s JVM.

6. Demonstrační příklad Test31.lua: externí lokální proměnné většího množství uzávěrů

V případě dalšího volání funkce createCounter() se vytvoří i nová lokální proměnná counter i nový uzávěr, tj. jednotlivé uzávěry obsahují vazby na své vlastní kopie původních lokálních proměnných (lokální proměnné tedy nejsou statické ve smyslu „statičnosti“ známém například z céčka). Každé volání funkce createCounter() tedy vede k alokaci paměti na haldě; do této paměti je uložena počáteční hodnota lokální proměnné counter a vytvořený uzávěr obsahuje odkaz na tuto hodnotu. Tuto vlastnost si otestujeme na dnešním druhém demonstračním příkladu nazvaném Test31.lua, v němž se vytvoří dva uzávěry, které se následně volají v počítané programové smyčce typu for:

--
-- Demonstracni priklad cislo 31.
--
-- Vytvareni a nasledne pouziti uzaveru (closure).
--
 
 
 
-- Vytvoreni a vraceni uzaveru
function createCounter()
    -- lokalni promenna, jejiz "zivotnost" presahuje
    -- pouhe zavolani a provedeni bloku funkce createCounter()
    local counter = 0
    -- navratovou hodnotou funkce createCounter() je anonymni
    -- funkce pracujici s promennou cnt, ktera je na tuto
    -- anonymni funkci navazana
    return function()
        -- counter se oznacuje jako "externi lokalni promenna"
        -- popr. v terminologii jazyka Lua "upvalue"
        counter = counter + 1
        return counter
    end
end
 
 
 
--
-- Spusteni testu.
--
function main()
    -- ziskame dvojici uzaveru
    local counter1 = createCounter()
    local counter2 = createCounter()
 
    -- volani uzaveru
    for i = 1, 10 do
        print("iteration #" .. i)
        print("    counter1: " .. counter1())
        print("    counter2: " .. counter2())
        print("    counter1: " .. counter1())
        print()
    end
end
 
 
 
main()
 
 
 
--
-- Finito.
--

Po spuštění tohoto příkladu by se na standardním výstupu měly objevit následující zprávy:

iteration #1
    counter1: 1
    counter2: 1
    counter1: 2
 
iteration #2
    counter1: 3
    counter2: 2
    counter1: 4
 
iteration #3
    counter1: 5
    counter2: 3
    counter1: 6
 
iteration #4
    counter1: 7
    counter2: 4
    counter1: 8
 
iteration #5
    counter1: 9
    counter2: 5
    counter1: 10
 
iteration #6
    counter1: 11
    counter2: 6
    counter1: 12
 
iteration #7
    counter1: 13
    counter2: 7
    counter1: 14
 
iteration #8
    counter1: 15
    counter2: 8
    counter1: 16
 
iteration #9
    counter1: 17
    counter2: 9
    counter1: 18
 
iteration #10
    counter1: 19
    counter2: 10
    counter1: 20

7. Překlad demonstračního příkladu Test31.lua do bajtkódu Lua VM

Opět se podívejme na způsob překladu jednotlivých částí demonstračního příkladu Test31.lua. Tentokrát již budou komentáře kratší, protože význam instrukcí CLOSURE, GETUPVALSETUPVAL jsme si již stručně vysvětlili v páté kapitole:

createCounter():
function <Test31.lua:10,23> (4 instructions at 0x8b3fcb0)
0 params, 2 slots, 0 upvalues, 1 local, 1 constant, 1 function
        1       [13]    LOADK           0 -1    ; inicializace lokální proměnné counter
        2       [22]    CLOSURE         1 0     ; vytvoření uzávěru, ten se uloží do registru R1
        3       [22]    RETURN          1 2     ; vrácení uzávěru
        4       [23]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (1) for 0x8b3fcb0:
        1       0
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (1) for 0x8b3fcb0:
        0       counter 2       5
 
; tabulka externích lokálních proměnných
upvalues (0) for 0x8b3fcb0:
uzávěr:
function <Test31.lua:17,22> (6 instructions at 0x8b3fee0)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
        1       [20]    GETUPVAL        0 0     ; přečíst hodnotu externí lokální proměnné counter do R0
        2       [20]    ADD             0 0 -1  ; zvýšit tuto hodnotu o jedničku
        3       [20]    SETUPVAL        0 0     ; uložit novou hodnotu do externí lokální proměnné
        4       [21]    GETUPVAL        0 0     ; znovu přečíst hodnotu externí lokální proměnné counter do R0
        5       [21]    RETURN          0 2     ; vrátit aktuální hodnotu počitadla
        6       [22]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (1) for 0x8b3fee0:
        1       1
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (0) for 0x8b3fee0:
 
; tabulka externích lokálních proměnných
upvalues (1) for 0x8b3fee0:
        0       counter 1       0
main():
function <Test31.lua:30,43> (35 instructions at 0x8b40108)
0 params, 9 slots, 1 upvalue, 6 locals, 7 constants, 0 functions
        1       [32]    GETTABUP        0 0 -1  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "createCounter"
        2       [32]    CALL            0 1 2   ; vytvoření uzávěru
        3       [33]    GETTABUP        1 0 -1  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "createCounter"
        4       [33]    CALL            1 1 2   ; vytvoření uzávěru
        5       [36]    LOADK           2 -2    ; příprava na smyčku for: počáteční hodnota počitadla je 1
        6       [36]    LOADK           3 -3    ; příprava na smyčku for: koncová hodnota počitadla je 10
        7       [36]    LOADK           4 -2    ; příprava na smyčku for: iterační krok je 1
 
        8       [36]    FORPREP         2 25    ; příprava počítané smyčky for s koncem na adrese 34
 
        9       [37]    GETTABUP        6 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        10      [37]    LOADK           7 -5    ; řetězec "iteration #"
        11      [37]    MOVE            8 5
        12      [37]    CONCAT          7 7 8   ; spojení řetězců
        13      [37]    CALL            6 2 1   ; volání funkce print()
 
        14      [38]    GETTABUP        6 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        15      [38]    LOADK           7 -6    ; řetězec "    counter1: "
        16      [38]    MOVE            8 0     ; bude se volat první uzávěr
        17      [38]    CALL            8 1 2   ; volání prvního uzávěru
        18      [38]    CONCAT          7 7 8   ; spojení řetězců
        19      [38]    CALL            6 2 1   ; volání funkce print()
 
        20      [39]    GETTABUP        6 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        21      [39]    LOADK           7 -7    ; řetězec "    counter2: "
        22      [39]    MOVE            8 1     ; bude se volat druhý uzávěr
        23      [39]    CALL            8 1 2   ; volání druhého uzávěru
        24      [39]    CONCAT          7 7 8   ; spojení řetězců
        25      [39]    CALL            6 2 1   ; volání funkce print()
 
        26      [40]    GETTABUP        6 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        27      [40]    LOADK           7 -6    ; řetězec "    counter1: "
        28      [40]    MOVE            8 0     ; bude se volat první uzávěr
        29      [40]    CALL            8 1 2   ; volání třetího uzávěru
        30      [40]    CONCAT          7 7 8   ; spojení řetězců
        31      [40]    CALL            6 2 1   ; volání funkce print()
 
        32      [41]    GETTABUP        6 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        33      [41]    CALL            6 1 1   ; volání funkce print() - prázdný řádek
 
        34      [36]    FORLOOP         2 -26   ; konec počítané smyčky for se skokem na instrukci 9
 
        35      [43]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (7) for 0x8b40108:
        1       "createCounter"
        2       1
        3       10
        4       "print"
        5       "iteration #"
        6       "    counter1: "
        7       "    counter2: "
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (6) for 0x8b40108:
        0       counter1        3       36
        1       counter2        5       36
        2       (for index)     8       35
        3       (for limit)     8       35
        4       (for step)      8       35
        5       i       9       34
 
; tabulka externích lokálních proměnných
upvalues (1) for 0x8b40108:
        0       _ENV    0       0

8. Demonstrační příklad Test32.lua: předání parametrů volanému uzávěru

Dnešní třetí a současně i poslední demonstrační příklad Test32.lua se v mnoha ohledech podobá prvnímu příkladu nazvaném Test30.lua. Je zde však jedna podstatná odlišnost – při volání uzávěru se mu předává parametr určující, jakým způsobem se má změnit hodnota externí lokální proměnné navázané na uzávěr. Tato změna ve zdrojovém kódu samozřejmě povede i ke změnám ve vygenerovaném bajtkódu, což uvidíme v následující kapitole:

--
-- Demonstracni priklad cislo 32.
--
-- Vytvareni a nasledne pouziti uzaveru (closure).
--
 
 
 
-- Vytvoreni a vraceni uzaveru
function createCounter()
    -- lokalni promenna, jejiz "zivotnost" presahuje
    -- pouhe zavolani a provedeni bloku funkce createCounter()
    local counter = 0
    -- navratovou hodnotou funkce createCounter() je anonymni
    -- funkce pracujici s promennou cnt, ktera je na tuto
    -- anonymni funkci navazana
    return function(delta)
        -- counter se oznacuje jako "externi lokalni promenna"
        -- popr. v terminologii jazyka Lua "upvalue"
        counter = counter + delta
        return counter
    end
end
 
 
 
--
-- Spusteni testu.
--
function main()
    -- ziskame "instanci" anonymni funkce i na ni navazanou
    -- externi lokalni promennou "counter"
    -- --> closure
    local mycounter = createCounter()
 
    -- volani uzaveru
    for i = 1, 10 do
        print("iteration #" .. i)
        print("    mycounter(1):  " .. mycounter(1))
        print("    mycounter(10): " .. mycounter(10))
        print("    mycounter(-2): " .. mycounter(-2))
        print()
    end
end
 
 
 
main()
 
 
 
--
-- Finito.
--

Po spuštění demonstračního příkladu Test32.lua by se na standardním výstupu měly objevit následující zprávy:

hacking_tip

iteration #1
    mycounter(1):  1
    mycounter(10): 11
    mycounter(-2): 9
 
iteration #2
    mycounter(1):  10
    mycounter(10): 20
    mycounter(-2): 18
 
iteration #3
    mycounter(1):  19
    mycounter(10): 29
    mycounter(-2): 27
 
iteration #4
    mycounter(1):  28
    mycounter(10): 38
    mycounter(-2): 36
 
iteration #5
    mycounter(1):  37
    mycounter(10): 47
    mycounter(-2): 45
 
iteration #6
    mycounter(1):  46
    mycounter(10): 56
    mycounter(-2): 54
 
iteration #7
    mycounter(1):  55
    mycounter(10): 65
    mycounter(-2): 63
 
iteration #8
    mycounter(1):  64
    mycounter(10): 74
    mycounter(-2): 72
 
iteration #9
    mycounter(1):  73
    mycounter(10): 83
    mycounter(-2): 81
 
iteration #10
    mycounter(1):  82
    mycounter(10): 92
    mycounter(-2): 90

9. Překlad demonstračního příkladu Test32.lua do bajtkódu Lua VM

Opět se podívejme na překlad demonstračního příkladu Test32.lua do bajtkódu Lua VM. Většina sekvencí instrukcí nám již bude známá z předchozích kapitol.

createCounter():
function <Test32.lua:10,23> (4 instructions at 0x91c7cb0)
0 params, 2 slots, 0 upvalues, 1 local, 1 constant, 1 function
        1       [13]    LOADK           0 -1    ; inicializace lokální proměnné counter
        2       [22]    CLOSURE         1 0     ; vytvoření uzávěru, ten se uloží do registru R1
        3       [22]    RETURN          1 2     ; vrácení uzávěru
        4       [23]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (1) for 0x91c7cb0:
        1       0
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (1) for 0x91c7cb0:
        0       counter 2       5
 
; tabulka externích lokálních proměnných
upvalues (0) for 0x91c7cb0:
uzávěr:
function <Test32.lua:17,22> (6 instructions at 0x91c7ee0)
1 param, 2 slots, 1 upvalue, 1 local, 0 constants, 0 functions
        ; zde dochází ke změně - registry jsou posunuty o jedničku (R0 totiž obsahuje parametr delta)
        1       [20]    GETUPVAL        1 0     ; přečíst hodnotu externí lokální proměnné counter do R0
        ; zde dochází ke změně - nepřičítá se konstanta, ale obsah registru R0
        2       [20]    ADD             1 1 0   ; zvýšit tuto hodnotu o obsah registru R0 - tedy o parametr delta
        3       [20]    SETUPVAL        1 0     ; uložit novou hodnotu do externí lokální proměnné
        4       [21]    GETUPVAL        1 0     ; znovu přečíst hodnotu externí lokální proměnné counter do R0
        5       [21]    RETURN          1 2     ; vrátit aktuální hodnotu počitadla
        6       [22]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (0) for 0x91c7ee0:
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (1) for 0x91c7ee0:
        0       delta   1       7
 
; tabulka externích lokálních proměnných
upvalues (1) for 0x91c7ee0:
        0       counter 1       0
main():
function <Test32.lua:30,44> (36 instructions at 0x91c80f0)
0 params, 9 slots, 1 upvalue, 5 locals, 9 constants, 0 functions
        1       [34]    GETTABUP        0 0 -1  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "createCounter"
        2       [34]    CALL            0 1 2   ; vytvoření uzávěru
        3       [37]    LOADK           1 -2    ; příprava na smyčku for: počáteční hodnota počitadla je 1
        4       [37]    LOADK           2 -3    ; příprava na smyčku for: koncová hodnota počitadla je 10
        5       [37]    LOADK           3 -2    ; příprava na smyčku for: iterační krok je 1
 
        6       [37]    FORPREP         1 28    ; příprava počítané smyčky for s koncem na adrese 35
 
        7       [38]    GETTABUP        5 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        8       [38]    LOADK           6 -5    ; řetězec "iteration #"
        9       [38]    MOVE            7 4
        10      [38]    CONCAT          6 6 7   ; spojení řetězců
        11      [38]    CALL            5 2 1   ; volání funkce print()
 
        12      [39]    GETTABUP        5 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        13      [39]    LOADK           6 -6    ; řetězec "    mycounter(1):  "
        14      [39]    MOVE            7 0     ; reference na uzávěr bude v registru 7
        15      [39]    LOADK           8 -2    ; parametr uzávěru 1
        16      [39]    CALL            7 2 2   ; volání uzávěru
        17      [39]    CONCAT          6 6 7   ; spojení řetězců
        18      [39]    CALL            5 2 1   ; volání funkce print()
 
        19      [40]    GETTABUP        5 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        20      [40]    LOADK           6 -7    ; řetězec "    mycounter(10): "
        21      [40]    MOVE            7 0     ; reference na uzávěr bude v registru 7
        22      [40]    LOADK           8 -3    ; parametr uzávěru 10
        23      [40]    CALL            7 2 2   ; volání uzávěru
        24      [40]    CONCAT          6 6 7   ; spojení řetězců
        25      [40]    CALL            5 2 1   ; volání funkce print()
 
        26      [41]    GETTABUP        5 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        27      [41]    LOADK           6 -8    ; řetězec "    mycounter(-2): "
        28      [41]    MOVE            7 0     ; reference na uzávěr bude v registru 7
        29      [41]    LOADK           8 -9    ; parametr uzávěru -2
        30      [41]    CALL            7 2 2   ; volání uzávěru
        31      [41]    CONCAT          6 6 7   ; spojení řetězců
        32      [41]    CALL            5 2 1   ; volání funkce print()
 
        33      [42]    GETTABUP        5 0 -4  ; přečtení aktuální hodnoty prvku z globální tabulky _ENV: "print"
        34      [42]    CALL            5 1 1   ; volání funkce print() - prázdný řádek
 
        35      [37]    FORLOOP         1 -29   ; konec počítané smyčky for se skokem na instrukci 7
 
        36      [44]    RETURN          0 1     ; automaticky generovaná instrukce
 
; tabulka konstant
constants (9) for 0x91c80f0:
        1       "createCounter"
        2       1
        3       10
        4       "print"
        5       "iteration #"
        6       "    mycounter(1):  "
        7       "    mycounter(10): "
        8       "    mycounter(-2): "
        9       -2
 
; tabulka lokálních proměnných a parametrů funkce či uzávěru
locals (5) for 0x91c80f0:
        0       mycounter       3       37
        1       (for index)     6       36
        2       (for limit)     6       36
        3       (for step)      6       36
        4       i       7       35
 
; tabulka externích lokálních proměnných
upvalues (1) for 0x91c80f0:
        0       _ENV    0       0

10. Repositář se zdrojovými kódy všech tří dnešních demonstračních příkladů

Všechny tři dnes popsané a použité demonstrační příklady byly uloženy do Mercurial repositáře umístěného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. Odkazy na prozatím poslední verze těchto tří příkladů naleznete v tabulce pod tímto odstavcem:

11. Odkazy na Internetu

  1. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  2. Programming in Lua: 6 – More about Functions
    http://www.lua.org/pil/6.html
  3. Programming in Lua: 6.1 – Closures
    http://www.lua.org/pil/6.1.html
  4. Programming in Lua: 9.1 – Coroutine Basics
    http://www.lua.org/pil/9.1.html
  5. Programming in Lua: Numeric for
    http://www.lua.org/pil/4.3.4.html
  6. Programming in Lua: break and return
    http://www.lua.org/pil/4.4.html
  7. Programming in Lua: Tables
    http://www.lua.org/pil/2.5.html
  8. Programming in Lua: Table Constructors
    http://www.lua.org/pil/3.6.html
  9. Programovací jazyk Lua
    http://palmknihy.cz/web/kni­ha/programovaci-jazyk-lua-12651.htm
  10. Lua: Tables Tutorial
    http://lua-users.org/wiki/TablesTutorial
  11. Lua: Control Structure Tutorial
    http://lua-users.org/wiki/ControlStruc­tureTutorial
  12. Lua Types Tutorial
    http://lua-users.org/wiki/LuaTypesTutorial
  13. Goto Statement in Lua
    http://lua-users.org/wiki/GotoStatement
  14. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  15. Lua 5.2 sources – lopcodes.h
    http://www.lua.org/source/5­.2/lopcodes.h.html
  16. Lua 5.2 sources – lopcodes.c
    http://www.lua.org/source/5­.2/lopcodes.c.html
  17. Python Byte Code Instructions
    https://docs.python.org/re­lease/2.5.2/lib/bytecodes­.html
  18. For-each Loop in Java
    http://www.leepoint.net/notes-java/flow/loops/foreach.html
  19. Python 2.x: funkce range()
    https://docs.python.org/2/li­brary/functions.html#range
  20. Python 2.x: typ iterátor
    https://docs.python.org/2/li­brary/stdtypes.html#itera­tor-types
  21. Python break, continue and pass Statements
    http://www.tutorialspoint­.com/python/python_loop_con­trol.htm
  22. For Loop (Wikipedia)
    http://en.wikipedia.org/wiki/For_loop
  23. Heinz Rutishauser
    http://en.wikipedia.org/wi­ki/Heinz_Rutishauser
  24. Parrot
    http://www.parrot.org/
  25. Parrot languages
    http://www.parrot.org/languages
  26. Parrot Primer
    http://docs.parrot.org/pa­rrot/latest/html/docs/intro­.pod.html
  27. Parrot Opcodes
    http://docs.parrot.org/pa­rrot/latest/html/ops.html
  28. Parrot VM
    http://en.wikibooks.org/wi­ki/Parrot_Virtual_Machine
  29. Parrot Assembly Language
    http://www.perl6.org/archi­ve/pdd/pdd06_pasm.html
  30. Parrot Reference: Chapter 11 – Perl 6 and Parrot Essentials
    http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html
  31. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  32. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  33. Byterun
    https://github.com/nedbat/byterun
  34. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Pyt­hon/lib/node56.html
  35. Python Byte Code Instructions
    https://docs.python.org/3­.2/library/dis.html#python-bytecode-instructions
  36. dis – Python module
    https://docs.python.org/2/li­brary/dis.html
  37. Comparison of Python virtual machines
    http://polishlinux.org/ap­ps/cli/comparison-of-python-virtual-machines/
  38. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  39. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  40. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  41. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  42. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  43. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  44. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  45. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  46. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  47. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  48. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  49. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  50. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  51. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  52. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  53. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  54. ASM Home page
    http://asm.ow2.org/
  55. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  56. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  57. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  58. BCEL Home page
    http://commons.apache.org/bcel/
  59. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  60. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  61. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  62. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  63. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  64. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  65. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  66. Javassist
    http://www.jboss.org/javassist/
  67. Byteman
    http://www.jboss.org/byteman
  68. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  69. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  70. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  71. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  72. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  73. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  74. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  75. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  76. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  77. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  78. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  79. Cobertura
    http://cobertura.sourceforge.net/
  80. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html

Autor článku

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