Obsah
2.1 Šablona pro programovou smyčku typu while
2.2 Šablona pro programovou smyčku typu repeat-until
2.3 Šablona pro počítanou programovou smyčku typu for
3. Šablony použité při překladu programové smyčky typu for-each
3.1 Šablona pro programovou smyčku typu for-each při průchodu polem
3.2 Šablona pro programovou smyčku typu for-each při průchodu asociativním polem
4. Programová smyčka typu for-each při průchodu polem
4.1 Zdrojový kód příkladu test35.lua
4.2 Překlad příkladu test35.lua do mezijazyka LuaJITu
5. Programová smyčka typu for-each při průchodu asociativním polem
5.1 Zdrojový kód příkladu test36.lua
5.2 Překlad příkladu test36.lua do mezijazyka LuaJITu
6. Vytvoření a následné použití (zavolání) uzávěru (closure)
6.1 Zdrojový kód příkladu test37.lua
6.2 Překlad příkladu test37.lua do mezijazyka LuaJITu
7. Externí lokální proměnné většího množství uzávěrů
7.1 Zdrojový kód příkladu test38.lua
7.2 Překlad příkladu test38.lua do mezijazyka LuaJITu
8. Předání parametrů volanému uzávěru
8.1 Zdrojový kód příkladu test39.lua
8.2 Překlad příkladu test39.lua do mezijazyka LuaJITu
9. Zdrojové kódy všech pěti dnešních demonstračních příkladů
10. Obsah dalších částí tohoto seriálu
11. Seznam všech popsaných instrukcí mezijazyka LuaJITu
1. LuaJIT – Just in Time překladač pro programovací jazyk Lua (7 – dokončení popisu mezijazyka LuaJITu)
V šesté části článku o vlastnostech LuaJITu jsme se převážně zabývali způsobem překladu různých typů programových smyček do mezijazyka (IR) využívaného LuaJITem. Připomeňme si, že se jednalo o programové smyčky typu while (test na ukončení smyčky je prováděn před každou iterací), repeat-until (test na ukončení smyčky je prováděn po každé iteraci) a taktéž o počítanou programovou smyčku typu for. LuaJIT překládá všechny tyto smyčky podle jednotné šablony zopakované ve druhé kapitole. Na ukázce šablon uvedených v navazující kapitole si povšimněte především použití speciální instrukce nazvané LOOP, která pouze označuje rozsah instrukcí, které smyčku tvoří – tato informace je následně použita just-in-time překladačem k detekci, která část aplikace se má v čase jejího běhu přeložit do nativního kódu. V případě počítané smyčky for není zapotřebí LOOP využívat, neboť její funkci zastupují další typy instrukcí: FORI na začátku smyčky (tato instrukce zajišťuje vstup do smyčky) a FORL na konci smyčky (tato instrukce zajišťuje řízení další iterace).
2. Šablony (templates) použité při překladu programových smyček while, repeat-until a počítané smyčky for
Ve stručnosti si zopakujme tvar šablon (posloupnost instrukcí IR) použitou při překladu typických programových smyček do mezijazyka LuaJITu:
2.1 Šablona pro programovou smyčku typu while
Při překladu programové smyčky typu while do mezijazyka LuaJITu se používá čtveřice instrukcí – podmíněný skok na začátku smyčky realizovaný dvojicí instrukcí IS??+JMP, nepodmíněný skok na konci smyčky realizovaný instrukcí JMP a „přebytečnou“ instrukcí LOOP označující tělo smyčky (tedy zjednodušeně řečeno rozsah instrukcí od-do):
+---> IS?? ; podmínka odvozená z invertované podmínky zapsané ve zdrojovém kódu | JMP --+ ; podmíněný skok ZA konec programové smyčky | LOOP | ; označení generické programové smyčky (pro detekci hot spotů) | ? | | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | | ? | ; end loop +---- JMP | ; nepodmíněný skok na začátek programové smyčky <-----+
2.2 Šablona pro programovou smyčku typu repeat-until
Překlad programové smyčky typu repeat-until do mezijazyka LuaJITu je jednodušší, neboť se podmíněný skok realizovaný dvojicí IS??+JMP přesouvá na konec smyčky. Na jejím začátku tak zbývá místo jen pro instrukci LOOP:
+---> LOOP ; označení generické programové smyčky (pro detekci hot spotů) | ? | ? ; instrukce tvořící tělo smyčky | ? ; instrukce tvořící tělo smyčky | ? ; instrukce tvořící tělo smyčky | ? | IS?? ; podmínka odvozená z podmínky zapsané ve zdrojovém kódu +---- JMP ; podmíněný skok na začátek programové smyčky
2.3 Šablona pro počítanou programovou smyčku typu for
Šablona použitá pro počítanou programovou smyčku typu for při překladu do mezijazyka LuaJITu je poněkud odlišná, neboť zde již nenajdeme instrukci LOOP. Je tomu tak z toho důvodu, že „označení“ začátku a konce tohoto typu smyčky nám zajistí instrukce FORI (začátek smyčky) a FORL (konec smyčky).
+---> FORI --+ ; vstup do počítané programové smyčky typu for | ? | | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | +----- FORL | ; další iterace, skok na začátek programové smyčky <-------+
3. Šablony použité při překladu programové smyčky typu for-each
Oproti trojici programových smyček představených v předchozí kapitole se práce překladače LuaJITu stává poněkud složitější ve chvíli, kdy se ve zdrojovém kódu vyskytne (velmi často používaná) programová smyčka typu for-each určená pro průchod polem popř. pro průchod asociativním polem. Tyto typy smyček by se teoreticky daly přeložit stejným způsobem jako programová smyčka while, přičemž by se před každou iterací zavolala funkce next(). Smyčka for-each by tedy byla do značné míry ekvivalentní zápisu:
-- inicializace - získání prvního prvku a jeho indexu z tabulky local index,value = next(my_table, nil) -- programová smyčka typu while while index do index,value = next(my_table, index) end
Ve skutečnosti však překladač LuaJITu generuje dosti odlišný IR, v němž nalezneme nové typy instrukcí, což si ukážeme v navazujících podkapitolách a kapitolách.
3.1 Šablona pro programovou smyčku typu for-each při průchodu polem
Při průchodu polem, tj. tabulkou, v níž mají všechny prvky přiřazen celočíselný index od 1 do n, se ve zdrojovém kódu využívá iterátoru ipairs() a v přeloženém kódu nalezneme jak volání tohoto iterátoru, tak i dvojici nových instrukcí nazvaných ITERC a ITERL. Instrukce ITERC zavolá znovu iterátor, přičemž parametry tohoto iterátoru jsou většinou již připraveny v rezervovaných slotech po celou dobu „života“ programové smyčky. Vlastní test, zda se má provést další iterace a tím pádem i skok, je představován funkcí ITERL:
GGET ; získání reference na funkci se jménem "ipairs" MOV ; předání iterátoru jako parametru funkce CALL ; zavolání funkce ipairs() JMP --+ ; přímý skok na instrukci ITERC +---> ? | | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | | ITERC<-+ +---- ITERL ; návrat na začátek smyčky (další iterace)
3.2 Šablona pro programovou smyčku typu for-each při průchodu asociativním polem
Podobným způsobem je přeložena i programová smyčka typu for-each ve chvíli, kdy se prochází všemi prvky asociativního pole, tj. takové tabulky, v níž je každý prvek indexován klíčem (typicky řetězcem, ale může se jednat i o klíč jiného typu, kromě hodnoty nil). Při překladu do bajtkódu se používají instrukce ISNEXT, ITERN a ITERL (tato instrukce je tedy stejná, jako tomu bylo i v předchozím případě). Instrukce ITERN dokáže spolupracovat (volat) funkce next() či pairs() a tím pádem implementovat jak načtení dalšího prvku z asociativního pole, tak i přípravu pro test, zda se má provést další iterace:
GGET ; získání reference na funkci se jménem "pairs" MOV ; předání iterátoru jako parametru funkce CALL ; zavolání funkce pairs() ISNEXT-+ ; přímý skok na instrukci ITERN +---> ? | | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | ; instrukce tvořící tělo smyčky | ? | | ITERN<-+ +---- ITERL ; návrat na začátek smyčky (další iterace)
4. Programová smyčka typu for-each při průchodu polem
V dnešním prvním demonstračním příkladu nazvaném test35.lua je nejprve vytvořena a současně i inicializována desetiprvková tabulka. Následně se v trojici programových smyček postupně vypíšou indexy prvků (první smyčka), hodnoty prvků (druhá smyčka) a indexy+hodnoty prvků (třetí smyčka) této tabulky.
4.1 Zdrojový kód příkladu test35.lua
-- -- LuaJIT: demonstrační příklad číslo 35. -- -- Práce s tabulkami: -- * vytvoření a inicializace neprázdné tabulky -- * programová smyčka typu for-each využívající -- funkci ipairs použitá pro průchod tabulkou. -- -- vytvoření desetiprvkové tabulky local tbl = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1} -- programová smyčka typu for-each využívající funkci ipairs for i,val in ipairs(tbl) do print(i) end -- odřádkování print() -- programová smyčka typu for-each využívající funkci ipairs for i,val in ipairs(tbl) do print(val) end -- odřádkování print() -- programová smyčka typu for-each využívající funkci ipairs for i,val in ipairs(tbl) do print(i, val) end -- finito
4.2 Překlad příkladu test35.lua do mezijazyka LuaJITu
V IR přeloženého příkladu test35.lua můžeme vidět použití instrukcí ITERC a ITERL:
; Překlad demonstračního příkladu test35.lua ; do IR využívaného virtuálním strojem a JIT ; překladačem LuaJIT. ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test35.lua:0-40 ; vytvoření a inicializace tabulky ; local tbl = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1} 0001 TDUP 0 0 ; vytvoření a inicializace tabulky ; programová smyčka typu for-each ; for i,val in ipairs(tbl) do 0002 GGET 1 1 ; získání reference na funkci se jménem "ipairs" 0003 MOV 2 0 ; předání iterátoru 0004 CALL 1 4 2 ; volání funkce ipairs() 0005 JMP 4 => 0009 ; přímý skok na instrukci ITERC ; print(i) 0006 => GGET 6 2 ; získání reference na funkci se jménem "print" 0007 MOV 7 4 ; bude se tisknout index 0008 CALL 6 1 2 ; volání funkce print() 0009 => ITERC 4 3 3 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; end loop 0010 ITERL 4 => 0006 ; další iterace (pokud k ní dojde) začne na instrukci 0006 ; print() 0011 GGET 1 2 ; získání reference na funkci se jménem "print" 0012 CALL 1 1 1 ; volání funkce print() ; programová smyčka typu for-each ; for i,val in ipairs(tbl) do 0013 GGET 1 1 ; získání reference na funkci se jménem "ipairs" 0014 MOV 2 0 ; předání iterátoru 0015 CALL 1 4 2 ; volání funkce ipairs() 0016 JMP 4 => 0020 ; přímý skok na instrukci ITERC ; print(val) 0017 => GGET 6 2 ; získání reference na funkci se jménem "print" 0018 MOV 7 5 ; bude se tisknout hodnota 0019 CALL 6 1 2 ; volání funkce print() 0020 => ITERC 4 3 3 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; end loop 0021 ITERL 4 => 0017 ; další iterace (pokud k ní dojde) začne na instrukci 0017 ; print() 0022 GGET 1 2 ; získání reference na funkci se jménem "print" 0023 CALL 1 1 1 ; volání funkce print() ; programová smyčka typu for-each ; for i,val in ipairs(tbl) do 0024 GGET 1 1 ; získání reference na funkci se jménem "ipairs" 0025 MOV 2 0 ; předání iterátoru 0026 CALL 1 4 2 ; volání funkce ipairs() 0027 JMP 4 => 0032 ; přímý skok na instrukci ITERC ; print(i, val) 0028 => GGET 6 2 ; získání reference na funkci se jménem "print" 0029 MOV 7 4 ; bude se tisknout index 0030 MOV 8 5 ; a současně i hodnota 0031 CALL 6 1 3 ; volání funkce print() 0032 => ITERC 4 3 3 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; end loop 0033 ITERL 4 => 0028 ; další iterace (pokud k ní dojde) začne na instrukci 0028 ; každý program je automaticky ukončen následující instrukcí 0034 RET0 0 1 ; konec
5. Programová smyčka typu for-each při průchodu asociativním polem
V dnešním druhém demonstračním příkladu s názvem test36.lua si ukážeme způsob průchodu asociativním polem ve smyčce typu for-each spojené s funkcí pairs(). Podobně jako v předchozím příkladu, i zde jsou použity tři programové smyčky, přičemž v první smyčce se na standardní výstup vytisknou pouze jednotlivé klíče (v pořadí, které obvykle neodpovídá pořadí vložení prvků do tabulky), ve smyčce druhé se vytisknou jen hodnoty prvků tabulky a konečně ve smyčce třetí se vytisknou jak hodnoty klíčů, tak i hodnoty prvků přiřazených k těmto klíčům.
5.1 Zdrojový kód příkladu test36.lua
-- -- LuaJIT: demonstrační příklad číslo 36. -- -- Práce s tabulkami: -- * vytvoření a inicializace prázdné tabulky -- * přidání prvku do tabulky s použitím klíčů -- * programová smyčka typu for-each využívající -- funkci ipairs použitá pro průchod tabulkou -- -- vytvoření prázdné tabulky local tbl = {} -- nastavit hodnotu prvku tabulky tbl["first"] = 777 -- nastavit hodnotu prvku tabulky tbl["second"] = 999 -- nastavit hodnotu prvku tabulky tbl["tenth"] = 1000 -- programová smyčka typu for-each využívající funkci ipairs for key,val in pairs(tbl) do print(key) end -- odřádkování print() -- programová smyčka typu for-each využívající funkci ipairs for key,val in pairs(tbl) do print(val) end -- odřádkování print() -- programová smyčka typu for-each využívající funkci ipairs for key,val in pairs(tbl) do print(key, val) end -- finito
5.2 Překlad příkladu test36.lua do mezijazyka LuaJITu
Při překladu tohoto demonstračního příkladu do IR jsou pro implementaci smyčky použity instrukce ISNEXT a ITERN:
; Překlad demonstračního příkladu test36.lua ; do IR využívaného virtuálním strojem a JIT ; překladačem LuaJIT. ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test36.lua:0-50 ; vytvoření prázdné tabulky ; local tbl = {} 0001 TNEW 0 0 ; vytvoření prázdné tabulky ; nastavit hodnotu prvku tabulky ; s využitím klíče reprezentovaného řetězcem ; tbl["first"] = 777 0002 KSHORT 1 777 ; ukládaná konstanta 0003 TSETS 1 0 0 ; uložení hodnoty do tabulky ; nastavit hodnotu prvku tabulky ; s využitím klíče reprezentovaného řetězcem ; tbl["second"] = 999 0004 KSHORT 1 999 ; ukládaná konstanta 0005 TSETS 1 0 1 ; uložení hodnoty do tabulky ; nastavit hodnotu prvku tabulky ; s využitím klíče reprezentovaného řetězcem ; tbl["tenth"] = 1000 0006 KSHORT 1 1000 ; ukládaná konstanta 0007 TSETS 1 0 2 ; uložení hodnoty do tabulky ; programová smyčka typu for-each ; for key,val in pairs(tbl) do 0008 GGET 1 3 ; získání reference na funkci se jménem "pairs" 0009 MOV 2 0 ; předání iterátoru 0010 CALL 1 4 2 ; volání funkce pairs() 0011 ISNEXT 4 => 0015 ; přímý skok na instrukci ITERN ; print(key) 0012 => GGET 6 4 ; získání reference na funkci se jménem "print" 0013 MOV 7 4 ; bude se tisknout klíč 0014 CALL 6 1 2 ; volání funkce print() 0015 => ITERN 4 3 3 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; end loop 0016 ITERL 4 => 0012 ; další iterace (pokud k ní dojde) začne na instrukci 0012 ; print() 0017 GGET 1 4 ; získání reference na funkci se jménem "print" 0018 CALL 1 1 1 ; volání funkce print() ; programová smyčka typu for-each ; for key,val in pairs(tbl) do 0019 GGET 1 3 ; získání reference na funkci se jménem "pairs" 0020 MOV 2 0 0021 CALL 1 4 2 0022 ISNEXT 4 => 0026 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; print(val) 0023 => GGET 6 4 ; získání reference na funkci se jménem "print" 0024 MOV 7 5 ; bude se tisknout hodnota 0025 CALL 6 1 2 ; volání funkce print() 0026 => ITERN 4 3 3 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; end loop 0027 ITERL 4 => 0023 ; další iterace (pokud k ní dojde) začne na instrukci 0023 ; print() 0028 GGET 1 4 ; získání reference na funkci se jménem "print" 0029 CALL 1 1 1 ; volání funkce print() ; programová smyčka typu for-each ; for key,val in pairs(tbl) do 0030 GGET 1 3 ; získání reference na funkci se jménem "pairs" 0031 MOV 2 0 0032 CALL 1 4 2 0033 ISNEXT 4 => 0038 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; print(key, val) 0034 => GGET 6 4 ; získání reference na funkci se jménem "print" 0035 MOV 7 4 ; bude se tisknout klíč 0036 MOV 8 5 ; a současně se bude tisknout i hodnota 0037 CALL 6 1 3 ; volání funkce print() 0038 => ITERN 4 3 3 ; volání iterátoru a zjištění. zda existuje další prvek v tabulce ; end loop 0039 ITERL 4 => 0034 ; další iterace (pokud k ní dojde) začne na instrukci 0034 ; každý program je automaticky ukončen následující instrukcí 0040 RET0 0 1 ; konec
6. Vytvoření a následné použití (zavolání) uzávěru (closure)
Poslední problematikou, kterou se v souvislosti s překladem zdrojových kódů jazyka Lua do mezikódu LuaJITu budeme zabývat, je způsob práce s takzvanými uzávěry (closures). Programovací jazyk Lua převzal mnoho vlastností z klasických funkcionálních programovacích jazyků, které práci s uzávěry podporují; jedná se současně o velmi důležitou vlastnost umožňující efektivnější implementaci některých typů algoritmů. U většiny funkcionálních jazyků – a taktéž u jazyka Lua – 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 (tyto proměnné se nazývají „externí lokální proměnné“ popř. „upvalues“).
To ovšem znamená, že pokud je uvnitř nějaké funkce vytvořena nová anonymní funkce (uzávěr) přistupující k lokálním proměnným své „obalující“ funkce a pokud je následně tato anonymní funkce vrácena příkazem return, jsou všechny odkazované lokální proměnné 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.
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).
V dnešním třetím demonstračním příkladu pojmenovaném test37.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.
6.1 Zdrojový kód příkladu test37.lua
-- -- LuaJIT: demonstrační příklad číslo 37 -- -- Vytvoření a následné použití uzávěru (closure). -- -- Vytvoření a vrácení uzávěru, tj. funkce na níž je navázána -- externí lokální proměnná - upvalue. function createCounter() -- lokální proměnná, jejíž "životnost" v čase běhu aplikace přesahuje -- pouhé zavolání a provedeni bloku funkce createCounter() local counter = 0 -- návratovou hodnotou funkce createCounter() je anonymní -- funkce pracující s proměnnou cnt, která je na tuto -- anonymní funkci navázána return function() -- counter se označuje jako "externí lokální proměnná" -- popř. v terminologii jazyka Lua "upvalue" counter = counter + 1 return counter end end -- -- Spuštění testu. -- function main() -- získáme "instanci" anonymní funkce i na ni navázanou -- externí lokální proměnnou nazvanou "counter" -- -& closure local mycounter = createCounter() print(mycounter()) print(mycounter()) print(mycounter()) end main() -- -- Finito. --
6.2 Překlad příkladu test37.lua do mezijazyka LuaJITu
Ve vygenerované sekvenci IR demonstračního příkladu test37.lua můžeme najít čtveřici nových instrukcí: FNEW, UCLO, UGET a USETV. Instrukce FNEW slouží pro vytvoření nového uzávěru z prototypu specifikovaného v operandu D, instrukce UCLO k uzavření hodnot a skoku na následující instrukci (tou je ve většině případů JMP či RET, skok se zde využije při dalších optimalizacích). V samotném uzávěru (tedy funkci využívající externí vázané proměnné) nalezneme instrukce UGET a USETV, které slouží pro přečtení či k zápisu do externí vázané proměnné (právě díky těmto instrukcím dokáže uzávěr přistupovat k vázaným proměnným):
; Překlad demonstračního příkladu test37.lua ; do IR využívaného virtuálním strojem a JIT ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test37.lua:18-23 ; implementace uzávěru ; counter = counter + 1 0001 UGET 0 0 ; přečtení hodnoty externí vázané proměnné counter 0002 ADDVN 0 0 0 ; přičtení jedničky 0003 USETV 0 0 ; zápis hodnoty externí vázané proměnné counter 0004 UGET 0 0 ; přečtení hodnoty externí vázané proměnné counter ; return counter 0005 RET1 0 2 ; vrácení nové hodnoty ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test37.lua:11-24 ; implementace funkce createCounter() ; local counter = 0 0001 KSHORT 0 0 ; nastavení hodnoty lokální proměnné counter ; return function() 0002 FNEW 1 0 ; vytvoření uzávěru 0003 UCLO 0 => 0004 0004 => RET1 1 2 ; vrácení uzávěru ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test37.lua:31-39 ; implementace funkce main() ; local mycounter = createCounter() 0001 GGET 0 0 ; získat referenci na funkci "createCounter" 0002 CALL 0 2 1 ; volání funkce "createCounter" ; print(mycounter()) 0003 GGET 1 1 ; získat referenci na funkci "print" 0004 MOV 2 0 0005 CALL 2 0 1 ; volání funkce "mycounter" 0006 CALLM 1 1 0 ; volání funkce "print" ; print(mycounter()) 0007 GGET 1 1 ; získat referenci na funkci "print" 0008 MOV 2 0 0009 CALL 2 0 1 ; volání funkce "mycounter" 0010 CALLM 1 1 0 ; volání funkce "print" ; print(mycounter()) 0011 GGET 1 1 ; získat referenci na funkci "print" 0012 MOV 2 0 0013 CALL 2 0 1 ; volání funkce "mycounter" 0014 CALLM 1 1 0 ; volání funkce "print" 0015 RET0 0 1 ; návrat z funkce "main" ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test37.lua:0-51 ; inicializace a volání funkce main 0001 FNEW 0 0 ; test37.lua:11 0002 GSET 0 1 ; "createCounter" 0003 FNEW 0 2 ; test37.lua:31 0004 GSET 0 3 ; "main" 0005 GGET 0 3 ; "main" 0006 CALL 0 1 1 ; volání funkce "main" ; každý program je automaticky ukončen následující instrukcí 0007 RET0 0 1 ; konec
7. 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 čtvrtém demonstračním příkladu nazvaném test38.lua, v němž se vytvoří dva uzávěry, které se následně volají v počítané programové smyčce typu for.
7.1 Zdrojový kód příkladu test38.lua
-- -- LuaJIT: demonstrační příklad číslo 38. -- -- Vytváření a následné použití uzávěru (closure). -- -- Vytvoření a vraceni uzávěru function createCounter() -- lokální proměnná, jejíž "životnost" v čase běhu aplikace přesahuje -- pouhé zavolání a provedeni bloku funkce createCounter() local counter = 0 -- návratovou hodnotou funkce createCounter() je anonymní -- funkce pracující s proměnnou cnt, která je na tuto -- anonymní funkci navázána return function() -- counter se označuje jako "externí lokální proměnná" -- popř. v terminologii jazyka Lua "upvalue" counter = counter + 1 return counter end end -- -- Spuštění testu. -- function main() -- získáme dvojici uzávěru local counter1 = createCounter() local counter2 = createCounter() -- volání uzávěru for i = 1, 10 do print("iteration #" .. i) print(" counter1: " .. counter1()) print(" counter2: " .. counter2()) print(" counter1: " .. counter1()) print() end end main() -- -- Finito. --
7.2 Překlad příkladu test38.lua do mezijazyka LuaJITu
; Překlad demonstračního příkladu test38.lua ; do IR využívaného virtuálním strojem a JIT ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test38.lua:17-22 ; implementace uzávěru ; counter = counter + 1 0001 UGET 0 0 ; přečtení hodnoty externí vázané proměnné counter 0002 ADDVN 0 0 0 ; přičtení jedničky 0003 USETV 0 0 ; zápis hodnoty externí vázané proměnné counter 0004 UGET 0 0 ; přečtení hodnoty externí vázané proměnné counter ; return counter 0005 RET1 0 2 ; vrácení nové hodnoty ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test38.lua:10-23 ; implementace funkce createCounter() ; local counter = 0 0001 KSHORT 0 0 ; nastavení hodnoty lokální proměnné counter ; return function() 0002 FNEW 1 0 ; vytvoření uzávěru 0003 UCLO 0 => 0004 0004 => RET1 1 2 ; vrácení uzávěru ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test38.lua:30-43 ; implementace funkce main() ; local mycounter1 = createCounter() 0001 GGET 0 0 ; získat referenci na funkci "createCounter" 0002 CALL 0 2 1 ; volání funkce "createCounter" ; local mycounter2 = createCounter() 0003 GGET 1 0 ; získat referenci na funkci "createCounter" 0004 CALL 1 2 1 ; volání funkce "createCounter" ; příprava počítané programové smyčky for 0005 KSHORT 2 1 0006 KSHORT 3 10 0007 KSHORT 4 1 0008 FORI 2 => 0035 ; tělo počítané programové smyčky for ; print("iteration #" .. i) 0009 => GGET 6 1 ; "print" 0010 KSTR 7 2 ; "iteration #" 0011 MOV 8 5 0012 CAT 7 7 8 ; spojení řetězců 0013 CALL 6 1 2 ; volání funkce "print" ; print(" counter1: " .. counter1()) 0014 GGET 6 1 ; "print" 0015 KSTR 7 3 ; " counter1: " 0016 MOV 8 0 0017 CALL 8 2 1 ; volání uzávěru 0018 CAT 7 7 8 ; spojení řetězců 0019 CALL 6 1 2 ; volání funkce "print" ; print(" counter2: " .. counter2()) 0020 GGET 6 1 ; "print" 0021 KSTR 7 4 ; " counter2: " 0022 MOV 8 1 0023 CALL 8 2 1 ; volání uzávěru 0024 CAT 7 7 8 ; spojení řetězců 0025 CALL 6 1 2 ; volání funkce "print" ; print(" counter1: " .. counter1()) 0026 GGET 6 1 ; "print" 0027 KSTR 7 3 ; " counter1: " 0028 MOV 8 0 0029 CALL 8 2 1 ; volání uzávěru 0030 CAT 7 7 8 ; spojení řetězců 0031 CALL 6 1 2 ; volání funkce "print" ; print() 0032 GGET 6 1 ; "print" 0033 CALL 6 1 1 ; konec těla programové smyčky for 0034 FORL 2 => 0009 ; návrat z funkce 0035 => RET0 0 1 ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test38.lua:0-55 ; inicializace a volání funkce main 0001 FNEW 0 0 ; test38.lua:10 0002 GSET 0 1 ; "createCounter" 0003 FNEW 0 2 ; test38.lua:30 0004 GSET 0 3 ; "main" 0005 GGET 0 3 ; "main" 0006 CALL 0 1 1 ; volání funkce "main" ; každý program je automaticky ukončen následující instrukcí 0007 RET0 0 1 ; konec
8. Předání parametrů volanému uzávěru
Dnešní pátý a současně i poslední demonstrační příklad, který je pojmenovaný test39.lua, se v mnoha ohledech podobá třetímu příkladu nazvanému test37.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 IR.
8.1 Zdrojový kód příkladu test39.lua
-- -- LuaJIT: demonstrační příklad číslo 39. -- -- Vytváření a následné použití uzávěru (closure). -- -- Vytvoření a vraceni uzávěru function createCounter() -- lokální proměnná, jejíž "životnost" v čase běhu aplikace přesahuje -- pouhé zavolání a provedeni bloku funkce createCounter() local counter = 0 -- návratovou hodnotou funkce createCounter() je anonymní -- funkce pracující s proměnnou cnt, která je na tuto -- anonymní funkci navázána return function(delta) -- counter se označuje jako "externí lokální proměnná" -- popř. v terminologii jazyka Lua "upvalue" counter = counter + delta return counter end end -- -- Spuštění testu. -- function main() -- získáme "instanci" anonymní funkce i na ni navázanou -- externí lokální proměnnou "counter" -- -> closure local mycounter = createCounter() -- volání uzávěru 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. --
8.2 Překlad příkladu test39.lua do mezijazyka LuaJITu
; Překlad demonstračního příkladu test39.lua ; do IR využívaného virtuálním strojem a JIT ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test39.lua:17-22 ; implementace uzávěru ; counter = counter + 1 0001 UGET 1 0 ; přečtení hodnoty externí vázané proměnné counter 0002 ADDVV 1 1 0 ; přičtení jedničky 0003 USETV 0 1 ; zápis hodnoty externí vázané proměnné counter 0004 UGET 1 0 ; přečtení hodnoty externí vázané proměnné counter ; return counter 0005 RET1 1 2 ; vrácení nové hodnoty ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test39.lua:10-23 ; implementace funkce createCounter() ; local counter = 0 0001 KSHORT 0 0 ; nastavení hodnoty lokální proměnné counter ; return function() 0002 FNEW 1 0 ; vytvoření uzávěru 0003 UCLO 0 => 0004 0004 => RET1 1 2 ; vrácení uzávěru ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test39.lua:30-44 ; implementace funkce main() ; local mycounter = createCounter() 0001 GGET 0 0 ; získat referenci na funkci "createCounter" 0002 CALL 0 2 1 ; volání funkce "createCounter" ; příprava počítané programové smyčky for 0003 KSHORT 1 1 0004 KSHORT 2 10 0005 KSHORT 3 1 0006 FORI 1 => 0036 ; tělo počítané programové smyčky for ; print("iteration #" .. i) 0007 => GGET 5 1 ; "print" 0008 KSTR 6 2 ; "iteration #" 0009 MOV 7 4 0010 CAT 6 6 7 ; spojení řetězců 0011 CALL 5 1 2 ; print(" counter1: " .. counter1()) 0012 GGET 5 1 ; "print" 0013 KSTR 6 3 ; " mycounter(1): " 0014 MOV 7 0 0015 KSHORT 8 1 0016 CALL 7 2 2 ; volání uzávěru 0017 CAT 6 6 7 ; spojení řetězců 0018 CALL 5 1 2 ; volání funkce "print" ; print(" counter2: " .. counter2()) 0019 GGET 5 1 ; "print" 0020 KSTR 6 4 ; " mycounter(10): " 0021 MOV 7 0 0022 KSHORT 8 10 0023 CALL 7 2 2 ; volání uzávěru 0024 CAT 6 6 7 ; spojení řetězců 0025 CALL 5 1 2 ; volání funkce print 0026 GGET 5 1 ; "print" 0027 KSTR 6 5 ; " mycounter(-2): " 0028 MOV 7 0 0029 KSHORT 8 -2 0030 CALL 7 2 2 ; volání funkce "print" 0031 CAT 6 6 7 ; spojení řetězců 0032 CALL 5 1 2 ; volání funkce "print" 0033 GGET 5 1 ; "print" 0034 CALL 5 1 1 ; volání funkce "print" ; konec těla programové smyčky for 0035 FORL 1 => 0007 ; návrat z funkce 0036 => RET0 0 1 ; návrat z funkce "main" ; metadata se základními informacemi o zdrojovém kódu, ; který byl použit pro vygenerování tohoto IR -- BYTECODE -- test39.lua:0-56 ; inicializace a volání funkce main 0001 FNEW 0 0 ; vytvoření funkce z definice na řádku test39.lua:10 0002 GSET 0 1 ; zápis "createCounter" do globální tabulky 0003 FNEW 0 2 ; vytvoření funkce z definice na řádku test39.lua:30 0004 GSET 0 3 ; zápis "main" do globální tabulky 0005 GGET 0 3 ; "main" 0006 CALL 0 1 1 ; volání funkce "main" ; každý program je automaticky ukončen následující instrukcí 0007 RET0 0 1 ; konec
9. Zdrojové kódy všech pěti dnešních demonstračních příkladů
V dnešní části i v šesti předchozích částech článku o LuaJITu jsme si řekli všechny podstatné informace o způsobu překladu zdrojových kódů naprogramovaných v jazyku Lua do mezijazyka LuaJITu. Ve skutečnosti však tento překlad není z programátorského hlediska příliš zajímavý, neboť je založen na předpřipravených šablonách. Mnohem zajímavější a taktéž složitější je způsob just-in-time překladu z mezijazyka LuaJITu do strojového kódu konkrétního typu mikroprocesoru. Tento překlad již musí brát v úvahu jak rozdíly mezi jednotlivými typy instrukčních sad (i386, x86_64, ARM, …), tak i počet použitelných pracovních registrů, adresovací režimy (a jejich efektivitu), způsob předávání parametrů volaným subrutinám atd. Navíc se již od dob prvních JIT překladačů ukazuje, že poměrně velkou část kódu reálných aplikací ani nemá cenu složitě překládat, ale postačuje interpretace. Pouze často volané funkce a samozřejmě též programové smyčky (speciálně vnořené smyčky) se „JITují“. Příště si ukážeme, jak je tato problematika řešená v LuaJITu.
10. Obsah dalších částí tohoto seriálu
Všechny dnes použité demonstrační příklady byly, jak je tomu ostatně v tomto seriálu již dlouhodobějším zvykem, uloženy do Git (přesněji řečeno do GitHub) repositáře umístěného na adrese https://github.com/tisnik/luajit-examples:
11. Seznam všech popsaných instrukcí mezijazyka LuaJITu
Na závěr si ještě uvedeme tabulku se všemi doposud popsanými instrukcemi mezijazyka LuaJITu. Připomeňme si, že IR používá takzvaný tříadresový kód a instrukce o pevné šířce třiceti dvou bitů. Přístup k instrukcím je tedy obecně mnohem rychlejší, než při použití instrukcí proměnné délky, tak jako je tomu v JVM. Existují dva formáty instrukcí, přesněji řečeno dva způsoby rozdělení 32bitového slova na jednotlivá bitová pole. V obou formátech má operační kód instrukce šířku osmi bitů, obsazení dalších 24 bitů je však odlišné.
11.1 První formát instrukcí
První formát se používá u instrukcí s trojicí operandů. Typické použití tohoto formátu je u aritmetických instrukcí:
Označení | Šířka (bitů) | Popis |
---|---|---|
B | 8 | první vstupní operand (zdrojová proměnná) |
C | 8 | druhý vstupní operand (zdrojová proměnná, numerická konstanta atd.) |
A | 8 | výsledek operace (proměnná pro uložení výsledku) |
OP | 8 | operační kód instrukce |
Celkem | 32 |
11.2 Druhý formát instrukcí
Druhý formát, resp. instrukce používající druhý formát dokážou adresovat pouze dva operandy, ovšem operand označený písmenem D má šířku šestnáct bitů a lze ho v některých instrukcích použít například k přímému uložení šestnáctibitové celočíselné konstanty se znaménkem (srov. s poměrně velkým množstvím instrukcí v bajtkódu JVM, které se pokouší o totéž, ale mnohem komplikovanějším způsobem):
Označení | Šířka (bitů) | Popis |
---|---|---|
D | 16 | vstupní operand (zdrojová proměnná) |
A | 8 | první operand nebo proměnná pro uložení výsledku |
OP | 8 | operační kód instrukce |
Celkem | 32 |
11.3 Seznam instrukcí
Nyní již následuje slíbená tabulka se seznamem instrukcí IR LuaJITu:
# | Instrukce | Operandy | Popis |
---|---|---|---|
1 | KNIL | base, base | nastaví sloty číslo A až D na hodnotu nil |
2 | KPRI | dest, pri | nastaví dest na hodnotu specifikovanou v D |
3 | KSHORT | dest, lits | přenese do dest šestnáctibitovou celočíselnou konstantu |
4 | KNUM | dest, num | přenese do dest zvolenou numerickou konstantu |
5 | KSTR | dest, str | přenese do dest zvolený řetězec |
6 | ADDVV | slot, slot | aritmetická operace: součet |
7 | SUBVV | slot, slot | aritmetická operace: rozdíl |
8 | MULVV | slot, slot | aritmetická operace: součin |
9 | DIVVV | slot, slot | aritmetická operace: podíl |
10 | MODVV | slot, slot | aritmetická operace: podíl modulo |
11 | ADDVN | slot, num | aritmetická operace s numerickou konstantou: součet |
12 | SUBVN | slot, num | aritmetická operace s numerickou konstantou: rozdíl |
13 | MULVN | slot, num | aritmetická operace s numerickou konstantou: součin |
14 | DIVVN | slot, num | aritmetická operace s numerickou konstantou: podíl |
15 | MODVN | slot, num | aritmetická operace s numerickou konstantou: podíl modulo |
16 | ADDNV | slot, num | aritmetická operace s numerickou konstantou: součet (otočené operandy) |
17 | SUBNV | slot, num | aritmetická operace s numerickou konstantou: rozdíl |
18 | MULNV | slot, num | aritmetická operace s numerickou konstantou: součin |
19 | DIVNV | slot, num | aritmetická operace s numerickou konstantou: podíl |
20 | MODNV | slot, num | aritmetická operace s numerickou konstantou: podíl modulo |
21 | JMP | adresa | nepodmíněný skok, popř. podmíněný skok, pokud mu předchází instrukce I* |
22 | IST | slot | následuje skok provedený při splnění podmínky A = true |
23 | ISF | slot | následuje skok provedený při splnění podmínky A = false |
24 | ISLT | slot, slot | následuje skok provedený při splnění podmínky A < D |
25 | ISGE | slot, slot | následuje skok provedený při splnění podmínky A ≥ D |
26 | ISLE | slot, slot | následuje skok provedený při splnění podmínky A ≤ D |
27 | ISGT | slot, slot | následuje skok provedený při splnění podmínky A > D |
28 | ISEQV | slot, slot | následuje skok provedený při splnění podmínky A = D |
29 | ISNEV | slot, slot | následuje skok provedený při splnění podmínky A ≠ D |
30 | ISEQN | slot, konstanta | následuje skok provedený při splnění podmínky A = konstanta |
31 | ISNEN | slot, konstanta | následuje skok provedený při splnění podmínky A ≠ konstanta |
32 | ISEQP | slot, pri | následuje skok provedený při splnění podmínky A = pri_type |
33 | ISNEP | slot, pri | následuje skok provedený při splnění podmínky A ≠ pri_type |
34 | ISEQS | slot, string | následuje skok provedený při splnění podmínky A = string |
34 | ISNES | slot, string | následuje skok provedený při splnění podmínky A ≠ string |
35 | MOV | kopie dat z D do A | |
36 | TNEW | dst, lit | vytvoření nové tabulky o velikosti specifikované v D (lit), reference na vytvořenou tabulku se uloží do A (dst) |
37 | TDUP | dst, tab | vytvoření nové tabulky na základě šablony specifikované v D (tab), reference na vytvořenou tabulku se uloží do A (dst) |
38 | GGET | dst, str | přečtení prvku z globální tabulky _G, v D (str) je uložen klíč (řetězec) |
39 | GSET | var, str | zápis prvku do globální tabulky _G, v D (str) je uložen klíč (řetězec) |
40 | TGETV | dst, var, var | čtení prvku z tabulky specifikované v B, klíč je uložen v proměnné |
41 | TGETS | dst, var, str | čtení prvku z tabulky specifikované v B, klíčem je řetězec |
42 | TGETB | dst, var, lit | čtení prvku z tabulky specifikované v B, klíčem je literál (konstanta, typicky celé číslo) |
43 | TSETV | var, var, var | zápis prvku do tabulky specifikované v B, klíč je uložen v proměnné |
44 | TSETS | var, var, str | zápis prvku do tabulky specifikované v B, klíčem je řetězec |
45 | TSETB | var, var, lit | zápis prvku do tabulky specifikované v B, klíčem je literál (konstanta, typicky celé číslo) |
46 | TSETM | base, num* | nastavení většího množství prvků dle vztahu: (A-1)[D], (A-1)[D+1], … = A, A+1, … |
47 | FNEW | dst, func | vytvoření uzávěru (closure) z prototypu funkce |
48 | CALL | base, lit, lit | volání funkce s předáním parametrů (standardní forma volání) |
49 | CALLM | base, lit, lit | volání funkce s předáním parametrů (speciální forma volání) |
50 | RET0 | A, D | návrat z funkce bez předání návratového hodnoty (A a D se ignorují) |
51 | RET1 | A, D | návrat z funkce s předáním jedné návratové hodnoty A (D se ignoruje) |
52 | RET | A, D | návrat z funkce s předáním jedné návratových hodnot A, A+1, … A+D-2 |
53 | FORI | A, D | test i≤max pro krok≥0 či i≥min pro krok<0 skok ZA tělo smyčky při nesplnění této podmínky |
54 | FORL | A, D | i=i+krok test i≤max pro krok≥0 či i≥min pro krok<0 skok na začátek smyčky při splnění této podmínky |
55 | ITERC | base, lit, lit | volání iterátoru A s parametry v A+1 a A+2 |
56 | ITERN | base, lit, lit | varianta instrukce ITERC pro funkci next() či pairs() |
57 | LOOP | rbase, jump | použití při JITování a detekci hot-spotů, neprovádí se skok! |
58 | ITERL | base, jump | iterátor pro smyčku typu for-each, kontrola na hodnotu odlišnou od nil a podmíněný skok |
59 | ISNEXT | base, jump | použito se speciální instrukcí ITERN pro podmíněný skok |
60 | FNEW | vytvoření nového uzávěru | |
61 | UCLO | uzavření (navázání) externích proměnných k funkci | |
62 | UGET | přečtení hodnoty externí vázané proměnné | |
62 | USETV | zápis nové hodnoty externí vázané proměnné | |
63 | USETS | zápis řetězce do externí vázané proměnné | |
64 | USETN | zápis čísla do externí vázané proměnné | |
65 | USETP | zápis vybrané primitivní hodnoty do externí vázané proměnné |
12. Odkazy na Internetu
- Wikipedia: Mezijazyk
http://cs.wikipedia.org/wiki/Mezijazyk - The LuaJIT Project
http://luajit.org/index.html - LuaJIT FAQ
http://luajit.org/faq.html - LuaJIT Performance Comparison
http://luajit.org/performance.html - LuaJIT 2.0 intellectual property disclosure and research opportunities
http://article.gmane.org/gmane.comp.lang.lua.general/58908 - LuaJIT Wiki
http://wiki.luajit.org/Home - LuaJIT 2.0 Bytecode Instructions
http://wiki.luajit.org/Bytecode-2.0 - Programming in Lua 9.1 – Coroutine Basics,
http://www.lua.org/pil/9.1.html - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Programming in Lua: 6 – More about Functions
http://www.lua.org/pil/6.html - Lua Lanes
http://kotisivu.dnainternet.net/askok/bin/lanes/ - Programming in Lua: 6.1 – Closures
http://www.lua.org/pil/6.1.html - Programming in Lua: 9.1 – Coroutine Basics
http://www.lua.org/pil/9.1.html - Programming in Lua: Numeric for
http://www.lua.org/pil/4.3.4.html - Programming in Lua: break and return
http://www.lua.org/pil/4.4.html - Programming in Lua: Tables
http://www.lua.org/pil/2.5.html - Programming in Lua: Table Constructors
http://www.lua.org/pil/3.6.html - Programovací jazyk Lua
http://palmknihy.cz/web/kniha/programovaci-jazyk-lua-12651.htm - Lua: Tables Tutorial
http://lua-users.org/wiki/TablesTutorial - Lua: Control Structure Tutorial
http://lua-users.org/wiki/ControlStructureTutorial - Lua Types Tutorial
http://lua-users.org/wiki/LuaTypesTutorial - Goto Statement in Lua
http://lua-users.org/wiki/GotoStatement - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Lua 5.2 sources – lopcodes.h
http://www.lua.org/source/5.2/lopcodes.h.html - Lua 5.2 sources – lopcodes.c
http://www.lua.org/source/5.2/lopcodes.c.html - Lambda the Ultimate: Coroutines in Lua,
http://lambda-the-ultimate.org/node/438 - Coroutines Tutorial,
http://lua-users.org/wiki/CoroutinesTutorial - Lua Coroutines Versus Python Generators,
http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators