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